“閖”题选讲

by lby in smwcDay10

1. AGC052B

link
题意
给定一棵 n n n 个结点的树( n n n 是奇数),对于任意一条边 ( u i , v i , w i , 1 , w i , 2 ) (u_i,v_i,w_{i,1},w_{i,2}) (ui,vi,wi,1,wi,2) ,可以将除这条边之外的和 u i u_i ui v i v_i vi 相连的所有边异或上 w i , 1 w_{i,1} wi,1,问最后是否可以让每条边的边权变为 w i , 2 w_{i,2} wi,2 。  n ≤ 1 0 5 , w ≤ 2 30 n \le 10^5 , w \le 2^{30} n105,w230

思路
考虑将操作简化, 将边权转化为点权 \color{blue}{将边权转化为点权} 将边权转化为点权 。如何构造,令 w = a u ⊕ a v w=a_u \oplus a_v w=auav ,即让根的点权 a r o o t a_{root} aroot [ 0 , 2 30 − 1 ] [0,2^{30}-1] [0,2301] 之中任意一个数,往下推即可得到整棵树的 a i a_i ai 。那么操作就是交换 a u , a v a_u,a_v au,av 即可,可以画图理解。所以问题就转化成:对两棵树 A , B A,B A,B(边权分别为 w i , 1 , w i , 2 w_{i,1},w_{i,2} wi,1,wi,2)分别求出两棵树的点权集合 { a i } , { b i } \{a_i\},\{b_i\} {ai},{bi},比较这两个集合是否相等。
但由于构造不是唯一的,就是说根的点权不唯一,假设根的点权需异或上 x x x 才可行,此时这整棵树的点权都会异或上 x x x ,会存在一种匹配方案满足 a 1 ⊕ x = b i 1 , a 2 ⊕ x = b i 2 , ⋯   , a n ⊕ x = b i n a_1 \oplus x=b_{i_1},a_2 \oplus x=b_{i_2},\cdots,a_n \oplus x=b_{i_n} a1x=bi1,a2x=bi2,,anx=bin ,变换一下式子,就有 a 1 ⊕ b i 1 = x , a 2 ⊕ b i 2 = x , ⋯   , a n ⊕ b i n = x a_1 \oplus b_{i_1}=x,a_2 \oplus b_{i_2}=x,\cdots,a_n \oplus b_{i_n}=x a1bi1=x,a2bi2=x,,anbin=x,将所有式子异或起来 ⊕ i = 1 n ( a i ⊕ b i ) = x ⊕ x ⊕ ⋯ ⊕ x ⏟ n 个 x \oplus_{i=1}^n (a_i \oplus b_i) =\underbrace{ x \oplus x \oplus \dots \oplus x}_{n个x} i=1n(aibi)=nx xxx,又因为 n n n 为奇数,所以异或 n n n x x x 会等于 x x x ,所以上面的式子又等于 ⊕ i = 1 n ( a i ⊕ b i ) = x \oplus_{i=1}^n (a_i \oplus b_i) = x i=1n(aibi)=x 确定了根的点权为 ⊕ i = 1 n ( a i ⊕ b i ) \oplus_{i=1}^n (a_i \oplus b_i) i=1n(aibi) 之后就可以解决问题了。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;

int idx,head[maxn];
struct EDGE{ int v,next,w,w2; }e[maxn*2];
void Insert(int u,int v,int w,int w2){
	e[++idx]={v,head[u],w,w2};
	head[u]=idx;
}

int f[maxn],f2[maxn];
void Dfs(int x,int fa,bool kt){
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fa){
			if(!kt) f[v]=f[x]^e[i].w;//在 Dfs 之前! 
			else f2[v]=f2[x]^e[i].w2;
			Dfs(v,x,kt);
		}
	}
}

int n;
bool Check(int f[],int f2[]){
	for(int i=1;i<=n;i++)
		if(f[i]!=f2[i]) return false;
	return true;
}

