2024 Noip 做題記錄(七)

DaiRuiChen007發表於2024-11-11

個人訓練賽題解(七)


\(\text{By DaiRuiChen007}\)



Round #25 - 2024.10.23

A. [AGC010D] Divisor

Problem Link

題目大意

給定 \(a_1\sim a_n\),保證 \(\gcd(a_1,\dots,a_n)=1\)

兩人輪流操作,每次給一個大於一的 \(a_i\) 減一,然後所有 \(a_i\) 約去 \(\gcd(a_1,\dots,a_n)\),無法操作者輸,求誰必勝。

資料範圍:\(n\le 10^5\)

思路分析

如果 \(a_i\) 中有 \(1\),那麼說明此後 \(\gcd(a)=1\),答案只和 \(\sum (a_i-1)\) 的奇偶性有關。

此時先手必勝當且僅當 \(a\) 中有奇數個偶數。

然後考慮一般的情況,如果偶數個數仍然為奇數,那麼修改一個偶數,此時奇數個數 \(\ge 2\)(初始至少存在一個偶數),後手無論如何操作一定有 \(2\nmid \gcd(a)\),無法改變元素奇偶性。

因此只要 \(a\) 中有奇數個偶數,先手必勝。

否則考慮剛才的結果,如果此時奇數個數 \(\ge 2\),先手無法改變元素奇偶性,必敗,否則必定操作這個元素,然後交換先後手變成一個值域折半的子問題,遞迴計算即可。

