【学习笔记】AGC045

A - Xor Battle

算是比较难的 A A A题了,当时降智被卡了半天

显然倒着做,我们知道 A A A必胜的状态,记作集合 S S S。假设此时操作的是 A A A,那么 S ′ S' S的基等于 S S S的基和 a i a_i ai的并。假设此时操作的是 B B B,注意到 A A A必胜和 B B B必败是等价的,所以 S ′ = { x ∣ x ∈ S , x ⊕ a i ∈ S } S'=\{x|x\in S,x\oplus a_i\in S\} S={xxS,xaiS}。我们知道 S S S是一个线性空间,如果 a i ∈ S a_i\in S aiS那么 T ′ = S T'=S T=S,如果 a i ∉ S a_i\notin S ai/S,假设存在 x ∈ S , x ⊕ a i ∈ S x\in S,x\oplus a_i\in S xS,xaiS,那么根据线性空间的基本性质有 a i ∈ S a_i\in S aiS,显然矛盾,因此 T ′ = ∅ T'=\varnothing T=。线性基维护即可。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f
using namespace std;
ll f[65],a[205],m;
int T,n;
string s;
void ins(ll x){
	for(int i=62;i>=0;i--){
		if(x>>i&1){
			if(f[i])x^=f[i];
			else{
				f[i]=x;return;
			}
		}
	}
}
int qry(ll x){
	for(int i=62;i>=0;i--){
		if(x>>i&1){
			if(f[i])x^=f[i];
			else return 0;
		}
	}return 1;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0); 
	cin>>T;
	while(T--){
		cin>>n,memset(f,0,sizeof f);
		for(int i=0;i<n;i++)cin>>a[i];
		cin>>s;int ok=0;
		for(int i=n-1;i>=0;i--){
			if(s[i]=='0')ins(a[i]);
			else if(!qry(a[i]))ok=1;
		}
		cout<<ok<<"\n";
	}
}

B - 01 Unbalanced

大致猜到了做法,但是不会证最后那个结论

问题相当于求 [ 0 : n ] [0:n] [0:n]前缀和的极差。考虑限制最大值的上界,在此前提下使得最小值最大。我们希望这个最大值的上界最小,因此二分即可。这个结论结合贪心的过程不难证明。

似乎奇偶性这个地方有一点小细节,改一下就能过了

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
string s;
int n,Min,a[N],res2;
stack<int>S;
int check(int mid){
	int m=0,m2=0;Min=0;
	while(S.size())S.pop();
	for(int i=0;i<n;i++){
		if(s[i]=='0')m--,a[i]=-1;
		else if(s[i]=='1'){
			m++,a[i]=1;
			if(m>mid){
				if(!m2)return 0;
				m-=2,m2--,a[S.top()]=-1,S.pop();
			}
		}
		else{
			if(m+1<=mid)m++,m2++,a[i]=1,S.push(i);
			else m--,a[i]=-1;
		}Min=min(Min,m);
	}return 1;
}
int main(){
	cin>>s,n=s.size();
	int l=0,r=n/2,res=0;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid*2+1))res=mid,r=mid-1;
		else l=mid+1;
	}
	check(res*2+1);
	int m=0;
	for(int i=0;i<n;i++){
		m+=a[i];assert(m<=res*2+1);
		Min=min(Min,m);
	}res2=res*2+1-Min;
	l=0,r=n/2+1,res=0;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid*2))res=mid,r=mid-1;
		else l=mid+1;
	}
	check(res*2);
	m=Min=0;
	for(int i=0;i<n;i++){
		m+=a[i];assert(m<=res*2);
		Min=min(Min,m);
	}res2=min(res2,res*2-Min);
	cout<<res2;
}

C - Range Set

不妨设 A ≥ B A\ge B AB ,然后逆向操作,每次可以把连着的 A A A 0 0 0或者 B B B 1 1 1替换成任意字符,如果出现 A A A 0 0 0就肯定赢了 。那么就把 ≥ B \ge B B 1 1 1都替换成 0 0 0然后看有没有 ≥ A \ge A A 0 0 0即可。

假设我们处理了前 i i i个位置,然后要接一段 0 0 0上去,显然可以分成多个阶段去做,如果要接一段 1 1 1上去,但是这一段长度 < A <A <A,那么相当于区间加,如果这一段 ≥ A \ge A A,那么可以先一次性转移 A A A个字符,然后再分阶段转移。

复杂度 O ( n 2 ) O(n^2) O(n2)