int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int x,y,z,z2; cin>>x>>y>>z>>z2;
		Insert(x,y,z,z2),Insert(y,x,z,z2);
	}
	f[1]=0; Dfs(1,0,0);
	f2[1]=0; Dfs(1,0,1);//f[1]=f2[1] !!!
	sort(f+1,f+n+1),sort(f2+1,f2+n+1);
	if(Check(f,f2)){
		cout<<"YES";
		return 0;
	}
	int sum=0;
	for(int i=1;i<=n;i++)
		sum^=(f[i]^f2[i]);
	f2[1]=sum; Dfs(1,0,1);
	sort(f2+1,f2+n+1);
	if(Check(f,f2)){
		cout<<"YES";
		return 0;
	}
	cout<<"NO";
	return 0;
}

2. AGC052C

link
题意
给定质数 P P P,计数满足以下条件的长度为 N N N 的序列个数:

  • 每一个元素在 [ 1 , P − 1 ] [1,P−1] [1,P1] 之间;
  • 可以重排这个序列,使得它的任意一个前缀和都不能够被 P P P 整除。

答案对 998244353 998244353 998244353 取模。 1 ≤ N ≤ 50000 , 1 ≤ P ≤ 1 0 8 1 \le N \le 50000,1 \le P \le 10^8 1N50000,1P108

思路
发现直接计数不好做,考虑容斥,什么情况重排不了:考虑一个一个放数,如果当前剩下的数有不同的,那必然不会出现模 P P P 0 0 0 的情况。也就是说,如果没有数的个数超过 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n,必然合法;否则,令这个数为 k k k ,由于 P P P 是质数,所以我们可以同时乘 k − 1 k^{-1} k1 (即 k k k 的逆元),显然不影响结果,此时可以认为这 N N N 个数包括 个数 > ⌊ n 2 ⌋ \gt \lfloor \frac{n}{2} \rfloor >2n 1 1 1 和其他一堆非 1 1 1 的数。
这样的话我们可以贪心放置,先放 P − 1 P−1 P1 1 1 1,放一个非 1 1 1 的数,再接着放 1 … 1 \dots 1 记序列 b b b 为原序列 a a a 中非 1 1 1 的数,则只有当 1 1 1 的个数 ≤ P − 1 + ∑ ( P − b i ) \le P−1+\sum (P−b_i) P1+(Pbi) 才可行。
考虑计数。首先计算 和不为 P P P 的排列有多少个。记 a i a_i ai 表示和为 P P P 的倍数的长度为 i i i 的排列数量, b i b_i bi 表示 不为 P 的倍数的长度为 i 的数组数量 P − 1 \frac{不为 P 的倍数的长度为 i 的数组数量}{P−1} P1不为P的倍数的长度为i的数组数量,转移为: a i = b i − 1 × ( P − 1 ) a_i=b_{i-1} \times (P-1) ai=bi1×(P1) b i = a i − 1 × ( P − 1 ) + b i − 1 × ( P − 1 ) × ( P − 2 ) P − 1 = a i − 1 + b i − 1 × ( P − 2 ) \begin {aligned} b_i &= \frac{a_{i-1} \times (P-1) +b_{i-1} \times (P-1) \times (P-2)}{P-1} \\ &= a_{i-1}+b_{i-1} \times (P-2) \end {aligned} bi=P1ai1×(P1)+bi1×(P1)×(P2)=ai1+bi1×(P2)
解释一下:

  • a i a_i ai 中因为 b i − 1 b_{i-1} bi1 的定义 / ( P − 1 ) /(P-1) /(P1) ,所以要乘回来,而第 i i i 个数因为要将和补为 P P P 的倍数,所以只有一种情况, × 1 \times 1 ×1 可以省略。
  • b i b_i bi 中:
    • a i − 1 × ( P − 1 ) a_{i-1} \times (P-1) ai1×(P1) 意思是前面 i − 1 i-1 i1 个数的和模 P P P 0 0 0,那么第 i i i 个数取 [ 1 , P − 1 ] [1,P-1] [1,P1] 都不会再让和为 P P P 的倍数;
    • b i − 1 × ( P − 1 ) × ( P − 2 ) b_{i-1} \times (P-1) \times (P-2) bi1×(P1)×(P2) 中:
      • × ( P − 1 ) \times (P-1) ×(P1) 是抵消定义中的 / ( P + 1 ) /(P+1) /(P+1)
      • × ( P − 2 ) \times (P-2) ×(P2) 是因为第 i i i 个数可以选择 [ 1 , P − 1 ] [1,P-1] [1,P1] 中除了会将和补至 P P P 的倍数的那个数外其他的数,共 ( P − 2 ) (P-2) (P2) 种。

