2021“MINIEYE杯”中國大學生算法設計超級聯賽 第三場題解


2021“MINIEYE杯”中國大學生算法設計超級聯賽   第三場題解

賽后深知自己水平提高空間很大

6979 Photoshop Layers

題意:

給定一些RGB編碼的顏色,他們有兩種操作:

1:直接覆蓋之前的顏色

2:同之前的顏色進行累加 與255取最小值

他有一堆詢問,詢問 L-R 代表從L顏色到R顏色進行操作后的顏色是什么

思路:
思考思考我們能夠發現:

1、由1顏色定義可知道如果當前下標R的顏色操作就是1的話,那么最后的顏色就是R下標顏色

2、根據1我們可知,當前R只會與最近的那個1操作有關,剩下的就是累加2操作了

那么我們可以對於每一個當前下標idx維護兩個值:

①代表離當前下標idx最近的那個1操作

②當前RGB顏色相對於最近1操作的那個前綴和

這樣對於每一個詢問,只需先判斷L和維護的那個idx的位置關系:

①如果L <= idx,說明當前前綴和的值就是答案

②否則,需要Sum[R] - Sum[L - 1]進行差分得到一段區間的RGB顏色~(注意相減后需要與255進行比較)

代碼:

點擊查看代碼
#include <iostream>
using namespace std;
const int MAXN = 100005;
struct node
{
    int r,g,b;
    int idx;//離他最近的那個m = 1 
    node()
    {r = g = b = 0;}
    node operator - (const node &a)    const
    {
        node now;
        now.r = min(r - a.r,255);
        now.g = min(g - a.g,255);
        now.b = min(b - a.b,255);
        now.idx = idx;
        
        return now;
    }
    void ok()
    {
        r = min(r,255);
        g = min(g,255);
        b = min(b,255);
    }
};
node Get(string s)
{
    node now;
    int x = 0;
    for(int i = 0;i < 6;i+=2){
        int k1,k2;
        if(s[i] >= 'A')
        k1 = s[i] - 'A' + 10;
        else
        k1 = s[i] - '0';
        
        if(s[i + 1] >= 'A')
        k2 = s[i + 1] - 'A' + 10;
        else
        k2 = s[i + 1] - '0';
        
        x = k1*16 + k2;
        if(i == 0)
        now.r = x;
        else if(i == 2)
        now.g = x;
        else if(i == 4)
        now.b = x; 
    }
    return now;
}

