【校内模拟】ふでペン ~ボールペン~(状压DP)(矩阵快速幂)(BM算法)(多项式取模优化线性递推)

简要题意:

对称不算同构,一共有 7 7 7 种四骨牌(玩过Tetris吗?)。

现在给你一个 4 ∗ n 4*n 4n 的矩形,你需要用四骨牌不重不漏地覆盖每个格子,问有多少种方案?对一个众所周知的模数取模。

题解:

其实应该猜想得到这种东西肯定是矩阵快速幂,既然是矩阵快速幂了,那就肯定有线性递推式。

不知道有没有人和我一样无所畏惧尝试手推线性递推式

存在线性递推式的临界其实就是指数增长到多项式增长。

然而这道题前30多项都是指数增长,我考场上推到n=4的时候就已经崩溃了,推了各种组合已经有50多种了,最后推不下去了

n=3的情况我还把三个长条给搞掉了

所以这道题其实真正能求线性递推式的方式只有一个:把暴力的DP写出来再说,之后能BM的BM,能上线代黑科技的就上线代黑科技。

考虑状压DP,我们转移一个新的行的时候新填的骨牌必须压在新的行上面。

同时我们必须把马上要丢掉的那行填满。

注意到我们可用的最宽的骨牌就是长条状,长度为 4 4 4,所以状态压缩需要压前 3 3 3行。

显然 2 12 2^{12} 212个状态里面相当大一部分都是废的。

我们把所有能用的骨牌也用状压表示,然后算一下 2 16 2^{16} 216个转移。

处理出转移矩阵后DP出前若干项后求线性递推式即可,(其实没有必要,我直接转移的。)

可以直接上BM,也可以麻烦一点,把所有能从初始状态到达并且回到初始状态的那些状态拿出来建矩阵,然后求特征多项式。(肉眼可见的难写)

然后直接多项式取模优化线性递推即可。

下面两份代码,一份状压DP+BM打表(不到1s出表),一份AC。

不知道为什么,std注释里面的那个BM好像是错的,求出来的递推式长度为98,最短递推式长度应该是35(不算自己那一项)


代码(打表求线性递推式):

#include
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int po(int a,int b){
	int r=1;for(;b;b>>=1,a=mul(a,a))
	if(b&1)r=mul(r,a);return r;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return ;}ex_gcd(b,a%b,y,x);y-=a/b*x;
}
inline int inv(int a){
	int x,y;ex_gcd(mod,a,y,x);
	return x+(x>>31&mod);
}

typedef std::vector<int> Poly;

Poly operator+(cs Poly &a,cs Poly &b){
	Poly c=a;if(a.size()<b.size())c.resize(b.size());
	for(int re i=0,li=b.size();i<li;++i)Inc(c[i],b[i]);
	return c;
}

Poly operator-(cs Poly &a,cs Poly &b){
	Poly c=a;if(a.size()<b.size())c.resize(b.size());
	for(int re i=0,li=b.size();i<li;++i)Dec(c[i],b[i]);
	return c;
}

namespace BM{
	cs int M=1e3+7;
	Poly R[M];int L,ct;
	int a[M],ps[M],dlt[M];
	Poly &solve(){ct=0;
		for(int re i=0;i<=L;++i){int d=a[i];
			for(int re j=1;j<R[ct].size();++j)
			Dec(d,mul(R[ct][j],a[i-j]));
			if(!d)continue;ps[ct]=i,dlt[ct]=d;
			if(!ct){
				R[ct+1].clear();
				R[++ct].resize(i+1);
				continue;
			}
			else {
				int coef=mul(d,inv(dlt[ct-1]));R[ct+1].clear();
				R[ct+1].resize(i-ps[ct-1]),R[ct+1].push_back(coef);
				for(int re j=1;j<R[ct-1].size();++j)
				R[ct+1].push_back(mul(mod-coef,R[ct-1][j]));
				R[ct+1]=R[ct+1]+R[ct];++ct;
			}
		}return R[ct];
	}
}
using BM::L;
using BM::a;

