AGC 045 部分简要题解

吹水

考场只 A 了一道题属实没救了。

不懂啊,怎么感觉 B 比 F 难...

A - Xor Battle

倒着考虑,遇到一个 \(1\) 的轮的时候,如果当前数字不在之后所有数字的线性基中,那么 \(1\) 显然有必胜策略。否则是否异或不会影响到之后是否胜利。

维护线性基即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010, M = 64;
ll a[N];int n;char s[N];ll b[N];
 
ll insert(ll x){
	for(int i=60;~i;i--)if(1&(x>>i)){
		if(!b[i]){b[i]=x;break;}
		else x^=b[i];
	}
	return x;
}
 
void Main()
{
	for(int i=0;i<M;i++)b[i]=0;
	cin >> n;for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	scanf("%s",s+1);
	bool Ans=0;
	for(int i=n;i;i--){
		if(s[i]=='0'){
			insert(a[i]);
		}else{
			ll d=insert(a[i]);
			// cerr << d << "??" << endl;
			Ans|=d!=0;
		}
	}
	printf("%d\n",(int)Ans);
}

int main(){int T;cin >>  T;while(T--)Main();}

B - 01 Unbalanced

第一步显然是让 \(0\) 变成 \(-1\)\(1\) 变成 \(1\),现在就变成前缀和的最大最小值只差。

一个好想的做法:(考场根本没往这方面想)二分答案,硬点 \(s_0=k\),最后要求 \(0\le s_i \le \text{mid}\)。硬点实际是不需要的,可以直接弄到一起做转移(\(s_i=k\) 是否可行)。

另外一个做法:考虑 \(max\) 确定的时候有容易证明的贪心:先让问号变成 \(-1\),从左到右依次考虑能否把当前变成 \(1\),可以则变。冷静分析可以发现的是我们令 \(max=t\) 的时候得到的 \(min\)\(f(t)\),实际上是有:\(f(t) \le f(t+2)+2\) 的。证明大概是考虑一个后缀如果被多增加了两次,那么实际上一定能在 \(f(t)\) 中多增加一次的。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int n;
char s[N];
int g[N];
 
int v[N], h[N];
 
int main()
{
	scanf("%s",s+1);n=strlen(s+1);
	int mx=0,ss=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='1')ss++;
		else ss--;
		v[i]=h[i]=ss;
		mx=max(mx,ss);
	}
	int mn=0;
	for(int i=n-1;i;i--)v[i]=max(v[i],v[i+1]);
	ss=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='1')ss++;
		else if(s[i]=='0')ss--;
		else if(s[i]=='?'){
			if(ss+v[i]+2-h[i-1]<=mx)ss++;
			else ss--;
		}
		mn=min(mn,ss);
	}
	int ans=mx-mn;
	mn=ss=0;
	++mx;
	for(int i=1;i<=n;i++){
		if(s[i]=='1')ss++;
		else if(s[i]=='0')ss--;
		else if(s[i]=='?'){
			if(ss+v[i]+2-h[i-1]<=mx)ss++;
			else ss--;
		}
		mn=min(mn,ss);
	}
	ans=min(ans,mx-mn);
	printf("%d\n",ans);
}

C - Range Set

考虑倒推,发现只要倒推到某一步骤两个都能操作那一定能全部变成 \(0\) 了。

优先考虑的显然是 \(A,B\) 中较小的操作,操作完这样的区间之后如果较大的那个能够操作,那么就能一直操作下去了。

于是就是一个简单的 dp。

#include<bits/stdc++.h>
using namespace std;
const int N = 5020;
const int mod=1e9+7;
typedef long long ll;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}

int g[N][2];
int f[N][2];
int ans=0, n, a, b;

int main()
{
	cin >> n >> a >> b;
	if(a>b)swap(a,b);
	if(b>1){
		f[0][0]=f[0][1]=1;
		for(int i=1;i<=n;i++){
			for(int k=0;k<2;k++){
				for(int j=1;j<=i;j++){
					if(k==0&&j<a)continue;
					f[i][k]=add(f[i][k],f[i-j][k^1]);
				}
			}
		}
		f[0][0]=0;
		for(int i=1;i<n;i++){
			if(i<a)g[i][0]=add(g[i][0],1);
			for(int j=1;j<min(i,a);j++){
				g[i][0]=add(g[i][0], g[i-j][1]);
			}
			if(i<b)g[i][1]=add(g[i][1], add(f[i-1][0],f[i-1][1]));
			for(int j=1;j<min(i,b);j++){
				int w=j==1?1:add(f[j-2][0],f[j-2][1]);
				g[i][1]=add(g[i][1],1ll*g[i-j][0]*w%mod);
			}
			if(n-i<b)ans=add(ans, mul(g[i][0], add(f[n-i-1][0],f[n-i-1][1])));
			if(n-i<a)ans=add(ans, g[i][1]);
		}
	}
	cout << sub(qpow(2,n), ans) << endl;
}

D - Lamps and Buttons

策略是每次找一个最小的点亮的且不知道环的把这个环弄出来,直到所有点都点亮。唯一的问题是有环没有被点亮,或者有个自环。

现在枚举最后一个自环的位置 \(c\),这个点硬点成为了自环,之后分成了 \(c-1\) , \(a-c\), \(n-a\) 的三段,(以下用 \(x,z,y\) 分别表示):

现在只需要求: 前 \(x\) 个无自环,之后 \(y\) 点所在的环有至少一个是前 \(x\) 个点,之后\(z\) 个随意。