最后得到 b N × ( P − 1 ) b_N \times(P-1) bN×(P1) 表示长度为 N N N 的排列中数的和非 P P P 倍数的个数。考虑减去不符合条件的方案数(即 “ 1 1 1” 的个数 ≤ P − 1 + ∑ ( P − b i ) \le P−1+\sum (P−b_i) P1+(Pbi))。
f i , j f_{i,j} fi,j 表示有 i i i 个非 1 1 1 的数( b 1 b_{1} b1~ b i b_{i} bi), ∑ c = 1 i ( P − b c ) = j \sum\limits_{c=1}^{i} (P−b_c)=j c=1i(Pbc)=j 的方案数。考虑转移,有 b i b_i bi 的取值范围为 [ 2 , P − 1 ] [2,P-1] [2,P1]。下面的 j − 1 j-1 j1 j − ( P − ( P − 1 ) ) j-(P-(P-1)) j(P(P1)) 化简得到。
f i , j = ∑ k = j − ( P − 2 ) j − 1 f i − 1 , k f_{i,j}=\sum\limits_{k=j-(P-2)}^{j-1} f_{i-1,k} fi,j=k=j(P2)j1fi1,k
所以不符合条件的方案数为:
∑ i = 0 n ∑ j = 0 n [ N − i − j ≢ 0    ( m o d   P ) ]    [ N − i > j + P − 1 ]    f i , j × ( N i ) × ( P − 1 ) \sum\limits_{i=0}^{n} \sum\limits_{j=0}^{n} [N-i-j \not\equiv 0 \; (mod \, P)]\; [N-i \gt j+P-1] \;f_{i,j}\times \binom{N}{i} \times (P-1) i=0nj=0n[Nij0(modP)][Ni>j+P1]fi,j×(iN)×(P1)
解释上面式子:

  • 第一个条件表示和不为 p p p 的倍数,序列之和 = 1    的个数 × 1 + 非    1    的数之和 = ( N − i ) × 1 + ∑ b i = 1 \; 的个数 \times 1+非 \;1 \; 的数之和 = (N-i) \times 1 + \sum b_i =1的个数×1+1的数之和=(Ni)×1+bi,又因为 j = ∑ c = 1 i ( P − b c ) = i × P − ∑ c = 1 i b c j=\sum\limits_{c=1}^{i} (P−b_c) = i \times P -\sum\limits_{c=1}^{i} b_c j=c=1i(Pbc)=i×Pc=1ibc i × P i \times P i×P P P P 0 0 0 所以可以忽略,所以序列之和模 P = ( N − i − j ) m o d    P P = (N - i -j) \mod P P=(Nij)modP
  • 第二个条件表示不符合条件,可由 1 的个数 > P − 1 + ∑ ( P − b i ) 1的个数 \gt P−1+\sum (P−b_i) 1的个数>P1+(Pbi) 推理得到;
  • × ( N i ) \times \binom{N}{i} ×(iN) 表示选出不为 1 1 1 的位置;
  • × ( P − 1 ) \times (P-1) ×(P1) 是因为众数不一定为 1 1 1,有可能是 [ 1 , P − 1 ] [1,P-1] [1,P1] 中任意一个,所以还要乘 ( P − 1 ) (P-1) (P1),不会算重是因为此时的众数一定是绝对众数了。

D P DP DP 中还可以前缀和优化。总时间复杂度为 O ( N 2 ) O(N^2) O(N2)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5005,mod=998244353;

ll Pow_(ll x,ll y){
	ll s=1;
	while(y){
		if(y&1) (s*=x)%=mod;
		(x*=x)%=mod;
		y>>=1;
	}
	return s;
}

ll fac[maxn],inv_fac[maxn];
ll C(int m,int n){ return fac[m]*inv_fac[n]%mod*inv_fac[m-n]%mod; }

ll fsa[maxn],fsb[maxn],f[maxn][maxn];
int main(){
	int n,p; cin>>n>>p;
	fac[0]=inv_fac[0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=fac[i-1]*i%mod;
		inv_fac[i]=Pow_(fac[i],mod-2);
	}
	fsa[0]=1;
	for(int i=1;i<=n;i++){
		fsa[i]=fsb[i-1]*(p-1)%mod;
		fsb[i]=(fsa[i-1]+fsb[i-1]*(p-2)%mod)%mod;
	}
	ll ans=fsb[n]*(p-1)%mod; 
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		ll sum=0;
		for(int j=1;j<=n;j++){
			(sum+=f[i-1][j-1])%=mod;
			if(j-(p-2)-1>=0) sum-=f[i-1][j-(p-2)-1],(sum+=mod)%=mod;
			f[i][j]=sum;
		}
	}
	for(int i=0;i<=n;i++)
		for(int j=0;j<=n;j++)//j 从 0 开始!!! 
			if((n-i-j)%p!=0&&n-i>j+p-1) ans-=f[i][j]*C(n,i)%mod*(p-1)%mod,(ans+=mod)%=mod;
	cout<<ans;
	return 0;
}

3. CF547D

link
题意
给定 n n n 个整点,你要给每个点染成红色或蓝色,要求同一水平线或垂直线上两种颜色的数量最多相差 1 1 1,求一种方案。 n ≤ 1 0 5 n \le 10^5 n105

思路
考虑如何建图,对于每一行的点都任意两两配对(连边),每个点都只能选择一次,让匹配出来的一组点一个染红一个染蓝,每一行最多剩下 1 1 1 个点,那就随便染。对于每一列也一样。每个点最多连 2 2 2 条边。所成的环也只会是偶环,二分图染色即可,可以验证这样构造时满足题意的。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;

int idx,head[maxn];
struct EDGE{ int v,next; }e[maxn*2];
void Insert(int u,int v){
	e[++idx]={v,head[u]};
	head[u]=idx;
}

int pnt[maxn];
bool vis[maxn];
void Dfs(int x,int fa){
	vis[x]=1;
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fa&&!vis[v]){
			pnt[v]=pnt[x]^1;
			Dfs(v,x);
		}
	}
}

struct NODE{ int x,y; }a[maxn];
vector<int>vt[maxn*2];
int main(){
	int n; cin>>n;
	int mar=0,mac=0;
	for(int i=1;i<=n;i++){
		cin>>a[i].x>>a[i].y;
		vt[a[i].x].push_back(i);
		mar=max(mar,a[i].x);
	}
	for(int i=1;i<=n;i++){
		vt[a[i].y+mar].push_back(i);
		mac=max(mac,a[i].y);
	}
	for(int i=1;i<=mar;i++)
		for(int j=0;j<vt[i].size();j+=2)
			if(j+1<vt[i].size()) Insert(vt[i][j],vt[i][j+1]),Insert(vt[i][j+1],vt[i][j]);
	for(int i=mar+1;i<=mar+mac;i++)
		for(int j=0;j<vt[i].size();j+=2)
			if(j+1<vt[i].size()) Insert(vt[i][j],vt[i][j+1]),Insert(vt[i][j+1],vt[i][j]);
	for(int i=1;i<=n;i++)
		if(!vis[i]) Dfs(i,0);
	for(int i=1;i<=n;i++)
		cout<<(pnt[i]?"r":"b");
	return 0;
}

4. CF1515E

link
题意
一排关机的电脑(共 n n n 个),你每次可以选一个关机的开机,如果一台电脑左右相邻的两台都开机了,那么它自动开机,求操作方案数。 n ≤ 400 n \le 400 n400

思路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值