void Go(node &a,node b,string s)
{//將b + s放入a中 
    for(int i = 0;i < 6;i+=2)
    {
        int k1,k2;
        if(s[i] >= 'A')
        k1 = s[i] - 'A' + 10;
        else
        k1 = s[i] - '0';
        
        if(s[i + 1] >= 'A')
        k2 = s[i + 1] - 'A' + 10;
        else
        k2 = s[i + 1] - '0';
        
        int x = k1*16 + k2;
        if(i == 0)
        a.r = b.r + x;
        else if(i == 2)
        a.g = b.g + x; 
        else if(i == 4)
        a.b = b.b + x;
    }
}
node sum[MAXN];
void print(node now)
{
    now.ok();
    char ans[10];
    int cnt = 0;
    int x = now.r/16;
    int y = now.r%16;
    if(x >= 10)
    {
        x -= 10;
        ans[++cnt] = 'A' + x;
    }
    else
    ans[++cnt] = x + '0';
    
    if(y >= 10)
    {
        y -= 10;
        ans[++cnt] = 'A' + y;
    }
    else
    ans[++cnt] = y + '0';
    
    x = now.g/16;
    y = now.g%16;
    if(x >= 10)
    {
        x -= 10;
        ans[++cnt] = 'A' + x;
    }
    else
    ans[++cnt] = x + '0';
    
    if(y >= 10)
    {
        y -= 10;
        ans[++cnt] = 'A' + y;
    }
    else
    ans[++cnt] = y + '0';
    
    x = now.b/16;
    y = now.b%16;
    if(x >= 10)
    {
        x -= 10;
        ans[++cnt] = 'A' + x;
    }
    else
    ans[++cnt] = x + '0';
    
    if(y >= 10)
    {
        y -= 10;
        ans[++cnt] = 'A' + y;
    }
    else
    ans[++cnt] = y + '0';
    
    for(int i = 1;i <= cnt;i++)
    printf("%c",ans[i]);
    puts("");
}
char ss[10]; 
int main()
{
    int t;
    scanf("%d",&t);
    sum[0].r = sum[0].g = sum[0].b = 0;
    sum[0].idx = 0;
    while(t--)
    {
        int n,q;
        scanf("%d %d",&n,&q);
        for(int i = 1;i <= n;++i)
        {
            int m;
            string s;
            scanf("%d  %s",&m,ss);
            s = ss;
            if(m == 1)
            {
                sum[i] = Get(s);
                sum[i].idx = i;
            } 
            else
            {
                Go(sum[i],sum[i - 1],s);
                sum[i].idx = sum[i - 1].idx;
            }
        }
        while(q--)
        {
            int le,ri;
            scanf("%d %d",&le,&ri);
            if(sum[ri].idx >= le)
            {//說明就是當前的前綴和了 
                print(sum[ri]);
            }
            else
            {//需要相減 
                print(sum[ri] - sum[le - 1]);
            }
        }
    }
    return 0;
}
/*
1
5 5
1 64C832
2 000100
2 010001
1 323C21
2 32C8C8

2 5


*/

