[FJWC2020 Day1T1] 人生

题目描述
现在你有 n n n个点,每个点有黑色( 0 0 0)或者白色( 1 1 1)或者没有颜色( − 1 -1 1),现在你需要把所有没有颜色的点染成黑色或者白色
你还需要添加一些不重复的有向边 ( i , j ) (i,j) (i,j),要求 1 ≤ i < j ≤ n 1\leq i<j\leq n 1i<jn,也就是说这些边需要从编号小的走到编号大的
一般的,一条合法的路径是指他经过的任意两个点的颜色不同。特别的,一个点也算作一条路径
问最后有多少种符合条件的图,使得这个图上有奇数条合法路径,答案对 998244353 998244353 998244353取模

对于 10 % 10\% 10%的数据, n ≤ 5 n\leq 5 n5
对于 40 % 40\% 40%的数据, n ≤ 50 n\leq 50 n50
对于 50 % 50\% 50%的数据, n ≤ 150 n\leq 150 n150
对于 65 % 65\% 65%的数据, n ≤ 500 n\leq 500 n500
对于 80 % 80\% 80%的数据, n ≤ 5000 n\leq 5000 n5000
对于全部的数据 , n ≤ 2 × 1 0 5 n\leq2\times 10^5 n2×105


solution
首先考虑对于确定的图,我们怎么计算可不可行
我们用 c n t i cnt_i cnti表示以第 i i i个点为结尾的路径个数,那么我们应该有这样的转移
c n t i = ∑ j = 1 i − 1   c n t j ( c o l o r i ≠ c o l o r j , ∃ ( j , i ) ) + 1 cnt_i=\begin{matrix}\sum_{j=1}^{i-1}\end{matrix}\ cnt_j(color_i\neq color_j,\exist(j,i))+1 cnti=j=1i1 cntj(colori=colorj,(j,i))+1
然后我们只需要判断 ∑ c n t i \sum cnt_i cnti的奇偶性即可
那么我们发现我们记录出这个东西有点冗余,我们让 c n t i cnt_i cnti表示以 i i i点为结尾的路径个数的奇偶性,那么我们之前的转移方程可以写成
c n t i = ( xorsum ⁡ j = 1 i − 1   c n t j ) xor ⁡ 1 ( c o l o r i ≠ c o l o r j , ∃ ( j , i ) ) cnt_i=\begin{matrix}(\operatorname{xorsum}_{j=1}^{i-1}\end{matrix}\ cnt_j) \operatorname{xor} 1(color_i\neq color_j,\exist(j,i)) cnti=(xorsumj=1i1 cntj)xor1(colori=colorj,(j,i))
其中 xorsum ⁡ \operatorname{xorsum} xorsum表示异或和

然后我们考虑进行 d p dp dp,因为题目中说所有边都是从编号小的连向大的,所以我们用 f i , j , x , y f_{i,j,x,y} fi,j,x,y表示前 i i i个点, c n t cnt cnt xorsum ⁡ \operatorname{xorsum} xorsum j j j,有 x x x c n t = 1 cnt=1 cnt=1的白色点,有 y y y c n t = 1 cnt=1 cnt=1的黑色点,的方案数,转移以将这个点染成白色为例,分两类进行讨论,一个是这个点的 c n t = 1 cnt=1 cnt=1,转移为

f [ i ] [ j ] [ x + 1 ] [ y ] + = f [ i − 1 ] [ j xor ⁡ 1 ] [ x ] [ y ] × calc ⁡ ( y , 0 ) × 2 x f[i][j][x+1][y]+=f[i-1][j\operatorname{xor} 1][x][y]\times \operatorname{calc}(y,0)\times 2^x f[i][j][x+1][y]+=f[i1][jxor1][x][y]×calc(y,0)×2x

c n t i = 0 cnt_i=0 cnti=0时,

f [ i ] [ j ] [ x ] [ y ] + = f [ i − 1 ] [ j ] [ x ] [ y ] × calc ⁡ ( y , 1 ) × 2 x f[i][j][x][y]+=f[i-1][j][x][y]\times\operatorname{calc}(y,1)\times 2^x f[i][j][x][y]+=f[i1][j][x][y]×calc(y,1)×2x

