状压 DP 做题笔记

F P4363 [九省联考 2018] 一双木棋 chess

轮廓线 DP +记忆化

我们可以发现一个位置能落子,当且仅当左上角的矩形内部 只有 自己一个空位

由此可得出状态是类似 阶梯形

那么我们考虑轮廓线 DP:

不妨用 \(1\) 表示竖边,\(0\) 表示横边

就可以用二进制表示出当前状态

\(f[msk]\) 表示这个轮廓线状态 距游戏结束 还能得多少分

状态的转移就是把第 \(i\) 位上的 \(1\) 左移一位即可

这个操作在位运算中可以写为 msk^(3<<i)

注意:边界条件是 f[((1<<n)-1)<<m]=0

Code
#include<bits/stdc++.h>
#define int long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define endl '\n'
using namespace std;

inline int read()
{
	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*10+(c-48);c=getchar();}
	return x*f;
}

const int N=5+9,M=2e6+509,mod=998244353;

int n,m;
int a[N][N],b[N][N];
unordered_map<int,int> f;
/*
轮廓线DP
一个位置能落子,当且仅当左上角的矩形内部只有自己一个空位
用 1 表示竖边,0 表示横边
状态的转移就是把其中一个 1 左移一位即可
本质就是 01−>10
令 f[msk] 表示这个轮廓线状态距游戏结束还能得多少分
可以得到边界条件 f[((1<<n)-1)<<m]=0
*/

int F(int msk,bool id)
{
	if(f.count(msk)) return f[msk];
	f[msk]=id?-1e18:1e18;
	int x=n,y=0;
	fd(i,0,n+m-2)
	{
		if(msk>>i&1) x--;else y++;//延轮廓线递推
		if((msk>>i&3)!=1) continue;//没空位
		int nxt=msk^(3<<i);//把 1 左移
		if(id) f[msk]=max(f[msk],F(nxt,id^1)+a[x][y]);
		else f[msk]=min(f[msk],F(nxt,id^1)-b[x][y]);
	}
	return f[msk];
}

