COCI2011/2012 Contest#1 F 狀壓加速dp
阿新 • • 發佈:2020-08-27
COCI2011/2012 Contest#1 F 狀壓加速dp
首先是一個非常Naive的dp,令\(dp[i][x][y]\)表示\(i\)時刻\(x,y\)是否能被跳到
列舉,然後轉移,如果滾動陣列,就可以做到\(O(n^2)\)空間,\(O(Tn^2)\)時間複雜度
這顯然是TLE的。。。
\[\ \]
注意到題目的\(n\leq 30\),可以直接用一個int存在某一行/列的答案
設時刻\(i\)第\(j\)列的答案為\(dp[i][j]\)
假設不考慮答案的限制,兩之間轉移可以做到\(O(1)\),即
1.\(dp[i][j\pm 1]\)左/右移兩位
2.\(dp[i][j\pm 2]\)
兩者轉移即可,但是涉及到倍數的限制,設\(can[i][j]\)為\(i\)時刻\(j\)列的可行跳躍位置
則只需要最後的時候讓\(dp[i][j]\)與\(can[i][j]\)取交集即可
如果直接列舉倍數,複雜度上限是\(O(n^2 T)\)
考慮分塊決策,設將\([1,D]\)的因數挑出來額外記錄一個數組\(can2[x][j]\)表示值為\(x\)的第\(j\)列有那些
不直接列舉他們,而是在每次訪問時考慮他們對於\(can[i][j]\)的貢獻
在優秀實現下,複雜度上限為\(O(n^2\frac{T}{D+1}+T (D+\sum_{t=1}^{D} \frac{1}{t} n))=O(n^2\frac{T}{D+1}+T (n\ln D+D))\)
這個實現上來說,就是列舉時間\(i\)後,判斷是否滿足$t|i \(,然後再將\)can2[t][j]\(貢獻到\)can[i][j]$
顯然,\(t|i\)成立的次數就是$T\sum_{t=1}^{D} \frac{1}{t} \(,也就是要迴圈這麼多次取貢獻\)j$這一維
調整一下\(D\)的引數
#include<bits/stdc++.h> using namespace std; #pragma GCC optimize(2) #define reg register #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) const int N=30,D=7; int n,m,a[N][N],dp[2][N]; int can[1000010][N],t[D+1][N]; int main(){ scanf("%d%d",&n,&m); int sx,sy; scanf("%d%d",&sx,&sy),sx--,sy--; rep(i,0,n-1) rep(j,0,n-1) { scanf("%d",&a[i][j]); if(a[i][j]<=D) t[a[i][j]][i]|=1<<j; else for(reg int T=a[i][j];T<=m;T+=a[i][j]) can[T][i]|=1<<j; } int cur=0; dp[cur][sx]=1<<sy; rep(i,1,m) { rep(j,1,D) if(i%j==0) rep(k,0,n-1) can[i][k]|=t[j][k]; rep(j,0,n-1) { dp[!cur][j]=0; if(j) { dp[!cur][j]|=dp[cur][j-1]<<2; dp[!cur][j]|=dp[cur][j-1]>>2; } if(j<n-1) { dp[!cur][j]|=dp[cur][j+1]<<2; dp[!cur][j]|=dp[cur][j+1]>>2; } if(j>1) { dp[!cur][j]|=dp[cur][j-2]<<1; dp[!cur][j]|=dp[cur][j-2]>>1; } if(j<n-2) { dp[!cur][j]|=dp[cur][j+2]<<1; dp[!cur][j]|=dp[cur][j+2]>>1; } dp[!cur][j]&=can[i][j]; } cur^=1; } int ans=0; rep(i,0,n-1) rep(j,0,n-1) if(dp[cur][i]&(1<<j)) ans++; printf("%d\n",ans); rep(i,0,n-1) rep(j,0,n-1) if(dp[cur][i]&(1<<j)) printf("%d %d\n",i+1,j+1); }