1. 程式人生 > 實用技巧 >「刷題筆記」DP優化-狀壓-EX

「刷題筆記」DP優化-狀壓-EX

棋盤

需要注意的幾點:

  • 題面編號都是從0開始的,所以第1行實際指的是中間那行
  • \(2^{32}\)取模,其實就是\(unsigned\ int\),直接自然溢位啥事沒有
  • 棋子攻擊範圍不會旋轉
    首先,我們得找出所有滿足條件的同一行的狀態,在此之前,我們要先處理出狀態\(S\)下,第\(k\)行能被棋子攻擊到的格子\(at[k][S]\)
    先欽定自己不能攻擊自己
    再列舉每一個狀態中每一個1,然後對攻擊範圍模板進行左移/右移操作,把這一行這種狀態能攻擊到的格子或上他
    接下來,列舉每一個狀態,如果\(at[1][i]&i==0\),則記錄下這種合法的情況。
    這個時候我們就可以列舉每一行的狀態來求解了,但是這樣時間複雜度是\(O(n\cdot 2^{2m})\)
    的,而在\(n\)給到\(1e6\)的情況下無疑會炸掉,需要考慮進一步的優化
    (頹了一下題解)
    其實,每一種狀態都是由之前的某種狀態轉移來的,且可以重複,那就說明如果兩個狀態可以排在一起,他們就可以多次排在一起
    考慮矩陣快速冪優化
    設初始矩陣中\(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;
}