ZR 集训 Day1 - 基础数据结构及其应用
AT_abc380_e [ABC380E] 1D Bucket Tool
使用并查集表示一个连通块,分别用 $col_i,st_i,ed_i$ 表示连通块 $i$ 的颜色、左端点和右端点。
进行修改操作时,要向当前连通块的左右两边看看能否合并。
代码:
C++
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,q;
int p[N],inv[N],st[N][70];
int solve(int x){//x表示
int cnt=min(x,n-m+1),t=max(0ll,x-(n-m+1));
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)&&st[t][i]!=m) t=st[t][i],cnt-=(1ll<<i);
if(!cnt) return t;
t=m,cnt--;
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)) t+=(1ll<<i),cnt-=(1ll<<i);
return t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++) cin>>p[i],inv[p[i]]=i,st[i-1][0]=inv[i];
fill(st[m],st[m]+63,m);
for(int k=1;k<63;k++) for(int i=0;i<m;i++) st[i][k]=st[st[i][k-1]][k-1];
while(q--){
int t;
cin>>t;
cout<<solve(n-t+1)<<"\n";
}
return 0;
}P1197 [JSOI2008] 星球大战
并查集是不支持删除的,但这道题需要我们对并查集进行删除操作。
不难想到,我们离线并倒着进行处理,这时,删除操作就变成了加边,这题就可做了。
坑点:点从 $0$ 开始计数。
代码:
C++
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,q;
int p[N],inv[N],st[N][70];
int solve(int x){//x表示
int cnt=min(x,n-m+1),t=max(0ll,x-(n-m+1));
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)&&st[t][i]!=m) t=st[t][i],cnt-=(1ll<<i);
if(!cnt) return t;
t=m,cnt--;
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)) t+=(1ll<<i),cnt-=(1ll<<i);
return t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++) cin>>p[i],inv[p[i]]=i,st[i-1][0]=inv[i];
fill(st[m],st[m]+63,m);
for(int k=1;k<63;k++) for(int i=0;i<m;i++) st[i][k]=st[st[i][k-1]][k-1];
while(q--){
int t;
cin>>t;
cout<<solve(n-t+1)<<"\n";
}
return 0;
}P2391 白雪皑皑
不难发现,后面的操作会覆盖前面的颜色,这是一个令人头疼的问题。
根据上面的思路,可以想到倒着进行操作,用连通块并查集维护那些被染了色的块,每次进行操作时找到区间可以染色的来染色。
染色时,我们将当前格子的父亲设为它右边格子,这样下次访问到这个格子时就可以直接跳过。
代码:
C++
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,q;
int p[N],inv[N],st[N][70];
int solve(int x){//x表示
int cnt=min(x,n-m+1),t=max(0ll,x-(n-m+1));
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)&&st[t][i]!=m) t=st[t][i],cnt-=(1ll<<i);
if(!cnt) return t;
t=m,cnt--;
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)) t+=(1ll<<i),cnt-=(1ll<<i);
return t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++) cin>>p[i],inv[p[i]]=i,st[i-1][0]=inv[i];
fill(st[m],st[m]+63,m);
for(int k=1;k<63;k++) for(int i=0;i<m;i++) st[i][k]=st[st[i][k-1]][k-1];
while(q--){
int t;
cin>>t;
cout<<solve(n-t+1)<<"\n";
}
return 0;
}P16377 [NordicOI 2026] Name Change 改名
能交换的两个位置我们连一条边,易正同一连通块内的位置是可以互相交换的,所以我们用并查集维护,最后判断每个联通块对应的两个字符集是否相等。
代码:
C++
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,q;
int p[N],inv[N],st[N][70];
int solve(int x){//x表示
int cnt=min(x,n-m+1),t=max(0ll,x-(n-m+1));
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)&&st[t][i]!=m) t=st[t][i],cnt-=(1ll<<i);
if(!cnt) return t;
t=m,cnt--;
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)) t+=(1ll<<i),cnt-=(1ll<<i);
return t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++) cin>>p[i],inv[p[i]]=i,st[i-1][0]=inv[i];
fill(st[m],st[m]+63,m);
for(int k=1;k<63;k++) for(int i=0;i<m;i++) st[i][k]=st[st[i][k-1]][k-1];
while(q--){
int t;
cin>>t;
cout<<solve(n-t+1)<<"\n";
}
return 0;
}P1084 [NOIP 2012 提高组] 疫情控制
倍增题,尝试封锁根节点的子节点。
不难发现答案具有单调性,可以二分答案。
这里使用倍增,先贪心地往上跳,如果可以跳到根节点,就先让它呆在根节点儿子处,再找到所有需要驻扎的根节点儿子,进行贪心配对。
代码:
C++
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,q;
int p[N],inv[N],st[N][70];
int solve(int x){//x表示
int cnt=min(x,n-m+1),t=max(0ll,x-(n-m+1));
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)&&st[t][i]!=m) t=st[t][i],cnt-=(1ll<<i);
if(!cnt) return t;
t=m,cnt--;
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)) t+=(1ll<<i),cnt-=(1ll<<i);
return t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++) cin>>p[i],inv[p[i]]=i,st[i-1][0]=inv[i];
fill(st[m],st[m]+63,m);
for(int k=1;k<63;k++) for(int i=0;i<m;i++) st[i][k]=st[st[i][k-1]][k-1];
while(q--){
int t;
cin>>t;
cout<<solve(n-t+1)<<"\n";
}
return 0;
}P3098 [USACO13DEC] The Bessie Shuffle G
考虑对操作进行逆操作,直接模拟会超时,想到使用倍增,详细的看代码实现:
C++
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,q;
int p[N],inv[N],st[N][70];
int solve(int x){//x表示需要当前卡片是第几轮被取出的
int cnt=min(x,n-m+1),//cnt表示需要经历的A类洗牌的次数
t=max(0ll,x-(n-m+1));//t表示A类洗牌后的位置
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)&&st[t][i]!=m) t=st[t][i],cnt-=(1ll<<i);//倍增进行逆A类洗牌
if(!cnt) return t;
t=m,cnt--;
for(int i=62;i>=0;i--) if(cnt>=(1ll<<i)) t+=(1ll<<i),cnt-=(1ll<<i);//剩余卡片不够m张,倍增直接取出
return t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++) cin>>p[i],inv[p[i]]=i,st[i-1][0]=inv[i];
fill(st[m],st[m]+63,m);
for(int k=1;k<63;k++) for(int i=0;i<m;i++) st[i][k]=st[st[i][k-1]][k-1];
while(q--){
int t;
cin>>t;
cout<<solve(n-t+1)<<"\n";
}
return 0;
}


Comments NOTHING