時間複雜度 \(\mathcal O(n\log^2V)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,a[MAXN];
void solve(string A,string B) {
	int s=0;
	for(int i=1;i<=n;++i) s^=(a[i]-1)&1;
	if(s) return cout<<A,void();
	int o=0;
	for(int i=1;i<=n;++i) {
		if(a[i]==1) return cout<<B,void();
		if(a[i]&1) {
			if(o) return cout<<B,void();
			o=i;
		}
	}
	--a[o];
	int g=a[1];
	for(int i=2;i<=n;++i) g=__gcd(a[i],g);
	for(int i=1;i<=n;++i) a[i]/=g;
	solve(B,A);
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	solve("First\n","Second\n");
	return 0;
}



B. [AGC011D] Reflect

Problem Link

題目大意

給定 \(n\) 個門,初始有開有關,\(k\) 次操作從第一個門左側扔一個球進去,如果一個門是開的,球會穿過,否則會被反彈,在此之後門會改變開關狀態,求最終每個門的狀態。

資料範圍:\(n\le 2\times 10^5,k\le 10^9\)

思路分析

如果第一個門是關的,會開啟第一個門並結束,否則考慮整個過程:對於第 \(i\) 個門,球第一次經過時一定是從左到右,且此時第 \(i-1\) 個門是關著的。

如果第 \(i\) 個門是開著的,那麼會直接穿過並繼續,否則會發開第 \(i-1\) 個門並反彈。

那麼第 \(i-1\) 個門最終的狀態就是第 \(i\) 個門原始的狀態取反。

因此第一個門是開著的時,會將所有門翻轉並迴圈左移一位。

那麼維護當前的偏移量,可以 \(\mathcal O(1)\) 維護每次扔球的過程。

打表發現,經過 \(\mathcal O(n)\) 次操作後序列的狀態構成長度為 \(2\) 的迴圈節,因此只要模擬 \(\mathcal O(n)\) 次。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
char s[MAXN];
signed main() {
	int n,k;
	scanf("%d%d%s",&n,&k,s);
	k=min(k,4*n+(k&1));
	int i=0;
	while(k--) {
		if(s[i%n]==(i&1?'B':'A')) s[i%n]^=3;
		else ++i;
	}
	for(int j=i;j<i+n;++j) printf("%c",s[j%n]^(i&1?3:0));
	return 0;
}



C. [AGC014E] Cut

Problem Link

題目大意

給定一棵 \(n\) 個點的樹,每次選擇一條邊割掉,然後在兩端連通塊中選一個點,在第二棵樹上連起來,求能否在第二棵樹上構造出目標樹。

資料範圍:\(n\le 10^5\)

思路分析

考慮第一條刪除的邊 \(e\),此後兩個連通塊中不會再連邊,因此第一次連的邊必須是唯一跨越 \(e\) 的邊。

因此一個樸素的做法就是把目標樹上的邊看成路徑,覆蓋在第一棵樹上,每次取出被覆蓋次數恰好 \(=1\) 的一條邊割斷,連線覆蓋這條邊的路徑。

遇到多個覆蓋次數 \(=1\) 的邊時,可以任選一條,用樹剖維護該過程即可。

時間複雜度 \(\mathcal O(n\log^2n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5,inf=1e9;
int n,rk[MAXN],cov[MAXN];
struct ZkyGt1 {
	static const int N=1<<17;
	int tr[N<<1];
	void psu(int p) {
		int k=min(tr[p<<1],tr[p<<1|1]);
		tr[p]+=k,tr[p<<1]-=k,tr[p<<1|1]-=k;
	}
	void init() {
		for(int i=0;i<N;++i) tr[i+N]=(1<i&&i<=n?cov[rk[i]]:inf);
		for(int i=N-1;i;--i) psu(i);
	}
	void add(int l,int r,int x) {
		for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
			if(~l&1) tr[l^1]+=x;
			if(r&1) tr[r^1]+=x;
			psu(l>>1),psu(r>>1);
		}
		for(;l>1;l>>=1) psu(l>>1);
	}
	int qry() {
		int p=1;
		while(p<N) {
			p<<=1;
			if(tr[p]>tr[p^1]) p^=1;
		}
		tr[p]=inf;
		for(int x=p;x>1;x>>=1) psu(x>>1);
		return rk[p-N];
	}
}	T;
struct FenwickTree {
	int tr[MAXN],s;
	void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]^=v; }
	int qry(int x) { for(s=0;x;x&=x-1) s^=tr[x]; return s; }
}	Q;
int st[MAXN],ed[MAXN],siz[MAXN],dep[MAXN],fa[MAXN],hson[MAXN],top[MAXN],dfn[MAXN],dcnt;
vector <int> G[MAXN];
void dfs0(int u,int fz) {
	siz[u]=1,dep[u]=dep[fz]+1,fa[u]=fz;
	for(int v:G[u]) if(v^fz) {
		dfs0(v,u),siz[u]+=siz[v];
		if(siz[v]>siz[hson[u]]) hson[u]=v;
	}
}
void dfs1(int u,int rt) {
	top[u]=rt,dfn[u]=++dcnt,rk[dcnt]=u;
	if(hson[u]) dfs1(hson[u],rt);
	for(int v:G[u]) if(v!=fa[u]&&v!=hson[u]) dfs1(v,v);
}
int LCA(int u,int v) {
	while(top[u]^top[v]) {
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
void add(int u,int v,int k) {
	while(top[u]^top[v]) {
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		T.add(dfn[top[u]],dfn[u],k),u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	if(u^v) T.add(dfn[v]+1,dfn[u],k);
}
void dfs3(int u) {
	for(int v:G[u]) if(v^fa[u]) dfs3(v),cov[u]+=cov[v];
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs0(1,0),dfs1(1,1);
	for(int i=1;i<n;++i) {
		cin>>st[i]>>ed[i];
		Q.add(dfn[st[i]],i),Q.add(dfn[ed[i]],i);
		++cov[st[i]],++cov[ed[i]];
		cov[LCA(st[i],ed[i])]-=2;
	}
	dfs3(1),T.init();
	for(int o=1;o<n;++o) {
		if(T.tr[1]!=1) return puts("NO"),0;
		int x=T.qry(),i=Q.qry(dfn[x]+siz[x]-1)^Q.qry(dfn[x]-1);
		add(st[i],ed[i],-1);
		Q.add(dfn[st[i]],i),Q.add(dfn[ed[i]],i);
	}
	puts("YES");
	return 0;
}



D. [AGC012E] Camel

Problem Link

題目大意

數軸上有 \(n\) 個點 \(a_1\sim a_n\),當前移動距離上限為 \(x\),每次操作可以移動到距離當前點 \(\le x\)\(a_i\) 上,或者令 \(x\gets \lfloor x/2\rfloor\) 後移動到任意一點上,對每個 \(a_i\) 求從當前點出發能否遍歷所有點。

資料範圍:\(n,x\le 2\times 10^5\)

思路分析

容易發現瞬移操作最多進行 \(\mathcal O(\log V)\) 次。

並且每次瞬移後會把當前點左側和右側距離 \(\le x\) 的點全部遍歷,相當於覆蓋一個區間的點。

那麼我們需要在 \(\log V\) 個線段集合中各選擇一個,覆蓋 \([1,n]\) 中的所有點。

可以用 dp 解決,\(f_S\) 表示在 \(S\) 對應的集合中選過線段,覆蓋的字首長度最大值,有解要求 \(f_U=n\)

要對每個起點求答案,相當於欽定了第一個集合中選擇的線段,那麼我們分別計算 \(f_S,g_S\) 表示用 \(S\) 中線段覆蓋的字首、字尾長度最大值。

求答案時列舉 \(S\) 即可,但此時要列舉 \(\mathcal O(n)\) 次,不可接受,但我們發現同一條線段內的只需要列舉一次,並且初始線段個數 \(>\log V\) 時一定無解,因此只要列舉 \(\mathcal O(\log V)\) 次。

時間複雜度 \(\mathcal O((n+V)\log V)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m,K,V[20],a[MAXN],lp[20][MAXN],rp[20][MAXN],f[1<<20],g[1<<20],t[MAXN];
signed main() {
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=K;i;i>>=1) V[m++]=i;
	V[m++]=0;
	for(int k=0;k<m;++k) {
		lp[k][1]=1,rp[k][n]=n;
		for(int i=2;i<=n;++i) lp[k][i]=(a[i]-a[i-1]>V[k])?i:lp[k][i-1];
		for(int i=n-1;i;--i) rp[k][i]=(a[i+1]-a[i]>V[k])?i:rp[k][i+1];
	}
	for(int s=0;s<(1<<m);++s) {
		f[s]=0,g[s]=n+1;
		for(int i=0;i<m;++i) if(s>>i&1) {
			f[s]=max(f[s],rp[i][f[s^(1<<i)]+1]);
			g[s]=min(g[s],lp[i][g[s^(1<<i)]-1]);
		}
	}
	int q=0;
	for(int i=1;i<=n;++i) if(i==n||a[i+1]-a[i]>K) t[++q]=i;
	if(q>m) {
		for(int i=1;i<=n;++i) puts("Impossible");
		return 0;
	}
	for(int i=1;i<=q;++i) {
		int U=(1<<m)-2; bool ok=0;
		for(int s=0;s<(1<<m);++s) if(!(s&1)&&t[i-1]<=f[s]&&g[U^s]<=t[i]+1) {
			ok=1; break;
		}
		for(int j=t[i];j>t[i-1];--j) puts(ok?"Possible":"Impossible");
	}
	return 0;
}



*E. [AGC011F] Timetable

Problem Link

題目大意

給定 \(n\) 段鐵路連線站點 \(0\sim n+1\),每隔 \(k\) 時刻分別有一輛 \(0\to n+1\)\(n+1\to 0\) 的列車發車,穿過第 \(i\) 段鐵路的時刻是 \(t_i\)

安排兩種列車在每個站點停靠的時間,使得對於若干條特殊鐵路,兩種列車穿過其的時間不交,最小化兩種列車執行時間之和。

資料範圍:\(n\le 10^5\)

思路分析

\(\bmod k\) 意義下考慮所有時間,那麼從 \(n+1\to 0\) 的列車等價於往時間軸負方向移動。

\(x_i,y_i\) 表示兩種方向的列車在第 \(i\) 個站的停靠時間,對應的大寫字母表示其陣列字首和。

那麼我們要求就是 \([X_{i-1}+T_{i-1},X_{i-1}+T_i]\)\([-Y_{i-1}-T_{i},-Y_{i-1}-T_{i-1}]\) 交集為空。

展開後可以得到這相當於 \(X_{i-1}+Y_{i-1}\in[-2T_{i-1},-2T_i]\)

目標是最小化 \(X_n+Y_n+2T_n\),直接 dp 設 \(f_{i,j}\) 表示 \(X_i+Y_i=j\) 的最小代價。

轉移時 \(f_{i,j}+x\to f_{i+1,(j+x)\bmod k}\),要求 \((j+x)\bmod k\in [l_i,r_i]\),發現每個 \(j\) 都是某個 \(l_i/r_i\),可以離散化。

直接把 \([l_i,r_i]\) 以外的 dp 值全部刪除並插入到 \(l_i\) 上即可,用 map 維護 dp 過程即可。

時間複雜度 \(\mathcal O(n\log n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
const ll inf=1e18;
int n,m=0,p=0,K;
ll a[MAXN],L[MAXN],R[MAXN],w[MAXN*2];
map <int,ll> dp;
signed main() {
	scanf("%d%d",&n,&K);
	for(int i=1,op;i<=n;++i) {
		scanf("%lld%d",&a[i],&op);
		if(op==1&&2*a[i]>K) return puts("-1"),0;
		a[i]+=a[i-1];
		if(op==1) ++m,L[m]=(K-2*a[i-1]%K)%K,R[m]=(K-2*a[i]%K)%K,w[++p]=L[m],w[++p]=R[m];
	}
	sort(w,w+p+1),p=unique(w,w+p+1)-w;
	for(int i=0;i<=p;++i) dp[i]=0;
	for(int i=1;i<=m;++i) {
		int l=lower_bound(w,w+p,L[i])-w;
		int r=lower_bound(w,w+p,R[i])-w;
		ll z=inf;
		if(l<=r) {
			auto il=dp.begin(),ir=dp.lower_bound(l);
			for(auto it=il;it!=ir;++it) z=min(z,it->second+w[l]-w[it->first]);
			dp.erase(il,ir);
			il=dp.upper_bound(r),ir=dp.end();
			for(auto it=il;it!=ir;++it) z=min(z,it->second+w[l]-w[it->first]+K);
			dp.erase(il,ir);
		} else {
			auto il=dp.upper_bound(r),ir=dp.lower_bound(l);
			for(auto it=il;it!=ir;++it) z=min(z,it->second+w[l]-w[it->first]);
			dp.erase(il,ir);
		}
		if(dp.count(l)) dp[l]=min(dp[l],z);
		else dp[l]=z;
	}
	ll ans=inf;
	for(auto it:dp) ans=min(ans,it.second);
	printf("%lld\n",ans+2*a[n]);
	return 0;
}



*F. [AGC013F] Flip

Problem Link

題目大意

給定 \(n-1\) 個二元組 \((a_i,b_i)\),以及 \(n\) 個數 \(c_1\sim c_{n}\)

\(q\) 次獨立詢問,加入一個新的二元組 \((d_i,e_i)\),給每個二元組選擇 \(x_i\in \{a_i,b_i\}\),並把他們和 \(c\) 兩兩匹配,要求 \(x_i\le c_{p_i}\)。最大化 \(x_i=a_i\) 的個數。

資料範圍:\(n,q\le 10^5\)

思路分析

先離散化使得 \(c_1\sim c_{n}=1\sim n\)

考慮如何判定一組 \(\{x_i\}\) 是否合法,顯然將兩個序列都升序排列最優,但這樣的條件不方便求答案。

可以轉化成給 \(f[x_i,n]\) 字尾加一,\(f[c_i,n]\) 字尾減一,要求所有 \(f_i\ge 0\)

不妨假設所有 \(b_i<a_i\),先選擇所有 \(x_i=a_i\),然後我們可以以 \(-1\) 的代價將 \(f[b_i,a_i)\) 區間加一,要求調整成所有 \(f_i\ge 0\)

可以採用樸素貪心,從前往後掃描所有 \(f_i\),遇到一個 \(f_i<0\) 就找到覆蓋 \(f_i\) 且右端點最大的區間進行 \(+1\),用優先佇列維護。

考慮最佳化,我們呢可以列舉每個詢問選了 \(d_i\) 還是 \(e_i\),那麼就要對每個 \(x\) 求出給 \(f[x,n]\) 加一後,用初始的 \([b_i,a_i)\) 使得所有 \(f_i\ge 0\) 的最小代價。

我們發現如果 \(f_i<-1\),那麼 \(f[x,n]\) 加一後也是負數,因此至少有一個原始區間 \([b_j,a_j)\) 包含 \(i\),根據貪心,從後向前找每個 \(f_i<-1\),取出左端點最小的一個區間進行 \(+1\) 一定是最優的。

那麼我們可以先用一些操作使得所有 \(f_i\ge -1\),然後 \(f[x,n]\) 加一後說明 \(f[x,n]\ge 0\),只要求出 \(f[1,x-1]\) 的答案,然後用前述貪心從左到右求出每個字首的答案即可。

時間複雜度 \(\mathcal O(n\log n+q)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5,inf=1e9;
int n,m,a[MAXN],b[MAXN],c[MAXN],qx[MAXN],qy[MAXN];
int rv,f[MAXN],tg[MAXN],dp[MAXN],nxt[MAXN];
vector <int> op[MAXN];
bool st[MAXN];
void solve1() {
	priority_queue <array<int,2>,vector<array<int,2>>,greater<array<int,2>>> q;
	for(int i=n,s=0;i>=1;--i) {
		s+=tg[i];
		for(int o:op[i]) q.push({b[o],o});
		while(s+f[i]<-1) {
			while(q.size()&&q.top()[0]>i) q.pop();
			if(q.empty()) {
				for(int j=1;j<=m;++j) puts("-1");
				exit(0);
			}
			int z=q.top()[1]; q.pop();
			st[z]=true,++rv,++s,++tg[a[z]-1],--tg[b[z]-1];
		}
	}
}
void solve2() {
	priority_queue <array<int,2>> q;
	for(int i=1,s=0;i<=n;++i) {
		s+=tg[i],dp[i+1]=dp[i];
		for(int o:op[i]) q.push({a[o]-1,o});
		if(s+f[i]<0) {
			while(q.size()&&q.top()[0]<i) q.pop();
			if(q.empty()) {
				for(int j=i+1;j<=n+1;++j) dp[j]=inf;
				return ;
			}
			int z=q.top()[1]; q.pop();
			++dp[i+1],++s,++tg[b[z]],--tg[a[z]];
		}
	}
}
signed main() {
	scanf("%d",&n),++n;
	for(int i=1;i<n;++i) scanf("%d%d",&a[i],&b[i]);
	for(int i=1;i<=n;++i) scanf("%d",&c[i]),f[i]=-1;
	sort(c+1,c+n+1);
	scanf("%d",&m);
	for(int i=1,x,y;i<=m;++i) {
		scanf("%d%d",&x,&y);
		qx[i]=lower_bound(c+1,c+n+1,x)-c;
		qy[i]=lower_bound(c+1,c+n+1,y)-c;
	}
	for(int i=1;i<n;++i) {
		a[i]=lower_bound(c+1,c+n+1,a[i])-c;
		b[i]=lower_bound(c+1,c+n+1,b[i])-c;
		b[i]=min(a[i],b[i]),++f[a[i]];
		if(a[i]>b[i]) op[a[i]-1].push_back(i);
	}
	for(int i=1;i<=n;++i) f[i]+=f[i-1];
	solve1();
	for(int i=n;i>=1;--i) tg[i]+=tg[i+1],f[i]+=tg[i];
	for(int i=1;i<=n;++i) op[i].clear();
	for(int i=1;i<n;++i) if(!st[i]&&a[i]>b[i]) op[b[i]].push_back(i);
	memset(tg,0,sizeof(tg));
	solve2();
	for(int i=1;i<=m;++i) printf("%d\n",max({-1,n-rv-dp[qx[i]],n-rv-dp[qy[i]]-1}));
	return 0;
}



*G. [AGC014F] Localmax

Problem Link

題目大意

給定 \(1\sim n\) 排列 \(a\),每次操作會把所有 LocalMax 按順序放到序列末尾,求多少次操作後序列被排好序。

資料範圍:\(n\le 2\times 10^5\)

思路分析

我們發現整個排列中性質最好的元素就是 \(1\),因為 \(1\) 的位置並不影響其他元素是否是 LocalMax。

因此我們先假設已知值域為 \([2,n]\) 的元素的排序過程,然後加入元素 \(1\) 的貢獻。

假設將值域 \([2,n]\) 的元素排序需要 \(f\) 步:

如果 \(f=0\):說明序列未經操作,當且僅當 \(a_1=1\) 時不用操作,否則需要進行一次操作。

否則我們需要判斷操作 \(f\) 步後是否有 \(a_1=1\)

考慮進行 \(f-1\) 次操作後的序列:此時序列開頭不可能是 \(2\),否則操作後一定會把 \(2\) 放到序列末尾。

那麼我們可以取出此時的序列開頭 \(x\),分析 \((1,2,x)\) 的位置關係,一定是 \((x,1,2)\)\((x,2,1)\),容易發現操作之後 \(a_1=1\) 當且僅當此時他們的相對順序是 \((x,1,2)\)

我們發現在操作過程中,如果 \(x\) 時 LocalMax,那麼 \(x\) 一定是序列開頭:

由於操作次數有限,因此取出最後的一個時刻滿足 \(x\) 是非開頭 LocalMax,那麼操作後 \(x\) 的前一個元素 \(y\) 也應該是原序列的 LocalMax。

那麼由於 \(x\) 最終要變成序列開頭,因此在此之後必然存在一次操作不同時移動 \(x,y\),此時 \(x\) 又變成非開頭 LocalMax,矛盾。

然後透過分類討論 \((1,2,x)\) 的位置關係,我們發現進行任意次操作,他們的位置關係始終迴圈同構,因此只需要維護他們的初始位置關係即可判斷復原 \(1\) 是否會額外產生一次操作。

倒序列舉,不斷從值域 \([i,n]\) 轉移到 \([i-1,n]\) 即可解決。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN],p[MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),p[a[i]]=i;
	int f=0,x=0;
	for(int i=n-1;i;--i) {
		if(!f) {
			if(p[i]<p[i+1]);
			else ++f,x=i+1;
		} else {
			if((p[x]<p[i]&&p[i]<p[i+1])||(p[i]<p[i+1]&&p[i+1]<p[x])||(p[i+1]<p[x]&&p[x]<p[i]));
			else ++f,x=i+1;
		}
	}
	printf("%d\n",f);
	return 0;
}




Round #26 - 2024.10.29

A. [AGC015D] Or

Problem Link

題目大意

\([l,r]\) 的元素中選出一個子集 \(S\),求 \(S\) 中元素按位或有多少種不同取值。

資料範圍:\(l,r<2^{60}\)

思路分析

首先 \([l,r]\) 內的元素肯定能得到,只需要考慮 \(>r\) 的元素有哪些能得到。

對於 \(l,r\) 二進位制下的一段公共字首,很顯然不會影響答案,可以去掉。

此時設 \(r\) 的最高位為 \(x\),次高位為 \(y\),很顯然 \(l<2^x\)

那麼我們把元素分成 \([l,2^x)\)\([2^x,r)\) 兩部分。

如果不選第一部分的元素,那麼能得到的元素就是 \([2^x,2^x+2^{y+1}-1]\),否則得到的元素就是 \([2^x+l,2^{x+1}-1]\)

取出這兩部分元素的並,並且和 \((r,2^{x+1}-1]\) 取並得到答案。

時間複雜度:\(\mathcal O(1)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int d(ll x) { return x?63-__builtin_clzll(x):-1; }
signed main() {
	ll A,B;
	scanf("%lld%lld",&A,&B);
	if(A==B) return puts("1"),0;
	ll ans=B-A+1,i=d(A^B),S=(1ll<<i)-1;
	A&=S,B&=S;
	if(d(A)<=d(B)) ans+=S-B;
	else ans+=S-A+(1ll<<(d(B)+1))-B;
	printf("%lld\n",ans);
	return 0;
}



B. [AGC015E] Car

Problem Link

題目大意

給定 \(n\) 個動點,第 \(i\) 個動點從 \(x_i\) 出發以 \(v_i\) 的速度移動,初始給若干個動點染色,兩個動點相遇後,如果有一個染色點,會給另一個點也染色。

求有多少種初始染色方案,使得最終每個點都被染色。

資料範圍:\(n\le 2\times 10^5\)

思路分析

\(f(S)\) 表示給 \(S\) 染色後最終會有哪些點被染色,容易發現 \(f(S\cup T)=f(S)\cup f(T)\),否則考慮一個 \(x\in f(S\cup T)\setminus f(S)\cup f(T)\),不斷考慮 \(x\) 從哪裡被染色會匯出矛盾。

觀察 \(f(\{i\})\) 的性質,把每個點畫在 \((v_i,x_i)\) 的座標上,兩個點相遇當且僅當連線斜率 \(<0\),且連線越平緩相遇時間越早。

因此一條染色路徑是斜率 \(<0\) 且遞減的折線。

觀察發現每個點可以染色的點是橫座標連續的一段區間,即 \(v_j<v_i\)\(x_j>x_i\) 的最小的 \(j\),以及 \(v_i<v_j\)\(x_j<x_i\) 的最大的 \(j\)

那麼給一個點染色相當於選擇一個區間,要求所有區間的併為 \([1,n]\)

\(f_i\) 表示考慮當前恰好覆蓋 \([1,i]\) 的方案數,轉移時列舉 \(r=i\) 的區間,從 \(f_{l-1}\sim f_r\) 更新 \(f_r\),注意右端點相同的區間要先從 \(l\) 最小的開始更新。

時間複雜度 \(\mathcal O(n\log n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7;
struct Node { int x,v; } a[MAXN];
ll f[MAXN],s[MAXN];
int n,l[MAXN],r[MAXN];
vector <int> op[MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d%d",&a[i].x,&a[i].v);
	sort(a+1,a+n+1,[&](auto i,auto j){ return i.v<j.v; });
	map <int,int> q;
	for(int i=1,t=0;i<=n;++i) {
		if(a[i].x>t) q[t=a[i].x]=i;
		l[i]=q.lower_bound(a[i].x)->second;
	}
	q.clear();
	for(int i=n,t=MOD;i>=1;--i) {
		if(a[i].x<t) q[t=a[i].x]=i;
		r[i]=(--q.upper_bound(a[i].x))->second;
		op[r[i]].push_back(l[i]);
	}
	f[0]=s[0]=1;
	for(int i=1;i<=n;++i) {
		s[i]=s[i-1];
		sort(op[i].begin(),op[i].end());
		for(int k:op[i]) {
			f[i]=(f[i]+s[i]+MOD-(k>1?s[k-2]:0))%MOD;
			s[i]=(s[i-1]+f[i])%MOD;
		}
	}
	printf("%lld\n",f[n]);
	return 0;
}



C. [AGC017F] Path

Problem Link

題目大意

給定 \(n\times n\) 楊輝三角,選出 \(m\) 條折線,每條折線在每一層都要從左到右升序排列。

還有 \(q\) 個限制形如第 \(i\) 個折線第 \(j\) 層必須向下 / 向右下,求方案數。

資料範圍:\(n,m\le 20\)

思路分析

考慮類似輪廓線 dp 的想法,\(f_{i,j,s}\) 表示處理到第 \(i\) 條折線的 \([1,j]\) 行,\(s\) 中狀壓記錄第 \(i\) 條折線的 \([1,j]\) 行,以及第 \(i-1\) 條折線的 \([j+1,n]\) 行。

但此時我們還需要額外記錄第 \(i-1\) 條折線在第 \(j\) 行的取值,無法接受。

考慮最佳化,我們發現兩條折線取值不同當且僅當某一行第 \(i-1\) 條折線向下,第 \(i+1\) 條折線向右下。

因此這種情況發生後,在第 \(i-1\) 條折線下一次向右下方移動之前,兩條折線不可能重疊。

因此我們可以把這條折線的下一次向右下方移動提前到當前位置上來,這樣第 \(i-1\) 條折線在第 \(j\) 行的取值就和第 \(i\) 條折線一致,且不會影響折線之間的重疊情況。

然後進行 dp 即可,用位運算最佳化狀態轉移即可。

時間複雜度 \(\mathcal O(nm2^n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
int lim[25][25],f[1<<20],g[1<<20];
signed main() {
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q),--n;
	for(int i=0;i<m;++i) for(int j=0;j<n;++j) lim[i][j]=-1;
	for(int x,y,z;q--;) scanf("%d%d%d",&x,&y,&z),lim[x-1][y-1]=z;
	f[0]=1;
	for(int i=0;i<m;++i) for(int j=0;j<n;++j) {
		memset(g,0,sizeof(g));
		for(int s=0;s<(1<<n);++s) {
			if(lim[i][j]!=1) if(!(s>>j&1)) add(g[s],f[s]);
			if(lim[i][j]!=0) {
				if(s>>j&1) add(g[s],f[s]);
				else {
					int o=s>>j; if(o) o&=o-1;
					add(g[((o|1)<<j)|(s&((1<<j)-1))],f[s]);
				}
			}
		}
		memcpy(f,g,sizeof(f));
	}
	int ans=0;
	for(int s=0;s<(1<<n);++s) add(ans,f[s]);
	printf("%d\n",ans);
	return 0;
}



*D. [AGC019E] Shuffle

Problem Link

題目大意

給定兩個長度為 \(n\) 的 01 串 \(A,B\)\(1\) 的個數相同,記為 \(a_1\sim a_k,b_1\sim b_k\)

隨機排列 \(a,b\),然後依次交換 \(A_{a_i},A_{b_i}\),求最終 \(A=B\) 的機率。

資料範圍:\(n\le 10^4\)

思路分析

我們先考慮將 \(a_i,b_i\) 一一匹配,然後確定一組交換 \(a_i,b_i\) 的順序。

假設我們已經知道所有 \((a_i,b_i)\) 為匹配,那麼考慮哪些交換順序是合法的。

用有向邊連線所有 \(a_i\to b_i\),很顯然每個點出度入度 \(\le 1\),圖會形成若干環和鏈。

環上的每個點 \(A_i=B_i=1\),任何順序交換不影響答案,每條鏈的鏈頭 \(A_i=1\),要移動到鏈尾 \(B_i=1\) 的位置上,只能從前往後按順序交換每條邊。

我們發現每個點是什麼狀態實際是由 \(A,B\) 確定的,我們可以確定有多少點只有出度 / 入度,多少點兩者都有。

那麼假設只有出度 / 入度的點有 \(p\) 個,兩者都有的點有 \(q\) 個。

那麼圖上會包含 \(p\) 條鏈和若干個環,其中“中轉點”(入度出度均為 \(1\) 的點)有 \(q\) 個。

\(f_{i,j}\) 表示當前圖上有 \(i\) 條鏈,鏈上有 \(j\) 個“中轉點”的方案數。

  • 如果加入一條鏈,那麼選擇兩個端點,轉移 \(f_{i-1,j}\times (p-i+1)^2\to f_{i,j}\)
  • 如果在鏈中間加入一箇中轉點,插入在某條鏈的末尾,轉移 \(f_{i,j-1}\times (q-j+1)\times i\to f_{i,j}\)

計算答案的時候列舉鏈上“中轉點”個數,剩餘的環隨便連:

\[\mathrm{Ans}=\sum_{i=0}^q\binom{p+q}{i}i!^2f_{p,q-i} \]

時間複雜度 \(\mathcal O(n^2)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=10005,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
char a[MAXN],b[MAXN];
int n,m,len;
ll ans,f[MAXN],fac[MAXN],ifac[MAXN];
signed main() {
	for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	scanf("%s%s",a+1,b+1),len=strlen(a+1);
	for(int i=1;i<=len;++i) if(a[i]=='1') b[i]=='1'?++m:++n;
	f[0]=1;
	for(int i=1;i<=n;++i) {
		for(int j=0;j<=m;++j) f[j]=f[j]*(n-i+1)*(n-i+1)%MOD;
		for(int j=1;j<=m;++j) f[j]=(f[j]+f[j-1]*i*(m-j+1))%MOD;
	}
	for(int j=0;j<=m;++j) {
		ans=(ans+f[m-j]*fac[n+m]%MOD*ifac[n+m-j]%MOD*fac[j])%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



*E. [AGC018F] Subtree

Problem Link

題目大意

給定 \(n\) 個點的有根樹 \(A,B\),給每個點賦權值,使得兩棵樹上每個點的子樹權值和都是 \(\pm 1\)

資料範圍:\(n\le 10^5\)

思路分析

很顯然每個點的權值的奇偶性和其兒子個數有關,如果兩棵樹上兒子個數奇偶性不同,一定無解。

可以猜測當每個點的權值取 \(\{-1,0,1\}\) 時一定有合法構造。

此時我們相當於每個點的子樹中,和為 \(+1\)\(-1\) 的子樹數量差 \(\le 1\)

這種題可以考慮用尤拉回路構造。

用一個虛根連線兩棵樹的根,方便處理每個節點。

此時度數為偶數的節點有奇數個兒子,權值一定取 \(0\),否則要決定權值取 \(\pm1\)

我們把這種度數為奇數的節點在兩棵樹上對應的點連起來,此時每個點度數為偶數,求出一條尤拉回路。

我們定義一個點的權值為 \(+1\) 當且僅當其連的額外邊在尤拉回路中的方向是 \(A\to B\)

此時 \(A\) 中的一棵子樹,權值和為 \(+1\) 當且僅當子樹根 \(u\) 到父親的邊在尤拉回路中方向是 \(fa\to u\),容易驗證該構造的正確性。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
struct Edge { int v,id; };
vector <Edge> G[MAXN];
int n,ec=0,fx[MAXN],fy[MAXN],cur[MAXN],ans[MAXN];
bool vis[MAXN*2];
void link(int x,int y) {
	++ec,G[x].push_back({y,ec}),G[y].push_back({x,ec});
}
void dfs(int u) {
	for(int &i=cur[u];i<(int)G[u].size();++i) if(!vis[G[u][i].id]) {
		Edge e=G[u][i];
		vis[e.id]=true,dfs(e.v);
		if(e.v==u+n) ans[u]=1;
		else if(u==e.v+n) ans[e.v]=-1;
	}
}
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) {
		scanf("%d",&fx[i]);
		link(~fx[i]?fx[i]:0,i);
	}
	for(int i=1;i<=n;++i) {
		scanf("%d",&fy[i]);
		link(~fy[i]?fy[i]+n:0,i+n);
	}
	for(int i=1;i<=n;++i) if(G[i].size()%2!=G[i+n].size()%2) return puts("IMPOSSIBLE"),0;
	puts("POSSIBLE");
	for(int i=1;i<=n;++i) if(G[i].size()%2) link(i,i+n);
	dfs(0);
	for(int i=1;i<=n;++i) printf("%d ",ans[i]);
	puts("");
	return 0;
}



*F. [AGC015F] Euclid

Problem Link

題目大意

定義 \(f(x,0)=0,f(x,y)=f(y,x\bmod y)+1\),對於所有 \(x\in[1,X],y\in [1,Y]\) 的數對求 \(f(x,y)\) 的最大值,以及有多少 \(f(x,y)\) 取到最大值。

資料範圍:\(X,Y\le 10^{18}\)

思路分析

\(g(x,y)=\max_{i\le x,j\le y}f(i,j)\),第一問要求 \(g(X,Y)\),假設 \(X\le Y\)

這是簡單的,取到最大值的 \((x,y)\) 一定是一對斐波那契數,求最大的 \(\mathrm{Fib}_k\le x,\mathrm{Fib}_{k+1}\le Y\)\(k\) 就是答案。

那麼我們就是要找所有 \(f(i,j)=g(i,j)=k\) 的元素對。

我們發現如果 \(i>2j\),那麼 \(g(i,j)=g(i-j,j)\),因此我們只要求出 \(j<i\le 2j\)\(f(i,j)=g(i,j)=k\) 的“極大”元素對,然後就可以算出答案總數。

發現 \(k=x\) 時的極大元素對做一步轉化一定能得到 \(k=x-1\) 的極大元素對。

然後考慮逆推生成,發現在 \(k=x-1\) 時的一個元素對 \((x,y)\) 對應 \(k=x\) 時的元素對 \((y+x,x)\)

並且還有額外的一個元素對 \((\mathrm{Fib}_{k-1}+\mathrm{Fib}_{k+1},\mathrm{Fib}_{k+1})\),可以證明其他的極大元素對不可能互相生成。

那麼元素對個數是 \(\mathcal O(k)\) 級別的。

時間複雜度 \(\mathcal O(\log V)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=105,MOD=1e9+7;
ll fib[MAXN];
vector <array<ll,2>> f[MAXN];
signed main() {
	const int n=88;
	fib[0]=fib[1]=1;
	for(int i=2;i<=n;++i) fib[i]=fib[i-1]+fib[i-2];
	f[1]={{1,2}};
	for(int i=2;i<=n;++i) {
		for(auto x:f[i-1]) f[i].push_back({x[1],x[0]+x[1]});
		f[i].push_back({fib[i+1],fib[i-1]+fib[i+1]});
	}
	int T; scanf("%d",&T);
	for(ll x,y;T--;) {
		scanf("%lld%lld",&x,&y);
		if(x>y) swap(x,y);
		int i=1; ll c=0;
		while(fib[i+1]<=x&&fib[i+2]<=y) ++i;
		for(auto o:f[i]) {
			if(o[0]<=x&&o[1]<=y) c=(c+(y-o[1])/o[0]+1)%MOD;
			if(o[1]<=x&&o[0]<=y) c=(c+(x-o[1])/o[0]+1)%MOD;
		}
		if(i==1) c=(c+x)%MOD;
		printf("%d %lld\n",i,c);
	}
	return 0;
}



*G. [AGC020F] Arc

Problem Link

題目大意

給定一個周長為 \(C\) 的環,有 \(n\) 條長度為 \(a_1\sim a_n\) 的弧,每條弧均勻隨機一個實數位置放置,求所有弧覆蓋整個環至少一次的機率。

資料範圍:\(C\le 50,n\le 6\)

思路分析

先從弧的位置是整點的情況入手:我們需要從一個合適的位置進行破環為鏈。

破環為鏈的一個缺陷是如果某個弧的起點 \(x_i\) 滿足 \(x_i+a_i>C\),那麼會對這條鏈的一段字首 \([1,x+a_i-C]\) 產生覆蓋。

但我們可以從最長鏈 \(a_p\) 的起點開始破環為鏈,那麼 \([1,a_p]\) 已經被覆蓋,其他鏈覆蓋的範圍 \(x+a_i-C\) 一定不超過 \(a_p\)

因此對於這種弧我們就不需要考慮越過 \(C\) 的貢獻,只考慮對字尾的覆蓋。

\(f_{i,j,s}\) 表示將 \(s\) 中的線段左端點放到 \([1,i]\) 中,覆蓋範圍 \([1,j]\) 的方案數,然後 dp 即可。

然後考慮取的是實數的情況。

事實上我們只關心 \(x_i,a_i+x_i\) 的相對大小關係,我們發現比較他們的大小關係只需要比較整數部分,以及小數部分的相對順序。

因此我們可以直接列舉 \(\{x_i\}\)(小數部分)的相對順序,由於 \(x_i\) 在實數範圍內均勻隨機,因此兩個 \(x_i\) 小數部分相等的機率為 \(0\)\(n!\) 列舉 \(\{x_i\}\) 的順序,然後做上面的 dp 即可。

時間複雜度 \(\mathcal O(n!\times 2^nn^2C^2)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[305][64],ans=0;
int n,m,a[10],p[10];
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;++i) scanf("%d",&a[i]),p[i]=i;
	sort(a,a+n);
	do {
		memset(dp,0,sizeof(dp));
		dp[a[n-1]*n][0]=1;
		for(int i=1;i<=n*m;++i) if(i%n) {
			int x=p[i%n-1];
			for(int j=i;j<=n*m;++j) for(int s=0;s<(1<<(n-1));++s) if(!(s>>x&1)) {
				dp[min(n*m,max(j,i+a[x]*n))][s|1<<x]+=dp[j][s];
			}
		}
		ans+=dp[n*m][(1<<(n-1))-1];
	} while(next_permutation(p,p+n-1));
	long double z=ans;
	for(int i=1;i<n;++i) z/=i;
	for(int i=1;i<n;++i) z/=m;
	printf("%.20Lf\n",z);
	return 0;
}




Round #27 - 2024.10.31

A. [AGC024E] Insert

Problem Link

題目大意

從空序列開始,依次向序列中插入一個 \([1,k]\) 的元素,插入 \(n\) 次,要求字典序單調遞增,求方案數。

資料範圍:\(n,k\le 300\)

思路分析

考慮對最終的序列 dp 刪除元素的順序,刪除 \(a_i\) 要求 \(a_i\) 後面第一個 \(\ne a_i\) 的元素小於 \(a_{i}\)

涉及元素大小關係的問題可以插入 dp,每次考慮插入最小值,值域從 \([2,k]\to [1,k]\)

考慮第一個 \(1\) 什麼時候刪除,容易發現這個元素必須在 \(1\) 後面的元素全部刪除後才能刪除。

那麼 \(1\) 前面的元素依然可以正常刪除,因為前面的元素值域 \([2,k]\),末尾元素是 \(1\) 還是空不影響是否可刪的判定。

那麼 \(f_{i,j}\) 表示值域 \([k-i+1,k]\),序列長度為 \(j\) 的方案數,轉移 \(f_{i,j}\gets f_{i-1,p}\times f_{i,j-p-1}\times\binom{j}{j-p}\)

時間複雜度 \(\mathcal O(n^2k)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=305;
ll f[MAXN][MAXN],C[MAXN][MAXN];
signed main() {
	int n,m,MOD;
	scanf("%d%d%d",&n,&m,&MOD);
	for(int i=0;i<MAXN;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	f[0][0]=1;
	for(int i=1;i<=m;++i) {
		memcpy(f[i],f[i-1],sizeof(f[i]));
		for(int j=1;j<=n;++j) for(int k=0;k<j;++k) {
			f[i][j]=(f[i][j]+f[i-1][k]*f[i][j-k-1]%MOD*C[j][j-k])%MOD;
		}
	}
	printf("%lld\n",f[m][n]);
	return 0;
}



*B. [AGC023D] Vote

Problem Link

題目大意

給定數軸上 \(x_1\sim x_n\) 個點,第 \(i\) 個點有 \(a_i\) 個人在公交上,每個人要到對應的點上下車。

當前公交在 \(s\),每個時刻所有人投票決定公交向左還是向右,選票多的方向(平票向左)。

每個人都向最小化到目標的時間,求最終執行時間。

資料範圍:\(n\le 10^5\)

思路分析

考慮哪個點最後訪問,很顯然是第 \(1\) 個或第 \(n\) 個判斷哪個點最後訪問,不難猜測最後訪問 \(n\) 當且僅當 \(a_1\ge a_n\)

\(n\) 歸納,如果 \(n=2\) 顯然成立。

考慮 \(s\)\(x_{n-1}\) 的關係,如果 \(x_{n-1}<s<x_{n}\) 那麼 \(x_1\sim x_{n-1}\) 都想前往負方向,否則會變成 \(n\gets n-1\) 的子問題。

因此路徑最後一步一定是 \(x_1\to x_n/x_n\to x_1\),不妨假設 \(a_1\ge a_n\),那麼 \(x_n\) 中的人到達的時間等於 \(x_1\) 中人到達時間加定值 \(x_n-x_1\)

所以 \(x_n\) 中人的目標轉化成了最小化到達 \(x_1\) 時間,令 \(a_1\gets a_1+a_n\) 遞迴即可。

注意如果 \(a_1\ge a_n,a_1+a_n\ge a_{n-1}\),那麼 \(x_1\to x_{n}\) 的路徑會順帶經過 \(x_{n-1}\),因此統計答案的時候要記錄當前最後一步的方向。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
int n,s;
ll a[MAXN],x[MAXN];
ll f(int l,int r,int op) {
	if(s<x[l]) return x[r]-s;
	if(x[r]<s) return s-x[l];
	if(a[l]<a[r]) return a[r]+=a[l],f(l+1,r,1)+(op!=1?x[r]-x[l]:0);
	else return a[l]+=a[r],f(l,r-1,0)+(op!=0?x[r]-x[l]:0);
}
signed main() {
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;++i) scanf("%lld%lld",&x[i],&a[i]);
	printf("%lld\n",f(1,n,-1));
	return 0;
}



*C. [AGC024F] Sequence

Problem Link

題目大意

給定若干長度 \(\le n\) 的 01 串,求一個 01 串至少是其中 \(k\) 個串的子序列,且長度最長,同長度字典序最小。

資料範圍:\(n\le 20\)

思路分析

可以考慮計算出每個 01 串 \(s\) 是多少個串的子序列。

一個比較複雜的問題是如何讓每個串恰好計算一次,可以考慮用子序列自動機進行判定。

對每個串建子序列自動機,\(f_{s,t}\) 表示當前取出的模式串是 \(s\),在子序列自動機上匹配後,文字串的剩餘部分是 \(t\),這樣的文字串有多少個。

轉移就是 \(s\to s+c\)\(c\in\{0,1\}\)),然後 \(t\) 刪除從前往後第一個 \(c\) 以及其前面的部分,可以用位運算最佳化轉移。

我們可以發現 \(|s|+|t|\le n\),因此總狀態數只有 \(\mathcal O(n2^n)\) 級別,轉移可以做到線性。

時間複雜度 \(\mathcal O(n2^n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
int n,K,f[21][1<<21],g[1<<21];
int hb(int x) { return 31-__builtin_clz(x); }
void add(int x,int i,int y,int w) { f[i][x<<i|y]+=w; }
signed main() {
	cin>>n>>K;
	for(int i=0;i<=n;++i) for(int s=0;s<(1<<i);++s) {
		char op; cin>>op;
		if(op=='1') ++f[i][1<<i|s];
	}
	for(int i=n;~i;--i) for(int s=0;s<(1<<(n+1));++s) if(f[i][s]) {
		int x=s>>i,y=s&((1<<i)-1);
		g[x]+=f[i][s];
		if(y) {
			int j=hb(y);
			add(x<<1|1,j,y&((1<<j)-1),f[i][s]);
		}
		if(y!=(1<<i)-1) {
			int j=hb(y^((1<<i)-1));
			add(x<<1,j,y&((1<<j)-1),f[i][s]);
		}
	}
	for(int i=n;i>=1;--i) for(int s=0;s<(1<<i);++s) if(g[1<<i|s]>=K) {
		for(int o=i-1;~o;--o) cout<<(s>>o&1);
		cout<<"\n";
		return 0;
	}
	cout<<"\n";
	return 0;
}



*D. [AGC021E] Box

Problem Link

題目大意

給定 \(n\) 個藍色盒子,順次加入 \(k\) 個紅或藍色的球,每個球選擇一個盒子放入,如果該盒子中當前顏色的球嚴格多於另一種顏色的球,那麼盒子會變成這種顏色。

求有多少種球的顏色序列使得存在至少一種放盒子的方案使得每個盒子最終為紅色。

資料範圍:\(n,k\le 5\times 10^5\)

思路分析

列舉紅球和藍球個數 \(x,y\)

一個盒子最終為紅色,要麼紅球個數比藍球多,要麼兩者相等且最後一次插入的是藍球。

因此 \(x<y\)\(x\ge y+n\) 是一定無解 / 有解的,可以忽略。

如果 \(x=y\),那麼最後一個球一定是藍球,刪去不影響結果,轉成 \((x,y-1)\) 的子問題。

此時 \(y<x<y+n\),如果一個盒子中紅球比藍球多 \(2\) 個以上,拿出一個給其他盒子一定不劣。

因此此時只會有 \(x-y\) 個紅球比籃球多一個的盒子,以及 \(n-(x-y)\) 個紅藍球個數相等的盒子。

其次,我們發現一個紅藍球個數相等的盒子,如果紅藍球個數 \(>1\),那麼可以把一個紅球和一個藍球一起放到一個紅球比藍球多一個的盒子。

因此所有紅藍球個數相等的盒子中,只有一個紅球一個藍球。

那麼我們把紅球看成左括號,藍球看成右括號,要滿足的限制就是序列的最大括號匹配 \(\ge n-(x-y)\)

把括號序列看成 \((0,0)\to (x+y,x-y)\)\(\pm 1\) 折線,我們知道序列的最大括號匹配之和路徑上最低點 \(h\) 有關,此時括號匹配等於 \(\dfrac{(x+y)-(0-h)-(x-y-h)}{2}\)

展開得到 \(h\ge n-x\),即路徑不能低於 \(y=n-x\),反射容斥得到方案數 \(\binom{x+y}{x}-\binom{x+y}{n-x+y-1}\)

時間複雜度 \(\mathcal O(k)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,MOD=998244353;
ll fac[MAXN],ifac[MAXN],ans;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
signed main() {
	for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	int n,K;
	scanf("%d%d",&n,&K);
	if(K<n) return puts("0"),0;
	for(int R=(K+1)/2;R<=K;++R) {
		int B=K-R; if(R==B) --B;
		ans=(ans+C(R+B,R)+MOD-C(R+B,n-R+B-1))%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



*E. [AGC022D] Shuttle

Problem Link

題目大意

給定 \(n\) 個商場位於 \(x_1\sim x_n\),在商場 \(x_i\) 需要停留至少 \(t_i\) 時刻,一輛車在 \(0,L\) 之間以 \(1\) 的速度來回往返,經過商場的時候可以上下車,求遍歷所有商場後回到起點的最小用時。

資料範圍:\(n\le 3\times 10^5\)

思路分析

首先整個過程以 \(2L\) 為週期,答案也是 \(2L\) 的倍數,那麼可以把 \(t_i\) 先對 \(2L\) 取模,然後要計算最小週期數。

我們發現樸素的做法就是在每個 \(x_i\) 下車,由於 \(t_i<2L\),因此等車下一次過 \(x_i\) 時停留結束上車即可。

考慮如何最佳化:這個過程中車在 \(0,L\) 分別折返一次才返回,實際經過了 \(x_i\) 兩次。

我們發現有的時候只要車在 \(0\)\(L\) 中的一處折返後停留時間就已經超過 \(t_i\) 了,可以經過一次時就提前上車。

因此記 \(l_i,r_i\) 表示當車在 \(0/L\) 折返後停留時間是否超過 \(t_i\),即 \(2x_i\ge t_i\)\(2(L-x_i)\ge t_i\)

考慮如何減少折返輪次,首先一次折返經過每個點兩次,因此一輪折返最多經過兩個點。

那麼我們肯定是在 \(L\) 折返時經過一個點,在 \(0\) 折返時經過一個點,即 \(x_i<x_j\)\(l_i=r_j=1\) 的點之間可以匹配。

然後求出最大匹配即可。

進一步觀察發現,一個 \((l,r)=(1,0)\) 的點一定位於 \(L/2\) 右邊,一個 \((l,r)=(0,1)\) 的點一定位於 \(L/2\) 左邊。

因此不存在形如 \((1,0)\)\((0,1)\) 的點互相匹配,只有 \((1,0)\) 的點和 \(L/2\) 右邊的 \((1,1)\) 匹配,\((0,1)\) 的點和 \(L/2\) 左邊的 \((1,1)\) 匹配,剩餘的 \((1,1)\) 內部匹配。

按順序維護貪心即可。

注意特殊情況如:\(t_i=0\),以及最後一個點不能匹配,只能在 \(r_n=1\) 時減少一次折返。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
int n,st[MAXN],tp;
ll a[MAXN],t[MAXN],L,ans;
bool l[MAXN],r[MAXN],vis[MAXN];
signed main() {
	scanf("%d%lld",&n,&L),ans=n+1;
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	for(int i=1;i<=n;++i) {
		scanf("%lld",&t[i]),ans+=t[i]/(2*L),t[i]%=2*L;
		l[i]=(2*a[i]>=t[i]),r[i]=(2*(L-a[i])>=t[i]);
	}
	if(r[n]) --ans;
	for(int i=1;i<n;++i) if(!t[i]) --ans,vis[i]=true;
	for(int i=1,hd=1,tl=0;i<n;++i) {
		if(l[i]&&r[i]&&!vis[i]) st[++tl]=i;
		if(!l[i]&&r[i]&&hd<=tl) --ans,vis[st[hd++]]=true;
	}
	tp=0;
	for(int i=n-1,hd=1,tl=0;i;--i) {
		if(l[i]&&r[i]&&!vis[i]) st[++tl]=i;
		if(l[i]&&!r[i]&&hd<=tl) --ans,vis[st[hd++]]=true;
	}
	int c=0;
	for(int i=1;i<n;++i) c+=l[i]&&r[i]&&!vis[i];
	ans-=c>>1;
	printf("%lld\n",2*L*ans);
	return 0;
}




Round #28 - 2024.11.04

A. [AGC025D] Grid

Problem Link

題目大意

給定 \(n,x,y\),在 \(2n\times 2n\) 的網格中選 \(n^2\) 個點使得任意兩個點之間的距離不為 \(\sqrt x\)\(\sqrt y\)

資料範圍:\(n\le 300\)

思路分析

\(x=dx^2+dy^2\),分討 \(x\bmod 4\) 的奇偶性:

  • \(x\bmod 4=2\):說明 \(dx,dy\) 為奇數,因此距離為 \(\sqrt x\) 的點橫座標奇偶性不同。
  • \(x\bmod 4=2\):說明 \(dx,dy\) 恰有一個奇數,因此距離為 \(\sqrt x\) 的點黑白間隔染色後顏色不同。
  • \(x\bmod 4=0\):說明 \(dx,dy\) 均為偶數,將整個網格按橫縱座標 \(\bmod 2\) 分成四個完全相等的部分。

因此我們發現距離為 \(\sqrt x\) 的點對一定可以被黑白染色,即形成的圖是二分圖。

那麼距離為 \(\sqrt x\)\(\sqrt y\) 的點是兩張二分圖,我們要在其中找到一個大小 \(\ge \dfrac 14|V|\) 的獨立集。

這是簡單的,在兩張二分圖上黑白染色,如果兩個點在兩張二分圖上顏色都相同就劃入同一個等價類,可以把 \(V\) 恰分成 \(4\) 個等價類,根據抽屜原理,直接取最大的一個即可。

時間複雜度 \(\mathcal O(n^2)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=605;
int n,k1,k2;
void col(auto a,int k) {
	int x=0;
	while(k%4==0) k>>=2,++x;
	if(k%4==1) {
		for(int i=0;i<n;++i) for(int j=0;j<n;++j) a[i][j]=((i>>x)+(j>>x))&1;
	} else if(k%4==2) {
		for(int i=0;i<n;++i) for(int j=0;j<n;++j) a[i][j]=i>>x&1;
	}
}
bool f[MAXN][MAXN],g[MAXN][MAXN];
signed main() {
	scanf("%d%d%d",&n,&k1,&k2),n*=2;
	col(f,k1),col(g,k2);
	for(int p:{0,1}) for(int q:{0,1}) {
		vector <array<int,2>> wys;
		for(int i=0;i<n;++i) for(int j=0;j<n;++j) {
			if(f[i][j]==p&&g[i][j]==q) wys.push_back({i,j});
		}
		if(wys.size()>=n*n/4) {
			wys.resize(n*n/4);
			for(auto z:wys) printf("%d %d\n",z[0],z[1]);
			return 0;
		}
	}
	return 0;
}



B. [AGC027D] Mod

Problem Link

題目大意

給定 \(n\),構造 \(n\times n\) 矩陣,使得所有元素 \(\le 10^{15}\) 且兩兩不同。

且對於任意相鄰元素 \(x,y\)\(\max(x,y)\bmod \min(x,y)\) 都相等且 \(>0\)

資料範圍:\(n\le 500\)

思路分析

一個樸素想法是對整個矩陣黑白間隔染色,然後在黑格上全填質數,白格填周圍四個數的乘積 \(+1\)

但這樣要太多質數,一個更好的辦法是給每行每列賦一個質數,黑格取所在行列質數乘積,白格填周圍四個數的 \(\mathrm{lcm}+1\) ,但這樣白格會變成周圍六個質數乘積。

不難想到對於每條對角線賦一個質數,黑格取所在兩條對角線質數乘積,白格周圍就只剩下了四個質數。

時間複雜度 \(\mathcal O(n^2)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=505,MAXV=10005;
bool isc[MAXV];
vector <int> pr;
ll a[MAXN][MAXN];
ll lcm(ll x,ll y) { return (__int128)x/__gcd(x,y)*y; }
signed main() {
	for(int i=2;i<MAXV;++i) {
		if(!isc[i]) pr.push_back(i);
		for(int p:pr) {
			if(i*p>=MAXV) break;
			isc[i*p]=true;
			if(i%p==0) break;
		}
	}
	int n,op=0;
	scanf("%d",&n);
	if(n==2) return puts("4 7\n23 10"),0;
	int L=0,R=2*n-1+(n&1);
	auto val=[&](){ return (op^=1)?pr[L++]:pr[--R]; };
	for(int i=0;i<=n+1;++i) for(int j=0;j<=n+1;++j) a[i][j]=1;
	
	for(int i=1;i<=n;i+=2) for(int x=1,y=i,p=val();y>=1;++x,--y) a[x][y]*=p;
	for(int i=2+(n&1);i<=n;i+=2) for(int x=i,y=n,p=val();x<=n;++x,--y) a[x][y]*=p;
	
	for(int i=n-1+(n&1);i>=1;i-=2) for(int x=1,y=i,p=val();y<=n;++x,++y) a[x][y]*=p;
	for(int i=3;i<=n;i+=2) for(int x=i,y=1,p=val();x<=n;++x,++y) a[x][y]*=p;
	
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if((i+j)&1) {
		a[i][j]=lcm(lcm(a[i-1][j],a[i+1][j]),lcm(a[i][j-1],a[i][j+1]))+1;
	}
	for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=n;++j) printf("%lld ",a[i][j]);
	return 0;
}



C. [AGC026E] Pair

Problem Link

題目大意

給定 \(n\)a,b 構成的字串,每次可以把從左往右的第 \(i\)ab 同時刪除,求能得到的字典序最大的串。

資料範圍:\(n\le 3000\)

思路分析

考慮從後往前 dp,設 \(f_i\) 表僅考慮第 \(i\sim n\)ab 得到的最大字典序串。

\(a_i,b_i\) 表示第 \(i\)ab 的位置。

很顯然可以直接刪除 \(a_i,b_i\) 得到 \(f_{i+1}\)

否則如果保留了 \(a_i,b_i\),如果 \(a_i<b_i\),那麼 \((a_i,b_i)\) 範圍內的字元都是 \(<i\)b\(>i\)a

那麼我們肯定不會保留這部分的 a,直接找到第一個 \(\min(a_j,b_j)>b_i\) 的位置並得到 \(\texttt{ba}+f_j\)

否則 \((b_i,a_i)\) 範圍內的是 \(>i\)b\(<i\)a,很顯然這些 b 一定是選擇了更優,然後我們又會選若干 \(b_j<a_j\) 的字元對,把這部分的 b 繼續選上,不斷遞迴直到 \((b_j,a_j)\) 中沒有 a,從 \(f_{j+1}\) 轉移。

時間複雜度 \(\mathcal O(n^2)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=6005;
int n,id[MAXN],a[MAXN],b[MAXN];
char s[MAXN];
string f[MAXN];
signed main() {
	scanf("%d%s",&n,s+1);
	for(int i=1,x=0,y=0;i<=n*2;++i) {
		if(s[i]=='a') a[id[i]=++x]=i;
		else b[id[i]=++y]=i;
	}
	for(int i=n;i>=1;--i) {
		if(a[i]<b[i]) {
			int j=i+1;
			for(;j<=n&&min(a[j],b[j])<=b[i];++j);
			f[i]=max("ab"+f[j],f[i+1]);
		} else {
			int j=b[i];
			for(;j<=n*2&&b[id[j]]<a[id[j]]&&(id[j]<=i||b[id[j]]<a[id[j]-1]);++j) {
				if(id[j]>=i) f[i]+=s[j];
			}
			f[i]=max(f[i]+f[id[j]],f[i+1]);
		}
	}
	printf("%s\n",f[1].c_str());
	return 0;
}



Problem Link

題目大意

給定兩棵 \(n\) 個點的樹 \(A,B\),對於每個點 \(u\) 可以在 \(A\) 上改變一個葉子的父親(每個 \(u\) 操作至多一次)。

求讓 \(A=B\) 的最小操作次數。

資料範圍:\(n\le 50\)

思路分析

假設操作次數 \(\ge 1\),那麼可以列舉第一步操作了哪個點 \(u\) 以及其新的父親。

此後 \(u\) 不能被操作,因此可以在兩棵樹上都把 \(u\) 定為根,那麼操作就是選一個葉子,把他的父親改成在 \(B\) 中的父親。

設兩棵樹上 \(u\) 的父親是 \(fa_u,fb_u\),那麼運算元就是 \(fa_u\ne fb_u\)\(u\) 個數。

並且對於一個要操作的 \(u\),其 \(A\) 中的兒子和 \(B\) 中的父親一定要在 \(u\) 操作前操作。

那麼所有 \(u\) 的操作順序就變成了一張拓撲圖,在上面拓撲排序判斷是否有解即可。

時間複雜度 \(\mathcal O(n^3)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=55;
int n,deg[MAXN],ind[MAXN],fa[MAXN],fb[MAXN];
bool w[MAXN];
vector <int> G[MAXN];
vector <array<int,2>> A,B;
void dfs(int u,int *f) { for(int v:G[u]) if(v^f[u]) f[v]=u,dfs(v,f); }
int sol(int rt) {
	for(int i=1;i<=n;++i) G[i].clear();
	for(auto e:A) G[e[0]].push_back(e[1]),G[e[1]].push_back(e[0]);
	fa[rt]=0,dfs(rt,fa);
	for(int i=1;i<=n;++i) G[i].clear();
	for(auto e:B) G[e[0]].push_back(e[1]),G[e[1]].push_back(e[0]);
	fb[rt]=0,dfs(rt,fb);
	for(int i=1;i<=n;++i) G[i].clear(),ind[i]=0;
	int s=0; w[rt]=0;
	for(int i=1;i<=n;++i) if(i^rt) w[i]=fa[i]^fb[i],s+=w[i];
	for(int i=1;i<=n;++i) {
		if(!w[i]&&w[fa[i]]) return n+1;
		if(w[i]&&w[fa[i]]) G[i].push_back(fa[i]),++ind[fa[i]];
		if(w[i]&&w[fb[i]]) G[fb[i]].push_back(i),++ind[i];
	}
	queue <int> q;
	for(int i=1;i<=n;++i) if(!ind[i]) q.push(i);
	while(q.size()) {
		int u=q.front(); q.pop();
		for(int v:G[u]) if(!--ind[v]) q.push(v);
	}
	for(int i=1;i<=n;++i) if(ind[i]) return n+1;
	return s;
}
void solve() {
	scanf("%d",&n),A.resize(n-1),B.resize(n-1);
	for(int i=1;i<=n;++i) deg[i]=0;
	for(auto &e:A) {
		scanf("%d%d",&e[0],&e[1]),++deg[e[0]],++deg[e[1]];
		if(e[0]>e[1]) swap(e[0],e[1]);
	}
	for(auto &e:B) {
		scanf("%d%d",&e[0],&e[1]);
		if(e[0]>e[1]) swap(e[0],e[1]);
	}
	sort(A.begin(),A.end());
	sort(B.begin(),B.end());
	if(A==B) return puts("0"),void();
	int ans=n+1;
	for(auto &e:A) {
		int u=e[0],v=e[1];
		if(deg[u]==1) {
			for(int i=1;i<=n;++i) if(i!=u&&i!=v) e={u,i},ans=min(ans,sol(u)+1);
		}
		if(deg[v]==1) {
			for(int i=1;i<=n;++i) if(i!=u&&i!=v) e={i,v},ans=min(ans,sol(v)+1);
		}
		e={u,v};
	}
	printf("%d\n",ans>n?-1:ans);
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



*E. [AGC025E] Direct

Problem Link

題目大意

給定 \(n\) 個點的樹和 \(m\) 條路徑,給每條路徑定向,一條邊 \((u,v)\) 如果被沿 \(u\to v\)\(v\to u\) 方向經過,分別產生 \(1\) 的收益,最大化收益。

資料範圍:\(n,m\le 2000\)

思路分析

假設每條邊被覆蓋次數為 \(d_e\),那麼其對答案的貢獻至多為 \(\min(2,d_e)\)

考慮構造,我們實際上可以將這個條件強化為沿 \(u\to v\) 經過和 \(v\to u\) 經過的路徑數相差 \(\le 1\)

可以發現在樹上隨意移動,如果最終回到原點,那麼每條邊沿兩種方向經過的次數一定相同。

那麼一個樸素的想法是對每條路徑連線起點和終點,求出尤拉回路後按尤拉回路定向,此時每條邊兩種方向經過次數相同。

問題是這張圖不一定有尤拉回路,可以調整,對於每個度數為奇數的點 \(u\),把 \(u\) 和其父親連一條邊。

由於初始所有點度數總和為偶數,因此這樣總能調整出解,並且每條樹邊至多被新增一次,刪去這些樹邊後,兩種方向的經過次數差就 \(\le 1\)

時間複雜度 \(\mathcal O(n\log n+m)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int n,m;
vector <int> G[MAXN];
int dfn[MAXN],st[MAXN][20],dcnt;
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int bit(int x) { return 1<<x; }
int LCA(int x,int y) {
	if(x==y) return x;
	int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
	return cmp(st[l][k],st[r-bit(k)+1][k]);
}
void dfs0(int u,int fz) {
	dfn[u]=++dcnt,st[dcnt][0]=fz;
	for(int v:G[u]) if(v^fz) dfs0(v,u);
}
int d[MAXN],s[MAXN],t[MAXN],ans=0;
struct Edge { int v,id; };
vector <Edge> E[MAXN];
void dfs1(int u,int fz) {
	for(int v:G[u]) if(v^fz) dfs1(v,u),d[u]+=d[v];
	ans+=min(d[u],2);
	if(E[u].size()&1) {
		E[u].push_back({fz,u+m});
		E[fz].push_back({u,u+m});
	}
}
bool vis[MAXN*2];
int cur[MAXN];
void eul(int u) {
	for(int &i=cur[u];i<(int)E[u].size();++i) if(!vis[E[u][i].id]) {
		auto e=E[u][i];
		vis[e.id]=true,eul(e.v);
		if(e.id<=m&&u!=s[e.id]) swap(s[e.id],t[e.id]);
 	}
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<n;++i) {
		scanf("%d%d",&u,&v);
		G[u].push_back(v),G[v].push_back(u);
	}
	dfs0(1,0);
	for(int k=1;k<=20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
		st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
	}
	for(int i=1;i<=m;++i) {
		scanf("%d%d",&s[i],&t[i]);
		++d[s[i]],++d[t[i]],d[LCA(s[i],t[i])]-=2;
		E[s[i]].push_back({t[i],i});
		E[t[i]].push_back({s[i],i});
	}
	dfs1(1,0);
	for(int i=1;i<=n;++i) eul(i);
	printf("%d\n",ans);
	for(int i=1;i<=m;++i) printf("%d %d\n",s[i],t[i]);
	return 0;
}



*F. [AGC025F] And

Problem Link

題目大意

給定兩個 \(n,m\) 位二進位制數 \(x,y\),每次操作同時給 \(x,y\) 加上 \(x\operatorname{AND}y\),求 \(k\) 次操作後的結果。

資料範圍:\(n,m,k\le 10^6\)

思路分析

樸素的做法就是模擬 \(k\) 輪,每輪從低往高處理 \(x_i=y_i=1\) 的進位。

但這樣做不好最佳化,觀察到單次操作中,後面的 \(x_i=y_i=1\) 不會撞到前面的 \(x_i=y_i=1\)

因此對於 \((x_i,y_i)=(1,1)\)\(1\),我們可以直接在最大的 \(i\) 處連續處理 \(k\) 次進位。

即找到 \(x_i=y_i=1\) 的最大位,不斷考慮 \(j=i,i+1,\dots\),如果 \(x_j=y_j=1\) 那麼給 \(x_j,y_j\) 加一併處理進位,然後令 \(k\gets k-1\)

對於每個 \(i\) 重設 \(k\) 跑一遍,複雜度 \(\mathcal O(n^2)\),容易發現大部分時候會有進位,即 \(\sum x_i+\sum y_i\) 減小。

沒有進位當且僅當 \(x_i=y_i=1,x_{i+1}=y_{i+1}=0\),那麼用棧維護所有 \(x_i\ne 0\)\(y_i\ne 0\) 的位置即可。

時間複雜度 \(\mathcal O(n+m+k)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+5;
int a[MAXN],b[MAXN],stk[MAXN],tp;
signed main() {
	int n,m,K;
	scanf("%d%d%d",&n,&m,&K);
	for(int i=n;i;--i) scanf("%1d",&a[i]);
	for(int i=m;i;--i) scanf("%1d",&b[i]);
	stk[0]=MAXN-1;
	for(int i=max(n,m);i;--i) {
		vector <int> nw;
		for(int j=i,c=K;;) {
			nw.push_back(j);
			while(stk[tp]<=j) --tp;
			if(a[j]>1||b[j]>1) {
				a[j+1]+=a[j]>>1,a[j]&=1;
				b[j+1]+=b[j]>>1,b[j]&=1;
				++j;
			} else if(a[j]&&b[j]&&c) {
				int p=min(stk[tp],c+j);
				a[j]=b[j]=0,++a[p],++b[p],c-=p-j,j=p;
			} else break;
		}
		reverse(nw.begin(),nw.end());
		for(int o:nw) if(a[o]||b[o]) stk[++tp]=o;
	}
	for(n=MAXN-1;!a[n];--n);
	for(int i=n;i;--i) printf("%d",a[i]); puts("");
	for(m=MAXN-1;!b[m];--m);
	for(int i=m;i;--i) printf("%d",b[i]); puts("");
	return 0;
}