P4158 [SCOI2009]粉刷匠
阿新 • • 發佈:2020-12-09
我們不妨先考慮只有一行的情形。
我們做兩個字首和\(red_i,bule_i\)分別表示前\(i\)個裡有多少個紅色塊和藍色塊。
設\(f[i][k]\)為做到第\(i\)塊,此時用了\(k\)次塗刷的最大收益。
我們思考如下問題:既然重複塗色沒有收益,那麼我們強制讓我們的塗色方案沒有重疊的情況,即讓我們對於這一行的方案如下圖:
可以看出一段木塊被分割成若干塊(無色代表沒塗色。
我們只要從\(i\)向前列舉上一段的終點在哪即可轉移,對於\(i\)這塊不塗色的情況我們直接拿\(i - 1\)的答案來覆蓋即可
所以對於一條木塊我們有了\(O(m^3)\)的做法來求出\(f\)
for(int r = 1;r <= m;++r){ for(int k = 1;k <= m;++k){ f[r][k] = f[r - 1][k]; for(int l = 1;l <= r;++l){ f[r][k] = std::max(f[l - 1][k - 1] + red[r] - red[l - 1],f[r][k]); f[r][k] = std::max(f[l - 1][k - 1] + bule[r] - bule[l - 1],f[r][k]); } } }
我們接下來考慮我們做完了一條木塊怎麼統計答案:
for(int to = t;to >= 0;-- to)
for(int k = 0;k <= std::min(m,(ll)to);++k)
fans[to] = std::max(fans[to],fans[to - k] + f[m][k]),ans = std::max(ans,fans[to]);
類似於一維揹包即可。(注意列舉\(k\)時不要超出陣列\(f\)的大小,因為這個我調了好久)
所以最後的複雜度是\(O(n * (m ^ 3 + tm))\)
喜聞樂見的程式碼環節
#include<iostream> #include<cstdio> #include<cstring> #define ll long long ll n,m,t,ans = 0,f[55][55]; ll red[55],bule[55]; ll fans[2505]; void init(){ memset(f,0,sizeof(f)); memset(red,0,sizeof(red)); memset(bule,0,sizeof(bule)); } int main(){ // freopen("q.in","r",stdin); // freopen("q.out","w",stdout); memset(fans,0,sizeof(fans)); scanf("%lld%lld%lld",&n,&m,&t); for(int i = 1;i <= n;++i){ init(); char s[55]; scanf("%s",s + 1); for(int i = 1;i <= m;++i){ red[i] = red[i - 1]; bule[i] = bule[i - 1]; if(s[i] == '0') red[i] ++ ; else bule[i] ++ ; } for(int r = 1;r <= m;++r){ for(int k = 1;k <= m;++k){ f[r][k] = f[r - 1][k]; for(int l = 1;l <= r;++l){ f[r][k] = std::max(f[l - 1][k - 1] + red[r] - red[l - 1],f[r][k]); f[r][k] = std::max(f[l - 1][k - 1] + bule[r] - bule[l - 1],f[r][k]); } } } // for(int i = 1;i <= m;++i,puts("")) // for(int k = 1;k <= m;++k) // std::cout<<f[i][k]<<" "; for(int to = t;to >= 0;-- to) for(int k = 0;k <= std::min(m,(ll)to);++k) fans[to] = std::max(fans[to],fans[to - k] + f[m][k]),ans = std::max(ans,fans[to]); } std::cout<<ans<<std::endl; }