signed main()
{
//#define FJ
#ifdef FJ
	freopen("color2.in","r",stdin);
	freopen("color.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif
	
	n=read(),m=read();
	fd(i,0,n-1) fd(j,0,m-1)
		a[i][j]=read();

	fd(i,0,n-1) fd(j,0,m-1)
		b[i][j]=read();
	
	f[((1<<n)-1)<<m]=0;
	printf("%lld",F((1<<n)-1,1));
	
    return 0;
}

H P5616 [MtOI2019] 恶魔之树

Hash+DP

显然题意可以转化为求 所有子序列的 \(\operatorname{lcm}\) 之和

注意到 \(1\le s_i\le 300\)去重,设值的个数为 \(m\) 个,并记录每种值的出现次数 \(cnt_i\)

然后可以把质数分为 \(\le \sqrt{300}\)\(> \sqrt{300}\) 两部分

发现在所有 \(s_i\) 中只会出现 至多一个 大质数 \(P_i(P_I>\sqrt{300})\),以每个 \(s_i\)\(P_i\) 为关键字排序(没有就是 \(1\)),令新数组为 \(New\)

然后 \(New\) 就被分成了若干段,每段都有同一个 \(P_i\)

\(f_{i,j,0/1}\) 为遍历到 \(New_{i}\),小质数的 \(\operatorname{lcm}\)\(j\)\(P_{i}\) 没选/选时,所有大质数对答案的贡献

下令 \(j'=\operatorname{lcm}(j,\frac{New_{i}}{P_{i}})\)

初始\(f_{0,1,0}=1\)

\(P_{i}=P_{i-1}\),则

\[\begin{cases} f_{i,j,0}\gets f_{i,j,0}+f_{i-1,j,0}\\ f_{i,j,1}\gets f_{i,j,1}+f_{i-1,j,1}\\ f_{i,j',1}\gets f_{i,j',1}+(f_{i-1,j,0}\times P_{i} +f_{i-1,j,1})(2^{cnt_{New_{i}}}-1) \end{cases} \]

否则

\[\begin{cases} f_{i,j,0}\gets f_{i,j,0}+f_{i-1,j,0}+f_{i-1,j,1}\\ f_{i,j',1}\gets f_{i,j',1}+P_{i}(f_{i-1,j,0}+f_{i-1,j,1})(2^{cnt_{New_{i}}}-1) \end{cases} \]

最后答案就是 \(\sum j(f_{m,j,0}+f_{m,j,1})\)

式中的 \(j\) 直接作下标会 RE,所以考虑 Hash(预处理所有 \(\operatorname{lcm}\)

\(\sqrt{300}\) 小的质数集合 \(P\)\(\{2,3,5,7,11,13,17\}\),所以状态总数为 \(\prod_{p \in P}(k_{p} +1)=17496\)

注意:最好不要用 %mod,建议用手写模

Code
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define endl '\n'
using namespace std;

inline int read()
{
	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*10+(c-48);c=getchar();}
	return x*f;
}

const int N=3e5+5,M=305,K=2e4+5;

int n,mod,a[N];
int Lcm[K],p[8]={0,2,3,5,7,11,13,17},k=7,pw[N];//lcm prime pow2
int tot,cnt[M],m,f[M][K][2],ans;
unordered_map<int,int> h;//hash
pair<int,int> b[N],t[M];

inline int add(int x,int y)
{
	return x+y>=mod?x+y-mod:x+y;
}

inline int mul(int x, int y)
{
	return (int)(1ll*x*y%mod);
}

int gcd(int x,int y)
{
	return y==0?x:gcd(y,x%y);
}

inline int lcm(int x,int y)
{
	return x/gcd(x,y)*y;
}

void init()
{
	fd(i,1,n) a[i]=read();
	
	fd(i,1,n)
	{
		b[i]={1,1};
		fd(j,1,k)
		{
			while(a[i]%p[j]==0)
			{
				a[i]/=p[j];
				b[i].second*=p[j];
			}
		}
		b[i].first=a[i];
	}
	
	sort(b+1,b+n+1);
	
	fd(i,1,n)//去重
	{
		if(b[i]!=b[i-1])
			t[++m]=b[i];
		cnt[m]++;
	}
}

void pre()
{
	for(int i2=1;i2<=300;i2*=2)
		for(int i3=1;i3<=300;i3*=3)
			for(int i5=1;i5<=300;i5*=5)
				for(int i7=1;i7<=300;i7*=7)
					for(int i11=1;i11<=300;i11*=11)
						for(int i13=1;i13<=300;i13*=13)
							for(int i17=1;i17<=300;i17*=17)
							{
								int x=1ll*i2*i3*i5*i7*i11*i13*i17;
								Lcm[++tot]=x,h[x]=tot;
							}
	
	pw[0]=1;
	fd(i,1,n) pw[i]=add(pw[i-1],pw[i-1]);
}

void DP()
{
	f[0][h[1]][0]=1;
	fd(i,1,m)
	{
		if(t[i].first==t[i-1].first)
		{
			fd(j,1,tot)
			{
				int x=lcm(Lcm[j],t[i].second);
				f[i][j][0]=add(f[i][j][0],f[i-1][j][0]);
				f[i][j][1]=add(f[i][j][1],f[i-1][j][1]);
				f[i][h[x]][1]=add(f[i][h[x]][1],mul(add(mul(f[i-1][j][0],t[i].first),f[i-1][j][1]),pw[cnt[i]]-1));
			}
		}
		else
		{
			fd(j,1,tot)
			{
				int x=lcm(Lcm[j],t[i].second);
				f[i][j][0]=add(f[i][j][0],add(f[i-1][j][0],f[i-1][j][1]));
				f[i][h[x]][1]=add(f[i][h[x]][1],mul(mul(add(f[i-1][j][0],f[i-1][j][1]),t[i].first),pw[cnt[i]]-1));
			}
		}
	}
	fd(i,1,tot) ans=add(ans,mul(add(f[m][i][0],f[m][i][1]),Lcm[i]%mod));
}

signed main()
{
// #define FJ
#ifdef FJ
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif
	
	n=read(),mod=read();
	pre();
	init();
	DP();
	printf("%lld",ans);
	
	return 0;
}

J P4484 [BJWC2018] 最长上升子序列

状压+打表

发现 \(n \le 28\),很 打表 的范围,但是如何 快速地暴力 是一个问题

事实上,从左向右推好像不是很可行,考虑对于一个排列,我们把数从小到大插入

那么我们首先令一个\(f_i\)(跟程序没关系)表示,在当前已经确定的一个序列里面,以第 \(i\) 个数结尾的最长上升子序列长度

基于这个数组,我们再令 \(maxL_i\) 表示 前缀最大值,即:

\[maxL_i = max\{f_1,f_2...,f_i\} \]

那么对于 \(maxL\) 数组,显然有:

\[maxL_i \leq maxL_{i+1} \leq maxL_{i} +1 \]

我们可以 差分 \(maxL\),不妨设对其进行差分的数组为 \(c\)

我们把数从小到大插入的时候,对于 \(c\) 数组:

考虑在第 \(i\) 位和第 \(i+1\) 位之间插入了一个新的数,因为是单调地插入的,所以新插入的这个数一定是当前序列的最大数

显然,这个数的 \(maxL\) 一定是 \(maxL_i+1\),因此把 \(c_{i+1}\) 改成 \(1\),而在 \(i\) 之后第一个比 \(a_i\) 大的数,记其位置为 \(pos\),则 \(c_{pos}\) 值肯定也为 \(1\)

但是当我们插入了这个新的数之后,由于在它刚刚插入的不计入 \(f_{pos}\),所以我们应当把 \(c_{pos}\) 置成 \(0\)

那么接下来要做的就是对 \(c\) 数组进行状压 DP

那我们不妨令 \(f_{i,j}\) 表示在一个 \(1\)~\(i\) 的排列里,差分数组 \(c\) 状态为 \(j\) 的方案数,那么答案就是:

\[ans=\frac{1}{n!}\sum_{i=0}^{2^{n-1}-1}{f_{n,i} \times len(i)} \]

也就是 \(\sum\) \((\)\(n\) 个数、状态为\(i\)的方案\(\times\)方案中的 LIS 的长度 \()\)

由于我们状压了 \(c\) 数组,所以每个方案中 LIS 的长度,就是该状态里 \(1\) 的个数

注意:

  • 序列 \(f\)\(c\) 仅用来推导

  • 记得开滚动数组

  • 由于一定存在 \(c_1=1\),实际有 \(n\) 个数的状态只需要记录最后 \(n-1\)

Code
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define endl '\n'
using namespace std;

inline int read()
{
	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*10+(c-48);c=getchar();}
	return x*f;
}

const int N=3e5+5,M=1<<27|1,K=2e4+5,mod=998244353;
int n,m;
int *f[2]={new int[M],new int[M]},len[M],ans,fac;
int res[30]={0,1,499122178,2,915057326,540715694,946945688,422867403,451091574,317868537,200489273,976705134,705376344,662845575,331522185,228644314,262819964,686801362,495111839,947040129,414835038,696340671,749077581,301075008,314644758,102117126,819818153,273498600,267588741};

inline int qpow(int x,int y)
{
	int re=1;x%=mod;
	while(y)
	{
		if(y&1) (re*=x)%=mod;
		(x*=x)%=mod,y>>=1;
	}
	return re;
}
inline int inv(int x)
{
	return qpow(x,mod-2);
}

signed main()
{
// #define FJ
#ifdef FJ
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif
#define Res
#ifdef Res
	//打表直接输出
	printf("%lld",res[read()]);
	return 0;
#endif
//#define Pre
#ifdef Pre
	//打表
	freopen("Pre.txt","w",stdout);
	int lim=28,T=0;
	DO:T=-~T;cerr<<"Now:"<<T<<endl;
	n=T-1,m=1<<n;
	fd(i,0,m) f[1][i]=f[0][i]=len[i]=0;
#else
	n=read()-1,m=1<<n;
#endif
	
	f[0][0]=1;
	fd(i,1,n)
	{
		int cur=i&1;//滚动数组
		fd(j,0,1<<i) f[cur][j]=0;//memset
		fd(j,0,(1<<(i-1))-1)
		{
			(f[cur][j<<1]+=f[cur^1][j])%=mod;
			int pos=-1;
			bd(k,i-1,0)//枚举插入到哪一位
			{
				int t=((j>>k)<<(k+1))|(1<<k)|(j&((1<<k)-1));
				//(j>>k)<<(k+1)是为了先清掉后面几位,不能简写成j<<1
				if(j&(1<<k)) pos=k;
				if(~pos) t^=(1<<(pos+1));
				(f[cur][t]+=f[cur^1][j])%=mod;
			}
		}
	}
	
	fd(i,1,m-1) len[i]=len[i-(i&-i)]+1;
	ans=0;fd(i,0,m-1) (ans+=f[n&1][i]*(len[i]+1))%=mod;
	fac=1;fd(i,1,n+1) (fac*=i)%=mod;
	
	ans=ans*inv(fac)%mod;
	printf("%lld,",ans);
	
#ifdef Pre
	if(T<lim) goto DO;
#endif
	
	return 0;
}

K UOJ #37. 【清华集训2014】主旋律

容斥+状压


简单一点的思路

题目等价于求有多少个边集可以使这张图强连通

即求这张图不强连通的方案数

\(Dag[s]\) 表示集合 \(s\) 是一个 \(DAG\),那么我们用全集减去它就是答案

我们再设 \(G[s]\) 表示集合 \(s\) 被划分为奇数个强连通分量的方案数,

\(H[s]\) 表示划分为偶数个强连通分量的方案数

\(E(S,T)\) 表示集合 \(S\) 向集合 \(T\) 连的边数

\[Dag[S]=\sum_{s \subset S}(D[S]-S[S])\times 2^{E(s,S-s)+E(S-s,S-s)} \]

最后加上自己连自己的方案数是因为我们的容斥系数已经弄好了,只需要让 \(S-s\) 缩完点之后成为一个 \(DAG\) 就行了,所以合法的边集是全集

我们最后的答案 \(f[s]\) 表示集合 \(s\) 强连通的方案数,\(G\)\(H\) 的转移有:

\[G[S]=\sum_{s \subset S}f[s]\times H[S-s]\\ H[S]=\sum_{s \subset S}f[s]\times G[S-s] \]

Code
#include "bits/stdc++.h"
#include "whrwlx/modint.h"
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define endl '\n'
using namespace std;

inline int read()
{
	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*10+(c-48);c=getchar();}
	return x*f;
}