其中 calc ⁡ ( y , o p t ) \operatorname{calc}(y,opt) calc(y,opt)表示在一个大小为 y y y的集合中,奇偶性为 o p t opt opt的子集的方案数(包括∅)

暴力枚举选了几个,这个转移就是 O ( n 4 ) O(n^4) O(n4)的,可以拿到 40 40 40

我们考虑 calc ⁡ ( y , o p t ) \operatorname{calc}(y,opt) calc(y,opt)等于多少,我们发现在 y ≥ 1 y\geq1 y1他一定是 2 y − 1 2^{y-1} 2y1,为什么呢?
y y y为奇数的时候,我们把它看成二进制,那么任意一个奇数个数的集合一定对应一个大小为偶数的集合即他的补集,所以奇数偶数各占一半
y y y为偶数的时候,我们考虑分成 1 , n − 1 1,n-1 1,n1两部分,已知 n − 1 n-1 n1中选出奇数和偶数的方案数是相同的,那么如果选剩下的一个那么就是 n − 1 n-1 n1中选奇数的方案,不选就是 n − 1 n-1 n1中选偶数的方案,所以奇数偶数数量还是相同的

考虑边界条件,当 y = 0 y=0 y=0时, 0 0 0个数中选偶数的方案数为 1 1 1 0 0 0个数中选奇数的方案为 0 0 0,注意特判就可以

那么现在转移就变成了 O ( n 3 ) O(n^3) O(n3)的,可以拿到 50 50 50

我们发现 i i i这一维可以滚动数组滚掉,空间变成平方,在开O2的情况下应该可以拿到 65 65 65

考虑进一步优化,我们把 calc ⁡ \operatorname{calc} calc的值带进之前的转移方程,变成

f [ i ] [ j ] [ x + 1 ] [ y ] + = f [ i − 1 ] [ j xor ⁡ 1 ] [ x ] [ y ] × 2 i − 1 ( 2 x = 2 i − y ) f[i][j][x+1][y]+=f[i-1][j\operatorname{xor} 1][x][y]\times 2^{i-1}(2^x=2^{i-y}) f[i][j][x+1][y]+=f[i1][jxor1][x][y]×2i1(2x=2iy)
f [ i ] [ j ] [ x ] [ y ] + = f [ i − 1 ] [ j ] [ x ] [ y ] × 2 i − 1 f[i][j][x][y]+=f[i-1][j][x][y]\times 2^{i-1} f[i][j][x][y]+=f[i1][j][x][y]×2i1

当然 y = 0 y=0 y=0的时候需要特判

观察这个转移,我们发现 x , y x,y x,y已经基本无关了,唯一有影响的就是是否是 0 0 0,所以我们可以变成 f [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] f[i][j][0/1][0/1] f[i][j][0/1][0/1],分别表示目前是否有白点和黑点,那么这样复杂度就变成了 O ( n ) O(n) O(n),可以通过

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=2e5+5;
const int mod=998244353;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int a[N];
int f[N][2][2][2];
int fac[N];
int ans;

int main()
{
	freopen("life.in","r",stdin);
	freopen("life.out","w",stdout);
	read(n);
	Rep(i,1,n)read(a[i]);
	fac[0]=1;
	Rep(i,1,n)fac[i]=fac[i-1]*2%mod;
	f[0][0][0][0]=1;
	Rep(i,1,n)
		Rep(j,0,1)
			Rep(x,0,1)
				Rep(y,0,1){
					if(a[i]!=1){
						f[i][j^1][x|1][y]+=1ll*f[i-1][j][x][y]*(y?fac[i-2]:fac[i-1])%mod;
						f[i][j^1][x|1][y]%=mod;
						f[i][j][x][y]+=1ll*f[i-1][j][x][y]*(y?fac[i-2]:0)%mod;
						f[i][j][x][y]%=mod;
					}
					if(a[i]){
						f[i][j^1][x][y|1]+=1ll*f[i-1][j][x][y]*(x?fac[i-2]:fac[i-1])%mod;
						f[i][j^1][x][y|1]%=mod;
						f[i][j][x][y]+=1ll*f[i-1][j][x][y]*(x?fac[i-2]:0)%mod;
						f[i][j][x][y]%=mod;	
					}
				}
	Rep(i,0,1)
		Rep(j,0,1)
			ans+=f[n][1][i][j],ans%=mod;
	printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值