其实有点像 D F A DFA DFA缩小状态数那种感觉,但是我太菜了代码比大佬的复杂了好几倍

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
int n,A,B,f[5005][5005][3],f2[5005][5005][3];
void add(int &x,int y){
	x=(x+y)%mod;
}
signed main(){
	cin>>n>>A>>B;
	if(A<B)swap(A,B);
	f[0][0][0]=f[0][0][1]=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<=A;j++){
			add(f[i][j][0],f2[i][j][0]);
			add(f[i][j][1],f2[i][j][1]);
			add(f2[i+1][j][0],f2[i][j][0]);
			add(f2[i+1][j][1],f2[i][j][1]);
			if(f[i][j][0]){
				add(f[i+1][min(A,j+1)][0],f[i][j][0]);
				add(f[i+1][min(A,j+1)][1],f[i][j][0]);
			}
			if(f[i][j][1]){
				add(f2[i+1][(j>=A)?A:0][0],f[i][j][1]);
				add(f2[min(n+1,i+B)][(j>=A)?A:0][0],-f[i][j][1]);
				add(f[min(n+1,i+B)][min(A,j+B)][2],f[i][j][1]);
				add(f[min(n+1,i+B)][min(A,j+B)][0],f[i][j][1]);
			}
			if(f[i][j][2]){
				add(f[i+1][min(A,j+1)][2],f[i][j][2]);
				add(f[i+1][min(A,j+1)][0],f[i][j][2]);
			}
		}
	}
	cout<<((ll)f[n][A][0]+f2[n][A][0]+mod+mod)%mod;
}

D - Lamps and Buttons

首先最无脑的策略是,从亮着的灯中任意选一个来撸 (因为排列 p p p是随机的),然后把这个环里面的灯全部撸完(也就是全部点亮),如果遇到自环就失败了。

但是这样似乎就和每个环里面亮着的灯的数目有关系,因此难以优化。

那我们考虑一些比较脑洞的想法。

首先我们考虑,直接从 1 1 1 A A A开始撸(这也是等价的最优策略),让我们先找到第一个 p i = i p_i=i pi=i的位置 t t t。然后对于 [ 1 : t − 1 ] [1:t-1] [1:t1]这些亮着的灯会把整个环撸完,对于 [ t + 1 : A ] [t+1:A] [t+1:A]的灯初始是亮着的因此最后也是亮着的,对于 [ A + 1 : n ] [A+1:n] [A+1:n]的点所在的环就必须至少有一个 [ 1 : t − 1 ] [1:t-1] [1:t1]中的点。

然后 [ 1 : t − 1 ] [1:t-1] [1:t1]不能有自环,这可以用容斥来解决。因此我们可以等价转化为:统计大小为 x + y + z x+y+z x+y+z的排列, ∀ i ∈ z \forall i\in z iz ∃ j ∈ x \exist j\in x jx i i i j j j在同一个环中。

将排列看成一个有向图,每次加入一个 i i i,有两种可能:新建一条边 ( i , i ) (i,i) (i,i)或者选择一条边 ( x , y ) (x,y) (x,y),删掉 ( x , y ) (x,y) (x,y)并加上 ( x , i ) , ( i , y ) (x,i),(i,y) (x,i),(i,y)

这个轮换令人想到斯特林数。。。。

注意到之前的枚举是 O ( n 2 ) O(n^2) O(n2),因此这里的计算应该是 O ( 1 ) O(1) O(1)的。事实上正解也非常巧妙:考虑在插入 x x x个数后立刻插入 z z z个数,这样 z z z只要不连自环,插在哪里都是合法的。因此方案数为 x ( x + y + z ) ! x + z \frac{x(x+y+z)!}{x+z} x+zx(x+y+z)!

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
const int N=1e7+5;
int n,A;
ll fac[N],inv[N],res;
ll fpow(ll x,ll y=mod-2){
	ll z(1);
	for(;y;y>>=1){
		if(y&1)z=z*x%mod;
		x=x*x%mod;
	}return z;
}
void init(int n){
	fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
	inv[n]=fpow(fac[n]);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
}
ll binom(ll x,ll y){
	if(x<0||y<0||x<y)return 0;
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
ll calc(ll x,ll y,ll z){
	assert(x+z>=1);
	return x*fac[x+y+z]%mod*fac[x+z-1]%mod*inv[x+z]%mod;
}
int main(){
	cin>>n>>A;
	init(n);
	for(int i=1;i<=A+1;i++){
		for(int j=0;j<i;j++){
			if(j&1){
				res=(res-binom(i-1,j)*calc(i-1-j,max(0,A-i),n-A))%mod;
			}
			else{
				res=(res+binom(i-1,j)*calc(i-1-j,max(0,A-i),n-A))%mod;
			}
		}
	}cout<<(res+mod)%mod;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值