inline int get(int s,int x,int y){return (s>>(x+(y<<2)))&1;}
inline int set(int s,int x,int y){return s|(1<<(x+y<<2));}

#define pc __builtin_popcount
#define ctz __builtin_ctz
#define clz __builtin_clz
namespace Block_Checker{
	int vis[4][4];
	int dfs(int x,int y,int S){
		vis[x][y]=S;int res=1;
		for(int re i=0;i<4;++i){
			static cs int dx[]={1,0,-1,0};
			static cs int dy[]={0,1,0,-1};
			int nx=x+dx[i],ny=y+dy[i];
			if(0<=nx&&nx<4&&0<=ny&&ny<4
			&&get(S,nx,ny)&&vis[nx][ny]!=S)
			res+=dfs(nx,ny,S);
		}return res;
	}
	bool chk(int S){
		if(pc(S)!=4)return false;
		for(int re i=0;i<4;++i)if(get(S,i,3))
		return dfs(i,3,S)==4;return false;
	}
}
using Block_Checker::chk;

cs int M=300;
int dp[M+7][1<<12|7];
int blk[100],ct;
int f[1<<16|7];

void work(){
	for(int s=0;s<(1<<16);++s)
	if(chk(s))blk[++ct]=s;
	cerr<<"ct : "<<ct<<"\n";
	f[0]=1;
	for(int re s=0;s<(1<<16);++s){
		for(int re i=1;i<=ct;++i)
		if(!(s&blk[i])&&((s>>12)==0||clz(s>>12)>clz(blk[i]>>12)))
		Inc(f[s|blk[i]],f[s]);
	}
	
	L=M;
	dp[0][(1<<12)-1]=1;
	for(int re i=0;i<L;++i)
	for(int re s=0;s<(1<<12);++s)if(dp[i][s])
	for(int re t=s>>4;t<(1<<12);t=(t+1)|(s>>4)){
		int tr=t<<4|((1<<4)-1);
		Inc(dp[i+1][t],mul(f[tr^s],dp[i][s]));
	}
	
	for(int re i=0;i<=L;++i)a[i]=dp[i][(1<<12)-1];
	auto F=BM::solve();
	cerr<<F.size()<<"\n";
	cout<<F.size()<<"\n";
	std::reverse(F.begin(),F.end());
	for(int re i=0;i<F.size();++i)F[i]=dec(0,F[i]);F.back()=1;
	for(int re i=0;i<F.size();++i)cout<<F[i]<<",";cout<<"\n";
	for(int re i=0;i<F.size();++i)cout<<a[i]<<",";cout<<"\n";
}

signed main(){freopen("get_recurrence.in","w",stdout);work();return 0;}

代码(AC了的):

#include
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int po(int a,int b){
	int r=1;for(;b;b>>=1,a=mul(a,a))
	if(b&1)r=mul(r,a);return r;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return ;}ex_gcd(b,a%b,y,x);y-=a/b*x;
}
inline int inv(int a){
	int x,y;ex_gcd(mod,a,y,x);
	return x+(x>>31&mod);
}

typedef std::vector<int> Poly;

cs int bit=12,SIZE=1<<bit|7;

int r[SIZE],*w[bit+1];
void init_NTT(){
	for(int re i=1;i<=bit;++i)w[i]=new int[1<<i-1];
	int wn=po(3,mod-1>>bit);w[bit][0]=1;
	for(int re i=1;i<(1<<bit-1);++i)w[bit][i]=mul(w[bit][i-1],wn);
	for(int re i=bit-1;i;--i)
	for(int re j=0;j<(1<<i-1);++j)w[i][j]=w[i+1][j<<1];
}
void NTT(int *A,int len,int typ){
	for(int re i=1;i<len;++i)if(i<r[i])std::swap(A[i],A[r[i]]);
	for(int re i=1,d=1;i<len;i<<=1,++d)
	for(int re j=0;j<len;j+=i<<1)
	for(int re k=0;k<i;++k){
		int &t1=A[j+k],&t2=A[i+j+k],t=mul(t2,w[d][k]);
		t2=dec(t1,t),Inc(t1,t);
	}
	if(typ==-1){
		std::reverse(A+1,A+len);
		for(int re i=0,iv=inv(len);i<len;++i)Mul(A[i],iv);
	}
}
void NTT(Poly &A,int len,int typ){NTT(&A[0],len,typ);}
void init_rev(int l){for(int re i=1;i<l;++i)r[i]=r[i>>1]>>1|((i&1)?l>>1:0);} 

