[FJWC2020D1T1]人生

280 篇文章 1 订阅
220 篇文章 2 订阅

题目

题目描述
有一个 n n n 个点的有向图,边都是编号小的指向编号大的。每个点有颜色,黑或白。一条合法的路径满足经过的点的颜色是黑白交错的。一个图是合法的,当且仅当合法路径数量为奇数。

现在,这个图的边和一些点的颜色都模糊不清了。你需要计算所有合法图的数量。即:计算所有合法的图的数量,要求其满足,一些特定点的颜色是指定的。

数据范围与提示
n ≤ 1 0 5 n\le 10^5 n105 。提示:本题并不难。

思路

一种很自然的思路是,从后往前 d p \tt dp dp,因为前面的点先确定,后面的点在连边的时候,会对前面的点造成深远的影响,这不利于计算。

我们都会直接计算路径数量,形如
f ( i ) = 1 + ∑ ⟨ i , j ⟩ ∈ E c i ≠ c j f ( j ) f(i)=1+\sum_{\lang i,j\rang\in\Bbb E}^{c_i\ne c_j}f(j) f(i)=1+i,jEci=cjf(j)

由于我们只在乎奇偶性,容易想到,对于 j    ( i < j ) j\;(i<j) j(i<j),如果 f ( j ) f(j) f(j) 是偶数,那么它不会造成任何作用。用 b , w b,w b,w 分别代表黑色点、白色点中 f f f 为奇数的数量,转移时枚举连了几条边,转移方程形如
∑ x ≤ b 2 ∣ x ( b x ) g i ( b , w ) ⋅ 2 i − b \sum_{x\le b}^{2|x}{b\choose x}g_{i}(b,w)\cdot 2^{i-b} xb2x(xb)gi(b,w)2ib

可能是 2 ∣ x 2|x 2x,也可能是 2 ∤ x 2\nmid x 2x 。然后你发现,这个转移系数其实就是 2 b − 1 2^{b-1} 2b1 嘛。再跟 2 i − b 2^{i-b} 2ib 合在一起,就成了 2 i − 1 2^{i-1} 2i1,与 b b b 无关了!

只需要存储 b b b 是否等于 0 0 0,因为 b = 0 b=0 b=0 时转移系数是 0 0 0 1 1 1,不是 2 b − 1 2^{b-1} 2b1

复杂度 O ( 8 n ) \mathcal O(8n) O(8n)

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int Mod = 998244353;
const int MaxN = 200005;
int dp[MaxN][2][2][2];
int powtwo[MaxN], a[MaxN];
void addBy(int &x,int y){
	x = (x+y)%Mod;
}

int main(){
	int n = readint(); powtwo[0] = 1;
	for(int i=1; i<=n; ++i){
		a[i] = readint();
		powtwo[i] = (powtwo[i-1]<<1)%Mod;
	}
	dp[0][0][0][0] = 1;
	for(int i=1; i<=n; ++i)
	for(int w=0; w<2; ++w)
	for(int b=0; b<2; ++b)
	for(int p=0; p<2; ++p){
		if(a[n+1-i] != 0){ // choose black
			if(w == 0) // no white, this = b1
				addBy(dp[i][w][b|1][p^1],1ll*
					powtwo[i-1]*dp[i-1][w][b][p]%Mod);
			else if(i != 1){
				int t = 1ll*powtwo[i-2]*dp[i-1][w][b][p]%Mod;
				/* case 1: choose even, this = b1 */ ;
				addBy(dp[i][w][b|1][p^1],t);
				/* case 2: choose odd, this = b0 */ ;
				addBy(dp[i][w][b][p],t);
			}
		}
		if(a[n+1-i] != 1){ // choose white
			if(b == 0) // no black, this = w1
				addBy(dp[i][w|1][b][p^1],1ll*
					powtwo[i-1]*dp[i-1][w][b][p]%Mod);
			else if(i != 1){
				int t = 1ll*powtwo[i-2]*dp[i-1][w][b][p]%Mod;
				/* case 1: choose even, this = w1 */ ;
				addBy(dp[i][w|1][b][p^1],t);
				/* case 2: choose odd, this = w0 */ ;
				addBy(dp[i][w][b][p],t);
			}
		}
	}
	int ans = 0;
	for(int w=0; w<2; ++w)
	for(int b=0; b<2; ++b)
		addBy(ans,dp[n][w][b][1]);
	printf("%d\n",ans);
	return 0;
}

吐槽

这道题并不难啊,不知道为什么,大家做的不是很好。明明 n 4 ∼ n 5 n^4\sim n^5 n4n5 d p \tt dp dp 很好想,写出来之后也很容易优化啊……

另外讲讲 T i w - A i r - O A O \sf Tiw\text-Air\text-OAO Tiw-Air-OAO 的真巨佬做法(这个做法可以用来改编这道题,使它成为真毒瘤):

不妨设第一个点的颜色是黑色。如果后面有一个白色的点,满足其 f f f 为奇数,那么考虑将图 异或 这条边(有则删去,无则加入),显然第一个点的 f f f 的奇偶性将会翻转,从而整张图的路径数量奇偶性翻转。

这就是说,大多数 v a l i d    g r a p h \rm valid\;graph validgraph i n v a l i d    g r a p h \rm invalid\;graph invalidgraph一一对应1(请允许我穿插一下英文,全部使用中文有点繁杂)。而我们知道二者的和——就是所有 p o s s i b l e    g r a p h \rm possible\;graph possiblegraph 。那么我们只要特殊处理一下,不对应的图就可以了。