QAQ因為是比賽時的代碼,所以有很多重復的函數塊...(偷懶

6981 Rise in Price

題意:

初始化寶石數量以及寶石價格為0。

給你一個寶石數量矩陣和寶石價格矩陣,每當你走到 i , j 位置時你可以拿走那個數字。

求最后你能獲得的最大寶石總價值(即寶石數量×寶石價格)。

你只能從(1,1)位置走到(n,n)位置且每次只能往右走或者往下走。

思路:

如果只有寶石數量這個矩陣的話,那就是一個非常經典的二維DP了...

我們先分析只有一個矩陣的時候的情況...

很顯然,當前狀態(i , j)可以從(i - 1 , j)和(i , j - 1)轉移而來

那么我們定義的狀態轉移函數就是:

其中f【i,j】表示的是走到當前下標(i , j)處我能獲得的最大寶石數量...

emmmmmm....但是這個題,它多了一個狀態,那就是還有鑽石的價格需要考慮,於是....我們就可以多加入一個狀態,然后再利用上面提到的狀態轉移方程...

由於保存鑽石數量和鑽石價格的兩個狀態是一致的...現在我們只分析保存鑽石數量這個狀態的狀態轉移怎么做

"笑死,多加一維不就成了!"

確實,不過看一眼數據,至少是1e6,得了,需要優化...

不過還是給出這個狀態轉移方程:

這里F【i,j,k】表示的是當我處在(i , j)位置時,擁有寶石數量為k + a[i][j]時,寶石價值的最大值

那么怎么優化呢?我們會發現,這個k其實不是每一個位置的狀態有的,也即1 - MAX(能拿到的最多寶石數)這些狀態有些是取不到的

那么對於每一個(i , j)我們只用保存能夠得到的狀態即可....因此這里的f[i,j]不是使用三維數組表示,而是使用表(vector or 鏈表)來進行存儲...

我們再仔細觀察...對於狀態(i,j,k1)(i,j,k2)若k1 < k2 且這個狀態(i,j,k1)<(i,j,k2)

那么k1這個狀態就是最差勁的,因為你不僅寶石數量少,而且價格還低(QAQ

所以這是一個無效狀態...

總歸:

①每次狀態轉移時我們只需要枚舉前兩個狀態其中的有效狀態

②對於當前得到的所有狀態(i,j)我們需要將“差勁狀態”剔除,"差勁狀態"滿足k1 < k2 && (i,j,k1)<(i,j,k2)

因此,既然i,j是一個表,何不按照k的遞增順序進行存儲呢,這樣對於每次進來的一個狀態 k'

我們能夠保證k'一定大於k ,下面只需要判斷(i,j,k1)<(i,j,k2)即可

如果k'這個狀態的(i,j,k')大於前面的(i,j,k),就可以將(i,j,k)直接刪除~

這里可能有點繞,我們講的明白一些:

就是這樣 ->

第一件事,我們假設

其中存儲的都是有效狀態,均是按照k值的大小單調遞增的

第二件事,我們轉移至(i,j)狀態時,由於前兩個狀態都是遞增有序的,那么我們其實只需要在O(N)的時間內合並兩個數組即可!

由於a[i][j](寶石數量)和b[i][j](寶石價值)兩個數組只會影響當前(i,j)狀態,於是合並時並不需要考慮a和b帶來的影響

合並操作:

如果k1 < k2那么我們肯定優先考慮將k1加入當前(i,j)狀態集

①如果當前(i,j)狀態集沒有狀態...我們直接加入

②否則將狀態集最后那個狀態的(i,j,k')與當前(i - 1,j,k1)進行比較,如果小於它的話,將(i,j,k')刪除(注意!這是一個循環的操作

因為當前(i,j)狀態集其中的k'一定是小於k1的!

最后將得到的所有狀態集中k加上a[i][j]以及(i ,j,k)加上b[i][j]即可得到當前(i,j)所有有效狀態~

代碼:

 

點擊查看代碼
#include <iostream>
#include <algorithm>
#include <vector>
#pragma GCC opetimize(2)
using namespace std;
const int MAXN = 107;
typedef long long ll;
struct node{
    int k;//寶石數 
    int w;//權值(賣出的最大價格) 
    node(int k = 0,int w = 0):k(k),w(w){}
    bool operator < (const node &a)    const
    {return k < a.k;}
};
vector<node> dp[MAXN][MAXN];
int a[MAXN][MAXN];
int b[MAXN][MAXN];
/*
2
4
2 3 1 5
6 3 2 4
3 5 1 4
5 2 4 1
3 2 5 1
2 4 3 5
1 2 3 4
4 3 5 3

4
2 3 1 5
6 3 2 4
3 5 1 4
5 2 4 1
3 2 5 1
2 4 3 5
1 2 3 4
4 3 5 3
*/
int main()
{
//    freopen("1009.in","r",stdin);
//    freopen("ans.out","w",stdout);
    int t;
    scanf("%d",&t);
    dp[0][1].push_back(node(0,0));
    dp[1][0].push_back(node(0,0));
    while(t--)
    {
        int n;
        scanf("%d",&n);
        
        for(int i = 1;i <= n;++i)
        for(int j = 1;j <= n;++j)
        scanf("%d",&a[i][j]);
        
        for(int i = 1;i <= n;++i)
        for(int j = 1;j <= n;++j)
        scanf("%d",&b[i][j]);

        for(int i = 1;i <= n;++i)
        for(int j = 1;j <= n;++j)
        {
            dp[i][j].clear();
            int k1 = dp[i - 1][j].size();
            int k2 = dp[i][j - 1].size();
            int i1 = 0,i2 = 0;
            while(i1 < k1 && i2 < k2)
            {
                while(i1 < k1 && dp[i - 1][j][i1].k < dp[i][j - 1][i2].k)
                {
                    while(dp[i][j].size() && dp[i][j].back().w <= dp[i - 1][j][i1].w)
                    dp[i][j].pop_back();
                    
                    dp[i][j].push_back(dp[i - 1][j][i1]);
                    ++i1;
                }
                
                while(i2 < k2 && dp[i][j - 1][i2].k <= dp[i - 1][j][i1].k)
                {
                    while(dp[i][j].size() && dp[i][j].back().w <= dp[i][j - 1][i2].w)
                    dp[i][j].pop_back();
                    
                    dp[i][j].push_back(dp[i][j - 1][i2]);
                    ++i2;
                }
            }
            while(i1 < k1)
            {
                while(dp[i][j].size() && dp[i][j].back().w <= dp[i - 1][j][i1].w)
                dp[i][j].pop_back();
                
                dp[i][j].push_back(dp[i - 1][j][i1]);
                ++i1;
            }
            while(i2 < k2)
            {
                while(dp[i][j].size() && dp[i][j].back().w <= dp[i][j - 1][i2].w)
                dp[i][j].pop_back();
                
                dp[i][j].push_back(dp[i][j - 1][i2]);
                ++i2;
            }
            for(int k = 0;k < dp[i][j].size();k++)
            {
                dp[i][j][k].w += b[i][j];
                dp[i][j][k].k += a[i][j];
            }
//            for(int k = 0;k < dp[i][j].size();k++)
//            printf(">>%d %d -> %d %d\n",i,j,dp[i][j][k].k,dp[i][j][k].w);
            //將i,j中所有 k1 < k2 && w1 < w2的狀態刪除 
        }
        ll ans = 0;
        for(int i = 0;i < dp[n][n].size();i++)
        ans = max(ans,1ll*dp[n][n][i].k*dp[n][n][i].w);
        printf("%lld\n",ans);
    }
    return 0;
}

6982 Road Discount

題意:

給定一些邊,其中一條邊有兩個權值,一個是初始權值,一個是折扣權值。

問:當我可以任意取K∈[0,n]條折扣權值時,其構成最小生成樹權值大小是多少?

思路:

QAQ當時沒有細想這個題,現在只能面向題解編程了,可能有些地方理解不透徹,還希望各位dalao指正~

第一件事:

如果我們K = 0,這時候構成一顆最小生成樹就是用所有初始權值來構造

如果我們K = n,這時候構成一顆最小生成樹就是用所有折扣權值來構造

考慮K ∈ [1 , n - 1],思考這樣一個問題:現在的K條件下構成最小生成樹的邊是否存在以上兩種特殊情況之外的邊呢?

答案肯定的,我們簡單證明一下:

假設K = 0時構成最小生成樹的邊集是e1,K = n時是e2

如果K ∈ [1 , n - 1],其中構成最小生成樹的邊e存在e ∉ {e1 , e2};

分兩種情況:

①這條邊是初始權值

②這條邊是折扣權值

如果是①,那么顯然選擇e這條邊的會使得我目前的答案(K ∈ [1 , n - 1])更優,那么這條邊一定會存在於e1當中!如果不在的話,那e1中的邊不就不是最優的了!

顯然,對於第二種情況e這條邊一定存在於e2這條邊中!

因此假設不成立。

K ∈ [1 , n - 1]時所有邊都存在於 {e1 , e2}中。

於是我們可以在O(MlogN)時間內求出我們需要的所有邊,然后我們來思考這樣一個問題:

如何在現在的{e1,e2}集合里面挑出K個e2的邊,挑出N - K個e1的邊?

題解的方法是,將所有的折扣邊都加上一個給定的值,這樣的話我在構成最小生成樹的時候就能產生不同的選擇(到底是選擇折扣邊還是初始權值呢?)

QAQ到這里我就無能為力了,可能是一種是一種常用方法吧,證明我也不會,如果有daolao會能不能教教我

由於邊權大小差最多不會超過1000,於是我們只需要枚舉[0,1000]所有的可能的給定的值就好了,然后求1001次該狀態下的最小生成樹

所有加上c固定邊權的最小生成樹我們需要保存以下狀態:

①此時我們拿走的折扣邊數量cnt

②此時的最小生成樹的權值和sum

設c = [0,1000]這個固定的值,很容易就能想到,當c遞增時,所選的cnt是非遞增的(因為這樣折扣邊 + c的值會越來越大,直到大於原始邊權,就不會再拿折扣邊了

於是對於每一個詢問的K值,我們可以二分(暴力)去離線回答詢問,在所有的c ∈ [0,1000]數組中尋找第一個小於等於K的cnt值,這樣,所取的下標c值就是當前加上的固定邊權

然后sum - c*k就是答案~

代碼

點擊查看代碼
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1005;
const int MAXM = 200005;
struct node
{
    int x,y;
    int w;
}a[MAXM],b[MAXM];
int fa[MAXN];
int n,m;
void ini()
{for(int i = 0;i <= n;i++)    fa[i] = i;}
int Find(int x)
{
    if(x == fa[x])    return x;
    return fa[x] = Find(fa[x]);
}
bool merge(int x,int y)
{
    x = Find(x);
    y = Find(y);
    if(x != y)
    {
        fa[x] = y;
        return true;
    }
    return false;
}
bool cmp(node a,node b)
{return a.w < b.w;}
void Minus(node *a)
{//將初始的最小生成樹建立號,這時候的邊就是以后會選擇的最好的邊 
    ini();
    sort(a,a + m,cmp);
    for(int i = 0,cnt = 0;i < m;i++)
    if(merge(a[i].x,a[i].y))    a[cnt++] = a[i];
}
struct NO
{
    int sum;//權值總和 
    int cnt;//黑邊數量 
    NO(int sum = 0,int cnt = 0):sum(sum),cnt(cnt){}
}all[MAXN];
NO go(int c)
{//每條邊添加權值c,此時最多能拿多少條黑邊呢(最小生成樹中)? 
    ini();
    int cnt = 0;//黑邊數量
    int num = 0;//最小生成樹中節點個數,其實沒啥用,因為一定可以構成一顆最小生成樹 
    int k1 = 0,k2 = 0;
    
    int sum = 0;
    while(k1 < n - 1 && k2 < n - 1)
    {//至多選擇n - 1條邊 
        if(a[k1].w <= b[k2].w + c)
        {//選擇白邊更優 
            if(merge(a[k1].x,a[k1].y))
            sum += a[k1].w,num += 1;
            ++k1; 
        }
        else
        {//選擇黑邊加上一個權值還是最優的 
            if(merge(b[k2].x,b[k2].y))
            sum += b[k2].w + c,++cnt,num += 1;
            ++k2;
        }
        if(num == n - 1)
        break;
    }
    while(k1 < n - 1)
    {
        if(merge(a[k1].x,a[k1].y))
        sum += a[k1].w;
        ++k1;
    }
    
    while(k2 < n - 1)
    {
        if(merge(b[k2].x,b[k2].y))
        sum += b[k2].w + c,++cnt;
        ++k2;
    }
    return NO(sum,cnt);
}
int ask(int x)
{//由於all數組是權值越來越大的,那么它的黑邊數也會越來越少,因此找到第一個 
//黑邊數符合 <= x的那個權值,就是答案
    for(int i = 0;i <= 1000;i++)//減去多加的那一部分 
    if(all[i].cnt <= x)    return all[i].sum - x*i;
    return -1;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&n,&m);
        for(int i = 0;i < m;i++)
        {
            scanf("%d %d %d %d",&a[i].x,&a[i].y,&a[i].w,&b[i].w);
            b[i].x = a[i].x;
            b[i].y = a[i].y;
        }
        
        Minus(a);
        Minus(b);
        for(int i = 0;i <= 1000;i++)
        all[i] = go(i);//預處理當所有黑邊加上一個數i后所構成的最小生成樹的黑邊數量 
        
        for(int i = 0;i < n;i++)
        {//拿去i條黑邊時構成的最小生成樹的權值和 
            printf("%d\n",ask(i)); 
        }
    }
    return 0;
}

待補知識點:

FFT、熟練潑糞(樹鏈剖分) +平衡樹、最小獨立集

嘮一嘴:FHQ Treap真的太厲害了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM