• 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
    ",(int)Ans);
    }
    
    int main(){int T;cin >>  T;while(T--)Main();}
    

    B - 01 Unbalanced

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

    一个好想的做法:(考场根本没往这方面想)二分答案,硬点 (s_0=k),最后要求 (0le s_i le ext{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
    ",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*bequiv 0 mod c)。显然不能有 (p,q) 都更小的一段。

    (p) 增加 (1) 之后,(q) 就需要减少 (D = a/b mod 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
    ",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
    ",ans);
    }
    #undef int
    
    int main(){int T;cin >> T;while(T--)Main();}
    
  • 相关阅读:
    想更改Github仓库中的某个文件结构
    记git一些基本用法
    剑指Offer-Python(16-20)
    剑指Offer-Python(11-15)
    初次使用flask
    Python的Cmd模块的简易运用学习
    SQL-全称量词查询
    线段树模板1
    OJ输入输出超时(C++)
    二叉查找树(BST)定义
  • 原文地址:https://www.cnblogs.com/weiyanpeng/p/13095975.html
Copyright © 2020-2023  润新知