正睿 2019 省選十連測 Day1 T2 壕
阿新 • • 發佈:2018-12-03
通過貪心來不斷找性質最後決定dp形式的一道思維題
先把陣列轉化成還需要加多少才能變成全 0(也就是在模 7 意義下取反)由於是
區間加的操作,可以直接轉差分陣列(此處首位各加上一個 0 然後差分),變成
兩個位置上一加一減。
一個非常直觀的性質是,如果我們把最優策略下的每個差分陣列上的操作,看成
在操作的兩個位置之間連一條邊,那麼連出來的圖肯定是一個森林,且每棵樹所
有位置的值(注意:是差分陣列中的值)的和在模 7 意義下應該為 0(因為每次
操作,差分陣列上兩個對應位置的和是沒有產生變化的)。而對於一個森林來講,
最後的操作次數是T-1。T是樹的大小。
進而我們可以發現,我們所要做的就是把差分陣列上面的值分成儘量多的,值的
和在模7意義下為0的組,對每一組都可以用大小減1次操作搞成全0。
顯然的貪心是,0全部放一組,1和6一一配對,2和5一一配對,3和4一一配對。
初步貪心之後,剩下的值只會有三種。
令f[x][y][z]表示三種數分別有x,y,z個的時候最多能搞出來多少組。
列舉分組進行轉移,複雜度視實現而定,一般來講在O(n^4)到O(n^6)之間。
出題人想良心一點,所以只要不是暴力轉移,應該都能卡著
時間過去。
轉移的時候明顯不需要列舉分組。
可以用一個新的狀態f[x][y][z][k]。
表示已經三種數分別用了x,y,z,個,
當前最後一組的值模7意義下是k的情況下,前面組好的全0的組最多有多少個。
那麼轉移是O(1)的。時間複雜度可以降到O(n^3)。
但需要滾動陣列。標程就是這樣實現的。
#include<iostream> #include<cctype> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<cstdlib> #include<algorithm> #define N 1100 #define eps 1e-7 #define inf 1e9+7 #define ll long long using namespace std; inline int read() { char ch=0; int x=0,flag=1; while(!isdigit(ch)){ch=getchar();if(ch=='-')flag=-1;} while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*flag; } int a,b,c,na,nb,nc,f[N],cnt[N],dp[50][200][500][7]; int dfs(int x,int y,int z,int k) { #define o dp[x][y][z][k] if(!x&&!y&&!z)o=0; if(o!=-1)return o; o=inf; if(x)o=min(o,dfs(x-1,y,z,(k+a)%7)+(((k+a)%7)?1:0)); if(y)o=min(o,dfs(x,y-1,z,(k+b)%7)+(((k+b)%7)?1:0)); if(z)o=min(o,dfs(x,y,z-1,(k+c)%7)+(((k+c)%7)?1:0)); return o; #undef o } int main() { int n=read(),i,ans=0; for(i=1;i<=n;i++)f[i]=read(),cnt[(f[i]-f[i-1]+7)%7]++; if(cnt[1]>=cnt[6])a=1;else a=6;na=abs(cnt[1]-cnt[6]);ans+=min(cnt[1],cnt[6]); if(cnt[2]>=cnt[5])b=2;else b=5;nb=abs(cnt[2]-cnt[5]);ans+=min(cnt[2],cnt[5]); if(cnt[3]>=cnt[4])c=3;else c=4;nc=abs(cnt[3]-cnt[4]);ans+=min(cnt[3],cnt[4]); if(nc<na) { swap(c,a); swap(nc,na); } if(nc<nb) { swap(c,b); swap(nc,nb); } if(nb<na) { swap(b,a); swap(nb,na); } memset(dp,-1,sizeof(dp)); ans+=dfs(na,nb,nc,0); printf("%d",ans); return 0; }