const int N=5+9+2,M=225,K=2e4+5,mod=1e9+7;

int n,m;
modint G[1<<N],H[1<<N],f[1<<N],pw[N*N];
bitset<M> in[1<<N],out[1<<N];

inline int calc(int x,int y)
{
	return (out[x]&in[y]).count();
}

signed main()
{
// #define FJ
#ifdef FJ
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif

	n=read(),m=read();
	int MS=(1<<n)-1;
	
	pw[0]=1;
	fd(i,1,n*n) pw[i]=pw[i-1]*2;
	
	fd(i,1,m)
	{
		int x=read(),y=read();
		fd(j,1,MS)
		{
			if(j&(1<<(x-1))) out[j][i]=1;
			if(j&(1<<(y-1))) in[j][i]=1;
		}
	}
	
	H[0]=1;
	fd(S,1,MS)
	{
		f[S]=pw[calc(S,S)];
		for(int s=(S-1)&S;s;s=(s-1)&S)
		{
			f[S]=f[S]-(G[s]-H[s])*pw[calc(s,S-s)+calc(S-s,S-s)];
			if((s&(S&-S))==0) continue;
			G[S]+=f[s]*H[S-s];
			H[S]+=f[s]*G[S-s];
		}
		f[S]=f[S]-(G[S]-H[S]);
		G[S]+=f[S];
	}
	
	cout<<f[MS].num;
	
	return 0;
}