Poly operator+(cs Poly &a,cs Poly &b){
	Poly c=a;if(a.size()<b.size())c.resize(b.size());
	for(int re i=0,li=b.size();i<li;++i)Inc(c[i],b[i]);
	return c;
}

Poly operator-(cs Poly &a,cs Poly &b){
	Poly c=a;if(a.size()<b.size())c.resize(b.size());
	for(int re i=0,li=b.size();i<li;++i)Dec(c[i],b[i]);
	return c;
}

Poly operator*(Poly a,Poly b){
	int deg=a.size()+b.size()-1,l=1;
	while(l<deg)l<<=1;init_rev(l);
	a.resize(l),NTT(a,l,1);
	b.resize(l),NTT(b,l,1);
	for(int re i=0;i<l;++i)
	Mul(a[i],b[i]);NTT(a,l,-1);
	a.resize(deg);return a;
}

Poly Inv(cs Poly &a,int lim){
	int n=a.size();Poly c,b(1,inv(a[0]));
	for(int re l=4;(l>>2)<lim;l<<=1){
		init_rev(l);c.resize(l>>1);
		for(int re i=0;i<(l>>1);++i)c[i]=i<n?a[i]:0;
		c.resize(l),NTT(c,l,1);
		b.resize(l),NTT(b,l,1);
		for(int re i=0;i<l;++i)
		Mul(b[i],dec(2,mul(b[i],c[i])));
		NTT(b,l,-1),b.resize(l>>1);
	}b.resize(lim);return b;
}

Poly operator/(Poly a,Poly b){
	if(a.size()<b.size())return Poly(1,0);
	int deg=a.size()-b.size()+1;
	std::reverse(a.begin(),a.end());
	std::reverse(b.begin(),b.end());
	b=Inv(b,deg);a=a*b;a.resize(deg);
	std::reverse(a.begin(),a.end());
	return a;
}

Poly operator%(cs Poly &a,cs Poly &b){
	if(a.size()<b.size())return a;
	Poly c=a-(a/b)*b;c.resize(b.size()-1);
	return c;
}

cs Poly Mod={
	998244352,998244350,3,13,50,123,998244314,998244128,998243694,998242877,
	998244293,1102,2600,6047,489,998241567,998241143,998234787,998243251,3349,
	1620,6885,1053,998242383,998243939,998242095,998243884,548,76,290,
	77,998244299,998244345,998244345,998244351,1
};

cs Poly a={
	1,1,4,23,117,454,2003,9157,40899,179399,
	796558,3546996,15747348,69834517,310058192,378623792,122781000,179638924,664040578,693857604,
	150068224,487780789,81153444,201496361,206356061,443239188,923221963,678050018,834687149,938086801,
	37535712,164288625,780488339,49747373,890150065,992668567
};

Poly pw[30];
void init_pws(){
	pw[0]={0,1};for(int re i=1;i<30;++i)pw[i]=pw[i-1]*pw[i-1]%Mod;
}

void solve(){
	int n=gi();Poly g={1};int ans=0;
	for(int re i=0;i<30;++i)if(n&(1<<i))g=g*pw[i]%Mod;
	for(int re i=0;i<g.size();++i)Inc(ans,mul(g[i],a[i]));
	cout<<ans<<"\n";
}

signed main(){
#ifdef zxyoi
	freopen("fudepen.in","r",stdin);
	freopen("fudepen.out","w",stdout);
#endif
	init_NTT();init_pws();
	int T=gi();while(T--)solve();
	return 0;
}

你可能感兴趣的:(状压DP,校内模拟,多项式)