「刷題筆記」DP優化-狀壓-EX
阿新 • • 發佈:2020-08-02
棋盤
需要注意的幾點:
- 題面編號都是從0開始的,所以第1行實際指的是中間那行
- 對\(2^{32}\)取模,其實就是\(unsigned\ int\),直接自然溢位啥事沒有
- 棋子攻擊範圍不會旋轉
首先,我們得找出所有滿足條件的同一行的狀態,在此之前,我們要先處理出狀態\(S\)下,第\(k\)行能被棋子攻擊到的格子\(at[k][S]\)
先欽定自己不能攻擊自己
再列舉每一個狀態中每一個1,然後對攻擊範圍模板進行左移/右移操作,把這一行這種狀態能攻擊到的格子或上他
接下來,列舉每一個狀態,如果\(at[1][i]&i==0\),則記錄下這種合法的情況。
這個時候我們就可以列舉每一行的狀態來求解了,但是這樣時間複雜度是\(O(n\cdot 2^{2m})\)
(頹了一下題解)
其實,每一種狀態都是由之前的某種狀態轉移來的,且可以重複,那就說明如果兩個狀態可以排在一起,他們就可以多次排在一起
考慮矩陣快速冪優化
設初始矩陣中\(A[i][j]=1\)表示\(j\)可以排在\(i\)的後面
則有這樣的柿子:\(f[i][j]=f[i][k]\cdot f[k][j]\)
這樣的情況下,我們求\(A^n\),因為第一種情況是為空,所以\(\sum\limits_{i=1}^{cnt}A^n[1][i]\)即為答案,意為開始為空,經過\(n\)行後最後一行為狀態\(i\)的總數
code:
#include<bits/stdc++.h> using namespace std; #define ll unsigned int #define ull unsigned long long #define ZZ_zuozhe #define M 6 #define N 80 ll n,m,p,k; ll lim[4]; ll at[4][1<<M]; ll sit[N],cnt=0; struct mat { ll a[N][N]; mat(){memset(a,0,sizeof a);} }; mat operator*(mat a,mat b) { mat c; for(int i=1;i<=cnt;i++)for(int j=1;j<=cnt;j++)for(int k=1;k<=cnt;k++) { c.a[i][j]=c.a[i][j]+a.a[i][k]*b.a[k][j]; } return c; } mat ksm(mat a,ll b) { mat res=a; b--; while(b) { if(b&1)res=res*a; a=a*a; b>>=1; } return res; } mat a,ans; ll t; int main() { scanf("%u%u%u%u",&n,&m,&p,&k); for(int i=0;i<3;i++) { for(int j=0;j<p;j++) { scanf("%u",&t); if(t)lim[i]|=(1<<j); } } lim[1]-=(1<<k); ll all=(1<<m)-1; for(int i=0;i<=all;i++) { at[0][i]=at[1][i]=at[2][i]=0; for(int j=0,p=i;p;j++,p>>=1) { if((p&1)==0)continue; at[0][i]|=(j<k)?lim[0]>>(k-j):lim[0]<<(j-k); at[1][i]|=(j<k)?lim[1]>>(k-j):lim[1]<<(j-k); at[2][i]|=(j<k)?lim[2]>>(k-j):lim[2]<<(j-k);//處理每種狀態每行能夠攻擊到的位置集合 } } for(int i=0;i<=all;i++) { if((i&at[1][i])==0)sit[++cnt]=i; } for(int i=1;i<=cnt;i++) { for(int j=1;j<=cnt;j++) { if(((sit[i]&at[0][sit[j]])==0)&&((sit[j]&at[2][sit[i]])==0))a.a[i][j]++; //處理初始矩陣,如果j放在i後面不衝突就++ } } a.a[1][1]=1; ans=ksm(a,n); ll aa=0; for(int i=1;i<=cnt;i++) { aa+=ans.a[1][i]; } printf("%u\n",aa); return 0; }