2021“MINIEYE杯”中國大學生演算法設計超級聯賽 第三場題解
2021“MINIEYE杯”中國大學生演算法設計超級聯賽 第三場題解
賽後深知自己水平提高空間很大
6979Photoshop 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因為是比賽時的程式碼,所以有很多重複的函式塊...(偷懶
6981Rise 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;
}
未完待續...