首先,根据正难则反的思路,合法方案数等于,总方案数 \(2^m\) 减去不合法方案数

不合法:即将图进行缩点之后并不是一个点而是一个 \(DAG\)

那么分为两部分:\(DAG\),以及其中的 \(SCC\)

首先定义 \(D(S)\) 为点集 \(S\) 构成的子图中,为 \(DAG\) 的子图个数

那么一个容斥是:

\[D(S) = \sum_{T \subseteq S} (-1)^{|T|+1}D(S\ \^{\ }\ T) \times 2^{E(T,S\ \^{\ }\ T)} \]

其中 \(E(S,T)\) 表示出点在点集 \(S\) 入点在点集 \(T\) 的边的数量

然后思考怎么算 SCC。首先定义一个 \(F(S)\) 表示点集 \(S\) 构成的子图中,为强连通图的子图个数(即答案)

接下来引入两个记号:

  • \(T \in \text{P}(S,k)\) 表示枚举将 \(S\) 拆分为 \(T_1,T_2,...,T_k\) 的所有方案,保证 $\forall i,j\in [1,k],T_i \cap T_j = \varnothing $
  • \(T \in \text{SP}(S,k)\) 表示枚举将 \(S\) 的严格拆分,即保证 \(k>1\)

那么可以列出 \(F\) 的公式:

\[F(S) = 2^{E(S,S)}-\sum_{T\in\text{SP}(S,k)} D(T)\times \prod_{i=1}^k F(T_i) \]

\(D(T)\) 展开,有:

\[F(S) = 2^{E(S,S)} - \sum_{T\in\text{SP}(S,k)} \left( \sum_{T'\subseteq T} (-1)^{|T'|+1} D(T\ \^{}\ T') \times 2^{E(T',T\ \^{}\ T')} \right) \times \prod_{i=1}^k F(T_i) \]

这个式子丝毫的不可做,但我们仔细观察这两个 \(\sum\) ,是枚举划分再枚举子集

我们反过来,先枚举子集,在枚举子集的划分,是完全等价的,那么有:

\[F(S)=2^{E(S,S)} - \sum_{T \subset S}\sum_{U\in\text{P}(T,k)}\sum_{V\in\text{P}((S\ \^{}\ T),l)} \left( (-1)^{k+1} D(V)\times 2^{E(T, S\ \^{}\ T)}\left(\prod _{i=1}^k F(U_i)\right)\times \left(\prod _{i=1}^l F(V_i)\right) \right) \]

考虑把 \((-1)^{k+1}\prod_{i=1}^k F(U_i)\) 提到第二个 \(\sum\) 后,发现这样其实可以使 \(U,V\) 两部分分开

设:

\[G(S)=\sum_{T\in\text{P}(S,k)} (-1)^{k+1} \prod_{i=1}^k F(T_i) \]

代入,得:

\[F(S)=2^{E(S,S)}-\sum_{T\ \^{}\ S}G(T)\times 2^{E(T,S\ \^{}\ T)} \left(\sum_{V\in\text{P}(S\ \^{}\ T,l)} D(V)\prod _{i=1}^l F(V_i)\right) \]

注意到 \(\sum_{V\in\text{P}(S\ \^{}\ T,l)} D(V)\prod _{i=1}^l F(V_i)\) 是枚举了 DAG 再将缩掉的点拆开,相当于任意图

所以原式等价于:

\[F(S)=2^{E(S,S)} - \sum_{T\ \^{}\ S}G(T)\times 2^{E(T,S\ \^{}\ T)+E(S\ \^{}\ T,S\ \^{}\ T)} \]

得到 \(G\) 的柿子:

\[G(S)=F(S)-\sum_{T \subset S,x\in T} F(T)G(S\ \^{}\ T) \]

其中 \(x\) 任取,因为如果没有 \(x\in T\) 的限制时,会算重

具体的,对于一张图,有一个 \(SCC\) 点集为 \(CCF\),在 \(T=CCF\)\(F(T)\) 被算了一次,在 \(CCF \subseteq S\ \^{}\ T\) 时又可能被算一遍

为什么 \(x \in T\) 可以让我们不重不漏?

  • 不重:
    考虑两张图相同,必要条件是 \(x\) 所在 \(SCC\) 的编号相同
    保证了编号不同,图自然就不重了

  • 不漏:
    首先 \(x\) 所在 \(SCC\) 会被枚举到,
    而其他部分在 \(G(S\ \^{}\ T)\) 已经确定是可以的了

最后 \(F,G\) 一起 DP 就行了

复杂度最优秀可以达到 \(O(3^n)\)

但是 \(n \le 15\),暴力求 \(E\) 也是可以的

复杂度 \(O(n3^n)\)

Code
#include<bits/stdc++.h>
#include "whrwlx/modint.h"
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define endl '\n'
using namespace std;

inline int read()
{
	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*10+(c-48);c=getchar();}
	return x*f;
}

const int N=5+9+2,M=225,K=2e4+5,mod=1e9+7;

int n,m;
modint<int,mod> f[1<<N],g[1<<N],pw[N*N];
//SCC DAG pow_of_2
int e[N],rnk[1<<N];

#define count __builtin_popcount
inline int E(int x,int y)
{
	int re=0;
	for(;x;x-=(x&-x))
		re+=count(e[rnk[x&-x]]&y);
	return re;
}

signed main()
{
// #define FJ
#ifdef FJ
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
#endif
//#define io
#ifdef io
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
#endif
	
	n=read(),m=read();
	int MS=(1<<n)-1;
	
	pw[0]=1;
	fd(i,1,n*n) pw[i]=pw[i-1]*2;
	
	fd(i,1,m)
	{
		int x=read(),y=read();
		e[x-1]|=(1<<(y-1));
	}
	fd(i,0,n) rnk[1<<i]=i;
	
//	#define DB
	
	fd(S,1,MS)
	{
		if(count(S)==1)//只有一个点,没有子集
		{
			f[S]=1,g[S]=1;
			continue;
		}
		#ifdef DB
			bitset<20> D;
			D.set(S);
			cout<<S<<endl<<":["<<D<<"]"<<endl;
		#endif
		int x=(S&-S),s=S-x;//见上文 x 的含义
		for(int T=s;;T=(T-1)&s)
		{
			#ifdef DB
				bitset<20> D;
				D.set(T|x);
				cout<<"g["<<D<<"]"<<endl;
			#endif
			g[S]-=f[(T|x)]*g[S^(T|x)];
			//这里 f[S] 还没求出,得先减,之后再把剩的 f[S] 加回来
			if(!T) break;
		}
		for(int T=S;T;T=(T-1)&S)
		{
			#ifdef DB
				bitset<20> D;
				D.set(T);
				cout<<"f["<<D<<"]"<<endl;
			#endif
			f[S]+=g[T]*pw[E(S^T,S^T)]*pw[E(S^T,T)];
		}
		f[S]=pw[E(S,S)]-f[S];//减去 DAG 得到 SCC
		g[S]+=f[S];//加回来
		#ifdef DB
			cout<<endl;
		#endif
	}
	
	cout<<f[MS].num;
	
	return 0;
}
手搓的 modint.h
template<typename _T,_T _MOD>
struct modint
{
	_T Mod=_MOD,num;
	modint(_T _Mod=_MOD,_T _num=0)
	{
		Mod=_Mod,num=_num;
	}
	inline _T qpow(_T x,_T y)
	{
		_T re=1;x%=Mod;
		while(y)
		{
			if(y&1) (re*=x)%=Mod;
			(x*=x)%=Mod,y>>=1;
		}
		return re;
	}
	inline _T inv(_T x)
	{
		return qpow(x,Mod-2);
	}
	inline modint operator+(const modint& y)
	{
		return modint(Mod,num+y.num<Mod?num+y.num:num+y.num-Mod);
	}
	inline modint operator+(const _T& y)
	{
		return modint(Mod,num+y<Mod?num+y:num+y-Mod);
	}
	inline modint operator-(const modint& y)
	{
		return modint(Mod,num-y.num<0?num-y.num+Mod:num-y.num);
	}
	inline modint operator-(const _T& y)
	{
		return modint(Mod,num-y<0?num-y+Mod:num-y);
	}
	inline modint operator*(const modint& y)
	{
		return modint(Mod,1ll*num*y.num%Mod);
	}
	inline modint operator*(const _T& y)
	{
		return modint(Mod,1ll*num*y%Mod);
	}
	inline modint operator/(const modint& y)
	{
		return modint(Mod,1ll*num*inv(y.num)%Mod);
	}
	inline modint operator/(const _T& y)
	{
		return modint(Mod,1ll*num*inv(y)%Mod);
	}
	inline modint operator^(const _T& y)
	{
		return modint(Mod,qpow(num,y));
	}
	inline modint operator+=(const modint& y)
	{
		return (*this)=(*this)+y;
	}
	inline modint operator+=(const _T& y)
	{
		return (*this)=(*this)+y;
	}
	inline modint operator-=(const modint& y)
	{
		return (*this)=(*this)-y;
	}
	inline modint operator-=(const _T& y)
	{
		return (*this)=(*this)-y;
	}
	inline modint operator*=(const modint& y)
	{
		return (*this)=(*this)*y;
	}
	inline modint operator*=(const _T& y)
	{
		return (*this)=(*this)*y;
	}
	inline modint operator/=(const modint& y)
	{
		return (*this)=(*this)/y;
	}
	inline modint operator/=(const _T& y)
	{
		return (*this)=(*this)/y;
	}
	inline void operator=(const _T& y)
	{
		num=y;
	}
};
posted @ 2024-10-28 09:18  whrwlx  阅读(12)  评论(1编辑  收藏  举报