agc045题解

(未完待续。。。)

A - Xor Battle

首先应该很容易发现:如果所有1号可以放的数可以被后面0
号放的数xor起来抵消的话,则0号玩家肯定赢,反之1号赢。
这也非常好证明,我就不证了。
重点是如何解决以下问题:

一个数组a[1]…a[n] ,其中 ( n ≤ 1 e 5 , a [ i ] ≤ 1 e 18 ) (n\leq1e5,a[i]\leq1e18) (n1e5,a[i]1e18)判断能否取出一些数使得这些数异或起来=k ( k ≤ 1 e 18 ) (k\leq 1e18) (k1e18)

这个问题看起来比较棘手,因为直接模拟的话是 O ( 2 n ) O(2^n) O(2n)肯定不行。
这里就要介绍一个算法:
首先分享一个英语博客:https://codeforces.com/blog/entry/68953
这已经讲的很细了,但我还是看来很久才理解。我简单地描述一下吧:
就是定义 f ( i ) f(i) f(i)表示最小的 f ( i ) f(i) f(i)其中 ( i > > f ( i ) ) a n d 1 = 1 (i >>f(i))and1=1 (i>>f(i))and1=1(也就是最低位的1的位)
然后维护以下组数 a 1 , a 2 , a 3 . . . , a n a_1,a_2,a_3...,a_n a1,a2,a3...,an其中对于任何一对i,j 满足 f ( a i ) ≠ f ( a j ) f(a_i) \neq f(a_j) f(ai)=f(aj)其中 i ≠ j i \neq j i=j
所以这样的数最多 l o g ( m a x a i ) log(maxa_i) log(maxai)个。
如果要想这个序列里加入一个数 x x x ,且依然满足以上性质。则就需要对 x x x进行"slightly modify"(略微的改动),然后加入,设改动过的数为 x ′ x' x
但是我们还需要满足所有数组[ a 1 , a 2 , a 3 . . . a n , x a_1,a_2,a_3...a_n,x a1,a2,a3...an,x]能组成的数,数组[ a 1 , a 2 , a 3 . . . x ′ a_1,a_2,a_3...x' a1,a2,a3...x]仍可以组成。
*注:这里的”组成“指"异或和"
我们可以执行以下操作:
将[ a 1 . . . a n a_1...a_n a1...an]按照 f ( a i ) f(a_i) f(ai)升序排序。
然后从1到n迭代,如果 1 a n d ( x > > f ( a i ) ) = 1 1and(x>>f(a_i))=1 1and(x>>f(ai))=1则让x ^=ai。
这样做有以下好处

  1. 两两的 f f f值不同,数的个数控制在 l o g ( m a x a i ) log(maxa_i) log(maxai)
  2. x x x可以被 x ′ x' x即其它的元素组成

这个数组维护好了,那怎样查找是否可以用其中的数来组成一个数 k k k呢?
其实很简单,由于 f f f的值两两不同,所以直接从低位到高位枚举就ok了。
算法的代码实现: O ( n ∗ l o g ( m a x a i ) ) O(n*log(maxa_i)) O(nlog(maxai))
(那个cf blog里的代码,实现起来可能不太一样,但精髓是一样的):

int basis[d]; // basis[i] keeps the mask of the vector whose f value is i

int sz; // Current size of the basis

void insertVector(int mask) {
	for (int i = 0; i < d; i++) {
		if ((mask & 1 << i) == 0) continue; // continue if i != f(mask)

		if (!basis[i]) { // If there is no basis vector with the i'th bit set, then insert this vector into the basis
			basis[i] = mask;
			++sz;
			
			return;
		}

		mask ^= basis[i]; // Otherwise subtract the basis vector from this vector
	}
}

这样A题瞬间就会做了:

#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define KEEP while(1)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define debug_pair(A) cerr<<A.FIR<<" "<<A.SEC<<endl;
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
typedef pair<mp,mp> superpair;
const int d=64;
LL store[d];
//from 0
LL a[202];
string s;

void sv(){
	int N;
	cin>>N;
	rb(i,1,N) cin>>a[i];
	cin >>s;
	reverse(ALL(s));
	reverse(a+1,a+1+N);
	s='%'+s;
	memset(store,0,sizeof(store));
	rb(i,1,N){
		LL inser=a[i];
		rep(j,d){
			if(store[j]&&((inser>>(j))&1)){
				inser ^=store[j];	
			}
		}
		if(s[i]-'0'){
			if(inser){
				cout<<1<<endl;
				return;
			}	
		}
		else{
			if(inser){
				rep(j,d){
					if(1&(inser>>j)){
						store[j]=inser;
						break;
					}
				}
			}
		}
	}
	cout<<0<<endl;
}
int main(){
	fastio;
	int T;					
	cin>>T;
	while(T--) sv();
	return 0;
}

B - 01 Unbalanced

假设用 s u m i , 0 sum_{i,0} sumi,0表示1~i的0的个数, s u m i , 1 sum_{i,1} sumi,1反之。
所以答案就是要使 m a x { ∣ ( s u m j , 0 − s u m i , 0 ) − ( s u m j , 1 − s u m i , 1 ) ∣ } max\{|(sum_{j,0}-sum_{i,0})-(sum_{j,1}-sum_{i,1})|\} max{(sumj,0sumi,0)(sumj,1sumi,1)} ( i < j ) (i< j ) (i<j)尽可能小。
整理一下就可以发现:
m a x { ∣ ( s u m i , 0 − s u m i , 1 ) − ( s u m j , 0 − s u m j , 1 ) ∣ } max\{|(sum_{i,0}-sum_{i,1})-(sum_{j,0}-sum_{j,1})|\} max{(sumi,0sumi,1)(sumj,0sumj,1)}
可以重新理解一下题目

有一个数列 a 1 , a 2 , a 3 . . . , a n ( n ≤ 1 0 5 , a i = ± 1 ) a_1,a_2,a_3...,a_n(n\leq10^5,a_i=±1) a1,a2,a3...,an(n105,ai=±1),有些位已经固定了,有些没有,设每一位的前缀和为 s u m 1 , s u m 2 , s u m 3 . . . , s u m n sum_1,sum_2,sum_3...,sum_n sum1,sum2,sum3...,sumn,问他们的极差最小为多少。

有一个很明显的做法就是枚举 s u m i sum_i sumi的上界,从前往后贪心使得 s u m i sum_i sumi最小值尽可能大,也就是先把所有的’?'换成-1,然后从前往后扫一遍,能变成+1的就变成+1。
这样的复杂度使 O ( n 2 ) O(n^2) O(n2)的显然不行。
下面想优化:
设把所有的?换成-1时,最大的前缀和为 Z Z Z
如果固定的最大值+2,那么最小值最多+2。所以只需要考虑把 Z Z Z Z + 1 Z+1 Z+1分别作为上界就行了。

C - Range Set

假设a>=b
首先如果一个序列 a a a可以被达到,那么必定存在一段连续的长度 ≥ \geq a/b的 0/1。
倒过来想:
如果有连续的一段长度>=a的0则一定可以。
如果把所有长度>=b的连续的1换成0,满足上一个情况则可以。
正常套路:
有所有情况-不合法的情况则为答案。
以下情况不合法:

  1. 把所有的长度<b的连续的1看作断点,把序列分成了很多段,把这些段叫做S
  2. 每个S的长度<a

这样dp就可以搞定了,设 d p i , j , k dp_{i,j,k} dpi,j,k表示考虑到第i位,与这位相连的S的长为j,这一位是k。
那么可以用以下的方式转移:
d p i , 0 , 1 = ∑ d p m , n , 0 ( i − m < b , n ≤ m i n ( m , a ) ) dp_{i,0,1}=\sum dp_{m,n,0}(i-m<b,n\leq min(m,a)) dpi,0,1=dpm,n,0(im<b,nmin(m,a))
d p i , j , 0 = ∑ d p i − m , j − m , 1 ( j ≠ 0 , j ≤ m i n ( i , a ) , m ≤ j ) dp_{i,j,0}=\sum dp_{i-m,j-m,1}(j\neq0,j \leq min(i,a) ,m\leq j ) dpi,j,0=dpim,jm,1(j=0,jmin(i,a),mj)
d p i , j , 1 = ∑ d p i − m , j − m , 0 ( j ≠ 0 , j ≤ m i n ( i , a ) , b ≤ m ≤ j ) dp_{i,j,1}=\sum dp_{i-m,j-m,0}(j\neq0,j \leq min(i,a) ,b\leq m\leq j ) dpi,j,1=dpim,jm,0(j=0,jmin(i,a),bmj)
这是 O ( n 3 ) O(n^3) O(n3)的优化比较套路可以看以下代码:

/*
AuThOr Gwj
*/
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define KEEP while(1)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define debug_pair(A) cerr<<A.FIR<<" "<<A.SEC<<endl;
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
typedef pair<mp,mp> superpair;
int n,a,b;
LL g[10050][2],allsum,sum[5005],f[5005][5005][2];
const int MOD=1e9+7,base=5010;	
int main(){
	fastio;
	cin>>n>>a>>b;
	if(a<b) swap(a,b);
	f[0][0][1]=1;
	g[0+base][1]=1;
	f[0][0][0]=1;
	sum[0]=1;
	allsum=1;
	rb(i,1,n){
		if(i>=b){
			allsum-=sum[i-b];
			allsum+=MOD;
			allsum%=MOD;
			rb(j,0,a-1){
				if(j>=b)
				g[j-i+base][0]+=f[i-b][j-b][0];
				g[j-i+base][0]%=MOD;
			}
		}
		rb(j,0,a-1){
			if(j<=i)
			rep(k,2){
				if(j==0){
					if(k==1){
						f[i][j][1]=allsum;
					}
				}
				else{
					if(!k){
						f[i][j][k]=g[j-i+base][1];
					}
					else{
						f[i][j][k]=g[j-i+base][0];
					}
				}
			}
			sum[i]+=f[i][j][0];
			sum[i]%=MOD;
			g[j-i+base][1]+=f[i][j][1];
			g[j-i+base][1]%=MOD;
		}
		allsum+=sum[i];
		allsum%=MOD;
	}
	LL res=1;
	rb(i,1,n) res<<=1,res%=MOD;
//	cout<<f[1][0][1]<<endl;
	rb(j,0,a-1){
		rep(k,2){
			res-=f[n][j][k];
//			cout<<j<<" "<<k<<" "<<f[n][j][k]<<endl;
			res+=MOD;
			res%=MOD;
		}
	}
	
	cout<<res<<endl;
	return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值