1. 程式人生 > 實用技巧 >【狀壓】ARC058E 和風いろはちゃん / Iroha and Haiku

【狀壓】ARC058E 和風いろはちゃん / Iroha and Haiku

題目連結

是我想不到的的狀壓方式。

首先,條件中的“存在”比較麻煩,由於一個相同的數列可能包含多個滿足條件的三元組,用乘法原理直接正面剛會出現重複計數的情況。

正難則反,所以我們反向考慮用總的\(10^n\)的方案數減去不合法的數列。


注意到\(X+Y+Z\leq 17\),所以可以考慮狀壓。

狀壓的方式非常不容易想到,具體是:

我們用二進位制位數來表示數字,例如:\(5->10000 \ ,\ 3->100\)

如果\(5\)後面是加上一個\(3\),即序列的一個字尾是\(53\),那麼這個狀態就用\(10000100\)表示(把兩個數的二進位制表示拼在一起)。同時,這個新的二進位制狀態的第\(8\)位是\(1\)

,表示\(5+3=8\);第\(3\)位為\(1\),表示最後一個數是\(3\),那麼倒數第二個數就可以用\(8-3=5\)算出來。

舉個更復雜的例子,假如字尾是\(536\)(五三打錢),我們的二進位制狀態就是\(10000100100000\),那麼我們可以通過這個二進位制狀態得到所有\(536\)這個字尾的所有情況:

\(14\)(第\(14\)位上為\(1,14=5+3+6\)

\(9\)(第\(9\)位上為\(1,9=3+6\)

\(6\)(第\(6\)位上為\(1,6=6\)

\(5\)\(14-9=5=5\)

\(8\)\(14-6=8=5+3\)

\(3\)\(9-6=3=3\)

(這實際上就是一種變相的字尾和)

那麼什麼樣的狀態表示俳句呢?

俳句是連續三段數,和分別為\(X,Y,Z\),所以俳句對應的二進位制第\(X+Y+Z,Y+Z,Z\)位上都是\(1\)

用這種方式表示狀態,狀態的最大位數就是最大和,所以狀態數是\(2^{X+Y+Z}\)種,在時空承受範圍之內。


關於\(dp\)過程,設\(dp[i][s]\)表示轉移到第\(i\)位,數列結尾的狀態為\(s\)的方案數。

轉移時列舉後繼數字(第\(i+1\)位)為\(j\),那麼新的二進位制狀態就是\(s'=(s<<j)|(1<<(j-1))\)

則有\(dp[i+1][s']+=dp[i][s]\)

邊界情況\(dp[0][0]=1\)

另外,這裡有一點需要提一下,就是\(n\)的範圍比較大,而每個數字的範圍在\([1,10]\),那麼最大的數字和應該是\(40\),但是我們只需要\(dp\)和是\(x+y+z\)的部分,因為這個是字尾和,前面的數不管是啥並不重要,我們的\(i\)指標在不斷移動的時候,就計算了不同位置的方案。


►Code View

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<string>
#include<map>
#include<cmath>
using namespace std;
#define N 45
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define LL long long
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
	return f*x;
}
int n,x,y,z;
LL dp[N][1<<18];
LL ksm(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
int main()
{
	n=rd(),x=rd(),y=rd(),z=rd();
	int jdg=((1<<(x+y+z-1))|(1<<(y+z-1))|(1<<(z-1)));
	dp[0][0]=1;
	int sum=(1<<(x+y+z))-1;//最高位是x+y+z-1 所以最大和就是所有位數都是1 也就是2^(x+y+z)-1 
	for(int i=0;i<=n-1;i++)
		for(int s=0;s<=sum;s++)
		{
			if(dp[i][s]==0) continue;//不會產生貢獻
			for(int j=1;j<=10;j++)//列舉第i+1位是什麼數字
			{
				int t=(s<<j)|(1<<(j-1));
				t&=sum;//防止溢位
				if((t&jdg)==jdg) continue;//存在俳句
				else dp[i+1][t]=(dp[i+1][t]+dp[i][s])%MOD;
			}
		}
	LL ans=0;
	for(int s=0;s<=sum;s++)
		ans=(ans+dp[n][s])%MOD;
	ans=(ksm(10,n)-ans+MOD)%MOD;
	printf("%lld\n",ans);
    return 0;
}