【CF704C】Black Widow(建图+多细节DP)

点此看题面

  • 给定(m)(01)变量(x_{1sim n}),并规定(x_{-i}=¬x_i)
  • (n)个表达式形如(x_i)(x_ivee x_j),保证一对(x_i,x_{-i})在所有表达式中总出现次数不超过(2)次。
  • 求有多少种给这(m)个变量定值的方案,使得所有表达式值的异或和为(1)
  • (n,mle10^5)

一言不合就建图

反正看到这种题目首先想到建图。

这道题的突破口肯定在于一对(x_i,x_{-i})出现不超过(2)次这种特殊性质,那么我们就想到在存在相关变量的两个表达式之间连边,如果相同边权为(0),相反边权为(1)

建出这张图之后,由于一个表达式最多由两个变量组成,因此每个点的度数不超过(2),所以最后的图必然由若干个环和若干个链组成。

对于不同连通块来说情况肯定是独立的,因此只要单独考虑某一条链或是某一个环即可。

但要先注意特判(x_ivee x_{i},x_ivee x_{-i})的特殊表达式,前者相当于可以任选(01)填写,后者相当于有两种给表达式异或和贡献(1)的方案(也可以看作给表达式异或和贡献(1),然后假装这个变量没出现过)。

还要特判建出的图中的单点,如果它是一个单独变量(x_i),和前面(x_ivee x_i)一样相当于可以任选(01)填写,否则它是(x_ivee x_j),就有三种情况贡献(1),一种情况贡献(0)

其实真要写都是很好处理的,但这种细节一旦忽略就比较致命了。

对于链的动态规划

直接设(f_{i,p,x})表示(DP)到第(i)位,之前的异或和为(p),当前式子中已经由上一个式子确定的变量是(x)的方案数,则直接枚举当前式子的未知变量(y)转移。

(y)也就是下个式子中由当前式子确定的变量,异或上边权即可得到(x'),而(p')直接由(p)异或上(xvee y)就结束了。

应该还是非常简单的。

但要注意环两端可能存在一个单独的变量表达式,如果在开头就不能初始化(f_{1,0,1}=1),如果在结尾就不能把最后的不确定变量当作(1)

对于环的暴拆成链

我们随便找到环上的一条边把它断开,然后枚举第一个表达式和最后一个表达式共有元素的值。

只要分两种情况,按照链的(DP)转移即可。

刚好前面链的动态规划因为单独变量表达式对于开头和结尾也存在一些特殊处理,直接利用一下,也不会增大多少码量。

代码:(O(n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
#define adde(x,y,z) (add(x,y,z),add(y,x,z),++deg[x],++deg[y])
using namespace std;
int n,m,ans[2],ty[N+5],lst[N+5],op[N+5],deg[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}e[2*N+5];
int nc[N+5];I bool Cir(CI x,CI fi,CI pre=0)//判断是否在环中,对于链需要记忆化
{
	#define E(x) ((((x)-1)^1)+1)//反向边
	if(nc[x]) return 0;for(RI i=lnk[x];i;i=e[i].nxt)
		if(i^E(pre)&&(e[i].to==fi||Cir(e[i].to,fi,i))) return 1;return nc[x]=1,0;
}
int cnt,ed,g[N+5],vis[N+5];I void Get(CI x)//抠出链或环
{
	++cnt,vis[ed=x]=1;for(RI i=lnk[x];i;i=e[i].nxt) !vis[e[i].to]&&(g[cnt]=e[i].v,Get(e[i].to),0);
}
int s[2],f[N+5][2][2];I void DP(CI op)//对于链的DP,最终的不确定变量必须选0/1(op=0/1)或任选(op=2)
{
	RI i,p,x,y;for(i=1;i^cnt;++i) for(p=0;p<=1;++p)//枚举之前的异或和
		for(x=0;x<=1;++x) for(y=0;y<=1;++y) Inc(f[i+1][p^(x|y)][y^g[i]],f[i][p][x]);//枚举式子中的两个变量
	for(y=0;y<=1;++y) if(op^!y) for(p=0;p<=1;++p) for(x=0;x<=1;++x) Inc(s[p^(x|y)],f[cnt][p][x]);//枚举最终的不确定变量
	for(i=1;i<=cnt;++i) f[i][0][0]=f[i][0][1]=f[i][1][0]=f[i][1][1]=0;//清空
}
I void Calc()//把贡献更新到答案中
{
	int res[2]={0,0};for(RI x=0;x<=1;++x) for(RI y=0;y<=1;++y) res[x^y]=(1LL*s[x]*ans[y]+res[x^y])%X;//枚举当前连通块异或和与其他连通块总异或和
	ans[0]=res[0],ans[1]=res[1],s[0]=s[1]=0;//更新答案,然后清空贡献
}
int main()
{
	RI i,x,y,o;for(scanf("%d%d",&n,&m),ans[0]=i=1;i<=n;++i)
	{
		scanf("%d%d",ty+i,&x),o=abs(x)!=x,lst[x=abs(x)]?(adde(lst[x],i,o^op[x]),lst[x]=-1):(lst[x]=i,op[x]=o);//相关变量连边
		if(ty[i]==1) continue;if(scanf("%d",&y),abs(x)==abs(y))//如果表达式中两个变量相关
			{vis[i]=1,o^(abs(y)!=y)?(swap(ans[0],ans[1]),lst[x]=0):(Inc(ans[0],ans[1]),ans[1]=ans[0]);continue;}//根据是否相同分类
		o=abs(y)!=y,lst[y=abs(y)]?(adde(lst[y],i,o^op[y]),lst[y]=-1):(lst[y]=i,op[y]=o);//相关变量连边
	}
	for(i=1;i<=m;++i) !lst[i]&&(ans[0]=2LL*ans[0]%X,ans[1]=2LL*ans[1]%X);for(i=1;i<=n;++i) if(!vis[i])//无用变量直接给答案乘2
	{
		if(!deg[i]) {if(ty[i]==1) {Inc(ans[0],ans[1]);ans[1]=ans[0];continue;}//单点,如果是单独变量
			int res[2]={(3LL*ans[1]+ans[0])%X,(3LL*ans[0]+ans[1])%X};ans[0]=res[0],ans[1]=res[1];continue;}//如果是两个变量
		if(!Cir(i,i)) {deg[i]==1&&(cnt=0,Get(i),f[1][0][0]=1,f[1][0][1]=ty[i]!=1,DP(ty[ed]^1?2:0),Calc(),0);continue;}//不在环中,只从链首DP
		for(cnt=0,Get(i),x=lnk[i];e[x].to^ed;x=e[x].nxt);//断环,找出断开的边
		e[x].v==0?(f[1][0][0]=1,DP(0),f[1][0][1]=1,DP(1)):(f[1][0][0]=1,DP(1),f[1][0][1]=1,DP(0)),Calc();//枚举这个变量取值
	}return printf("%d
",ans[1]),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF704C.html