由于最后一个点的 f f f 必然是奇数,所以只有它的颜色和第一个点的颜色相同时,才可能不存在对应关系。不妨设二者都是黑色。中间的白色点的 f f f 都是偶数,那么无论黑色点怎么连,都不产生任何影响,进而 黑色点的 f f f 都是奇数。那么每个白色点只需要连奇数个黑色点就行了。由于最后一个点是黑色的,所以每个白色点都有 1 2 \frac{1}{2} 21 的情况数合法(道理与 d p \tt dp dp 做法中的那个相同),并且两两独立。

然后这张图究竟是 v a l i d    g r a p h \rm valid\;graph validgraph 还是 i n v a l i d    g r a p h \rm invalid\;graph invalidgraph 呢?(二者要区分,因为前者需要加上,后者需要减去。)明显只跟黑色点数量有关嘛。是奇数则合法,否则不合法。

于是我们很轻松的写出了这个式子(其中 m m m 是边数, i i i 是枚举 ? ? ? 中有多少个黑色):
2 m ⋅ ∑ i ≤ c ? ( − 1 ) c b + 1 + i ( c ? i ) ( 1 2 ) c w + c ? − i =    2 m − c w − c ? ( − 1 ) c b + 1 ∑ i ≤ c ? ( − 1 ) i ( c ? i ) ⋅ 2 i =    ( − 1 ) c b + 1 + c ? ⋅ 2 m − c w − c ? \begin{aligned} &2^{m}\cdot \sum_{i\le c_?}(-1)^{c_b+1+i}{c_?\choose i}\left({1\over 2}\right)^{c_w+c_?-i}\\ =\;&2^{m-c_w-c_?}(-1)^{c_b+1}\sum_{i\le c_?}(-1)^{i}{c_?\choose i}\cdot 2^i\\ =\;&(-1)^{c_b+1+c_?}\cdot 2^{m-c_w-c_?} \end{aligned} ==2mic?(1)cb+1+i(ic?)(21)cw+c?i2mcwc?(1)cb+1ic?(1)i(ic?)2i(1)cb+1+c?2mcwc?

没错,真的化简成为了二项式展开的形式。怎么会这么巧妙!下面是他现场 A C AC AC 的代码,我添加了一点注释。

#include <bits/stdc++.h>

typedef long long ll;

const int N = 200000;
const int P = 998244353;

int norm(int x) {if( x >= P ) x -= P; return x;}
int reduce(int x) {if( x < 0 ) x += P; return x;}
void add(int &x, int y) {if( (x += y) >= P ) x -= P;}
void sub(int &x, int y) {if( (x -= y) < 0 ) x += P;}
int mpow(int b, int p) {
	int r = 1; for(; p; p >>= 1, b = (ll)b * b % P)
		if( p & 1 ) r = (ll)r * b % P;
	return r;
}

int a[N + 5], c[3], n;

int solve() {
	/* 让 c[0] 是与 1 同色的颜色 */ ; 
	if( a[1] == 1 ) std::swap(c[0], c[1]);
	/* 大力计算! */ ;
	int ret = mpow(2, ((ll)n * (n - 1) / 2 - 1 - c[2] - c[1]) % (P - 1));
	if( (c[0] + c[2] + 1) & 1 ) ret = norm(P - ret);
	/* 消除影响 */ ;
	if( a[1] == 1 ) std::swap(c[0], c[1]);
	return ret;
}

int main() {
	scanf("%d", &n);
	for(int i=1;i<=n;i++) {
		scanf("%d", &a[i]);
		c[a[i] == -1 ? 2 : a[i]]++;
	}
	
	if( n == 1 ) {
		printf("%d\n", a[1] == -1 ? 2 : 1);
	} else {
		int ans = mpow(2, ((ll)n * (n - 1) / 2 - 1 + c[2]) % (P - 1));
		if( a[1] == -1 ) {
			c[2]--; // 多统计的值,我们将在下面确定其值
			if( a[n] == -1 ) {
				c[2]--; // 同理
				/* 我们只计算首尾同色情况 */ ;
				a[1] = a[n] = 0, add(ans, solve());
				a[1] = a[n] = 1, add(ans, solve());
			} else {
				/* 让 1 去适应 n */ ;
				c[a[n]]--;
				a[1] = a[n], add(ans, solve());
			}
		} else {
			/* 反正就是大力讨论,想办法让 1,n 同色 */ ;
			c[a[1]]--;
			if( a[n] == -1 ) {
				c[2]--;
				a[n] = a[1], add(ans, solve());
			} else if( a[1] == a[n] ) {
				c[a[n]]--;
				add(ans, solve());
			}
		}
		printf("%d\n", ans);
	}
}

在他的这种做法下,可以改编出这样的题目:

  • T T T 次操作,每次交换两个 a a a 值,仍然求答案。 T ≤ 1 0 5 T\le 10^5 T105
    直接套这个公式,不需要更新 c c c 值,则 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 算快速幂就是了。
  • 已知序列中有 x x x 个黑色, y y y 个白色,求所有这种序列的答案之和。共 1 0 5 10^5 105 个询问。
    讨论一下 1 , n 1,n 1,n 的颜色,然后套公式,乘组合数罢了。复杂度 O ( T log ⁡ n + n ) \mathcal O(T\log n+n) O(Tlogn+n)
  • 已知序列是循环的,给出循环节与循环次数,求答案。循环次数高达 1 0 9 10^9 109,循环节长度高达 1 0 6 10^6 106
    又是这个公式的裸题。直接算呗,有啥好说的。

把出题人吊打了!再膜一次, T i w - A i r - O A O {\sf Tiw\text-Air\text-OAO} Tiw-Air-OAO

永远的神!

  1. 这个技巧非常高妙。比如这篇文章中的第二题,看上去非常不可做,但是利用一一对应就变成了水题。 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值