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();}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM