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
維護出相鄰兩個數的異或和。
可以使用 prev
和 next
方便地得到前驅和後繼的迭代器。
時間複雜度 \(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;
}