atcoder 雜題 #02

dengchengyu發表於2024-12-07

atcoder 雜題 #02

  • arc065_b Connectivity
  • arc137_b Count 1's
  • abc287_f Components
  • abc308_g Minimum Xor Pair Query

arc065_b

對兩種邊分別建圖求並查集,其實就是求有多少個點滿足兩個圖都在同一個並查集。

可以把一個點的並查集標號扔進 map<pair<int,int>,int> 裡,就能統計個數了。

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

這道題容易往錯誤的方向想或者想複雜。

AC 程式碼:

const int N=2e5+5;
int n,m,p;
int fa[N];
int getfa(int x){
	if(fa[x]==x)return x;
	return fa[x]=getfa(fa[x]);
}
int fa2[N];
int getfa2(int x){
	if(fa2[x]==x)return x;
	return fa2[x]=getfa2(fa2[x]);
}
map<pair<int,int>,int> mp;
signed main(){
	read(n,m,p);
	fo(i,1,n)fa[i]=i,fa2[i]=i;
	fo(i,1,m){
		int u,v;
		read(u,v);
		u=getfa(u),v=getfa(v);
		if(u!=v)fa[u]=v;
	}
	fo(i,1,p){
		int u,v;
		read(u,v);
		u=getfa2(u),v=getfa2(v);
		if(u!=v)fa2[u]=v;
	}
	fo(i,1,n)mp[{getfa(i),getfa2(i)}]++;
	fo(i,1,n){
		write(mp[{getfa(i),getfa2(i)}],' ');
	}
	return 0;
}

arc137_b

我沒有想到這樣神奇的水題。

考慮一個要翻轉的區間 \([l,r]\),把一個端點移動一個位置,那麼貢獻的變化量為 1。

那麼求出貢獻的最大值和最小值,最大值的區間一定能透過不斷移動一單位的端點變成最小值的區間,那麼最大值到最小值都能取遍。

於是用字首和求貢獻變化量的最大值 \(Max\) 和最小值 \(Min\)。答案就是 \(Max-Min+1\)

而翻轉一段區間的變化量就是 \(len-2*cnt\),即 \((r-2*cnt_r)-(l-1-2*cnt_{l-1})\)

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

AC 程式碼:

const int N=3e5+5;
int n;
int a[N];
int s[N];
int mx=0,mn=0;
int Max=0,Min=0;
signed main(){
	read(n);
	fo(i,1,n){
		read(a[i]);
		s[i]=s[i-1]-2*a[i];
	}
	fo(i,1,n){
		s[i]+=i;
		mx=max(mx,s[i]-Min);
		mn=min(mn,s[i]-Max);
		Max=max(Max,s[i]);
		Min=min(Min,s[i]);
	}
	write(mx-mn+1);
	return 0;
}

abc287_f

一眼樹上揹包,並且看到 \(n\le 5000\) 是可以透過的。

我們要注意在限制列舉範圍的情況下,樹上揹包的複雜度是 \(O(n^2)\) 的。

\(f_{i,j}\) 表示選擇點 \(i\) 且子樹內有 \(j\) 個連通塊的方案數,\(g_{i,j}\) 表示不選擇點 \(i\) 的方案數。

\(f\to g,g\to g,g\to f\) 都是直接把連通塊個數相加,\(f\to f\) 則把連通塊個數相加後減 1。

注意要先轉移再把 \(sz\) 加上覆雜度才是正確的。

AC 程式碼:

const int N=5005;
const int mod=998244353;
int n;
vector<int> G[N];
int f[N][N],g[N][N];
int sz[N];
void dfs(int x,int y){
	g[x][0]=1;
	f[x][1]=1;
	sz[x]=1;
	for(int v:G[x]){
		if(v==y)continue;
		dfs(v,x); 
		fd(i,sz[x],0){
			fd(j,sz[v],1){
				g[x][i+j]=(g[x][i+j]+(ll)g[x][i]*g[v][j])%mod;
				g[x][i+j]=(g[x][i+j]+(ll)g[x][i]*f[v][j])%mod;
				f[x][i+j]=(f[x][i+j]+(ll)f[x][i]*g[v][j])%mod;
				f[x][i+j-1]=(f[x][i+j-1]+(ll)f[x][i]*f[v][j])%mod;	
			}
		}
		sz[x]+=sz[v];
	}
}
signed main(){
	read(n);
	fo(i,1,n-1){
		int u,v;
		read(u,v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	fo(i,1,n){
		write((f[1][i]+g[1][i])%mod,'\n');
	}
	return 0;
}

abc308_g

考慮對每個數建出 01-Trie。對於一個數 \(x\),我們找一個數 \(y\) 要使異或和最小,那麼 \(\text{LCA}(x,y)\) 的深度要儘可能大,即高位連續相等的位要儘可能多。而如果 \(x,y\) 在 01-Trie 上低位還有分支,那麼 \(x\oplus y\) 肯定不是最優的。

於是發現,答案就是 01-Trie 上相鄰兩個葉子的異或和的最小值,又發現 01-Trie 上相鄰兩個葉子在值域上也相鄰。

於是用 set 維護當前黑板上的數,同時也能方便得到相鄰的數。再用一個 set 維護出相鄰兩個數的異或和。

可以使用 prevnext 方便地得到前驅和後繼的迭代器。

時間複雜度 \(O(Q\log Q)\)

int Q;
const int N=3e5+5;
int tot;
int a[N];
set<pair<int,int> > s;
set<pair<int,pair<int,int>> > t;
void del(int x,int y){
	if(x>y)swap(x,y);
	t.erase({a[x]^a[y],{x,y}});
}
void add(int x,int y){
	if(x>y)swap(x,y); 
	t.insert({a[x]^a[y],{x,y}});
}
signed main(){
	cin>>Q;
	while(Q--){
		int opt;
		cin>>opt;
		if(opt==1){
			++tot;
			cin>>a[tot];
			auto i=s.insert({a[tot],tot}).first;
			if(i!=s.begin())add(prev(i)->second,tot);
			if(next(i)!=s.end())add(next(i)->second,tot);
			if(i!=s.begin()&&next(i)!=s.end())del(prev(i)->second,next(i)->second);
		}
		else if(opt==2){
			int x;
			cin>>x;
			auto i=s.lower_bound({x,0});
			if(i!=s.begin())del(prev(i)->second,i->second);
			if(next(i)!=s.end())del(next(i)->second,i->second);
			if(i!=s.begin()&&next(i)!=s.end())add(prev(i)->second,next(i)->second);
			s.erase(i);
		}
		else{
			cout<<(t.begin()->first)<<'\n'; 
		}
	}
	return 0;
}

相關文章