容斥掉无自环的条件,变成:前 \(x\) 个随意,之后 \(y\) 个点所在的环有至少一个是前 \(x\) 个点,之后 \(z\) 个随意。

这个不难推出来是:\(\frac{(x+y+z)!}{x+y}*x\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
/* math */
const int N = 1e7+5;
const int M = 5010;
int fac[N], ifac[N], inv[N];
inline void init(int n=1e7){
	fac[0]=ifac[0]=1;for(int i=1;i<=n;i++)fac[i]=mul(fac[i-1],i);
	ifac[n]=qpow(fac[n],mod-2);for(int i=n-1;i;--i)ifac[i]=mul(ifac[i+1],i+1);
	inv[1]=1;for(int i=2;i<=n;i++)inv[i]=mul(mod-mod/i,inv[mod%i]);
}
int g[M];
int n,a;
inline int binom(int a,int b){return mul(fac[a],mul(ifac[b],ifac[a-b]));}
inline int solve(int a,int b,int c){return mul(fac[a+b+c],mul(a,inv[a+b]));}
int ans=0;
int main()
{
	init();
	cin >> n >> a;
	for(int i=1;i<=a;i++){
		for(int j=1;j<=i;j++){
			int w=binom(i,j);if((i-j)&1)w=sub(0,w);
			int qwq=solve(j,n-a,max(0,a-i-1));
			ans=add(ans, mul(w,qwq));
		}
	}
	cout << ans << endl;
}

E - Fragile Balls

咕咕咕

F - Division into Multiples

这个是可以搞成 \(a,b,c\) 两两互质的关系的。具体可以看代码。

现在考虑最优的一对 \(p,q\) 满足 \(p*a+q*b\equiv 0 \bmod c\)。显然不能有 \(p,q\) 都更小的一段。

\(p\) 增加 \(1\) 之后,\(q\) 就需要减少 \(D = a/b \bmod c\) (取模意义下)。那么相当于是从 \((0,c)\) 一直走 \((1,-D)\),然后去掉左下角有点的坐标。

很容易观察到最后得到的点构成一个凹壳,且卸率最多由 \(\log\) 个等差数列构成。

得到这个等差数列的方法有很多种,比如直接考虑将 \(c\) 分成若干链,这样是可以递归下去的。

题解做法是考虑一个 \(c*D\) 的格子。每次向右上角走,坐标每次分别对 \(c,D\) 取模。把所有走到横坐标轴上的前缀 max 取出来。这样与原问题是一一对应的。对于这样一个问题可以类似扩欧那样递归下去,也可以轻松证明其复杂度。

upd: 这个做法的正确性仍然需要依靠前缀min都在凸壳上,然而可以发现的是当斜率增大的时候,向量的横坐标也在增大,所以容易证明不存在以上问题。

跑多步的时候相当于对这些向量做 \(min\) 卷积,这个时候因为是凹壳,这是一个闵可夫斯基和。所以剩下的部分二分答案可以轻松做到 \(log^2V\)

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

#define int long long
int a,b,x,y,c;

inline int gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}

inline void exgcd(int a,int b,int &x,int &y){
	if(b)exgcd(b,a%b,y,x),y-=a/b*x;
	else x=1,y=0;
}

inline int inv(int x,int p){//gcd(a,b)=0;
	int a,b;exgcd(x,p,a,b);
	if(a<0)a+=p;
	return a;
}

vector<int> s, t;

inline int gety(int x,int step){
	if(x==0)return c;
	return (c-1ll*x*step%c)%c;
}

inline int lowdiv(int x,int y){
	return x/y-(x%y&&(x^y)<0);
}

void Main(){
	int ans=0;
	cin >> a >> x >> b >> y >> c;
	int g=gcd(a,b);a/=g,b/=g;
	c/=gcd(c,g);
	for(int _=1;_<=2;_++){
		g=gcd(a,c);
		c/=g,a/=g;int h=gcd(b,g);
		b/=h,y/=g/h;

		swap(a,b),swap(x,y);
	}
	if(c==1){printf("%lld\n",x+y);return ;}
	int step=1ll*inv(b,c)*a%c;
	vector<int> pos, cnt;
		int z=inv(step, c), h=step, w=c, cur=0;
		while(w){
			int p=w/h;
			pos.push_back(1ll*cur*z%c);
			cnt.push_back(p);
			cur+=p*h;
			w%=h;
			if(w==0)break;
			h%=w;
			if(h==0)h=w;
		}
		pos.push_back(c);
	for(size_t i=0;i<cnt.size();i++){
		int lx=pos[i], ly=gety(lx, step);
		int rx=pos[i+1], ry=gety(rx, step);
		int dx=(rx-lx)/cnt[i];
		int dy=(ly-ry)/cnt[i];
		int lw=0,up=x+y+1;
		while(up-lw>1){
			int mid=(lw+up)>>1;
			int p=lowdiv(x-lx*mid,dx);
			int q=lowdiv(y-ry*mid,dy);
			if(p>=0&&q>=0&&p+q>=cnt[i]*mid)lw=mid;
			else up=mid;
		}
		ans=max(ans, lw);
	}
	printf("%lld\n",ans);
}
#undef int

int main(){int T;cin >> T;while(T--)Main();}
posted @ 2020-06-11 20:50  jerome_wei  阅读(730)  评论(2编辑  收藏  举报