CF1956F Nene and the Passing Game 题解
CF1956F Nene and the Passing Game
思路
这个题这一看很平常,就只是维护一个连通性问题,数连通块个数。
但是再看两眼会发现不对。我们将一个人可以传球的区间分为左手和右手。如果我们直接让人和左右手区间同时连边,那可能会出现两个人的右手与右手相连的情况。但是原题的限制要求只有左手与右手相连才是合法的情况。
考虑如何去掉这个东西。先给出结论:只保留被左手与右手覆盖的点,再去连边数连通块是正确的。
考虑为什么。这里给出一个相对感性的理解。
我们考虑上述结论不成立的可能的情况。即两个人本来可以相连但是将单独被左手或者右手的点删去后就不相连了。
但是发现如果一个点不被任何人的右手相连,那么无论那些人的左手能够达到这个点,都不可能产生有贡献的连边。
这样就证明了被删去的点一定不会产生贡献。
但是这样直接暴力连边的复杂度仍然是 \(O(n^2)\) 的。发现我们可以只对区间中的一个点连边,然后让这个区间内的点一次连边即可保证连通性不变。代码实现中我们可以通过差分来维护这个过程。
复杂度 \(O(n\alpha)\)。其中 \(\alpha\) 是并查集维护连通性的复杂度。实际上可以通过连边后 dfs 来做到 \(O(n)\)。
code
#include<bits/stdc++.h>
using namespace std;
// #define int long long
const int N=4e6+7;
int n,a[N],b[N],sl[N],sr[N],s[N],stk[N],fa[N],cnt=0,vis[N];
int findl(int x){
int l=1,r=cnt,ans=cnt+1;
while(l<=r){
int mid=(l+r)>>1;
if(stk[mid]>=x)ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
int findr(int x){
int l=1,r=cnt,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(stk[mid]<=x)ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void merge(int x,int y){fa[find(x)]=find(y);}
void solve(){
cin>>n;
for(int i=1,l,r;i<=n;i++){
cin>>a[i]>>b[i];
l=i-b[i],r=i-a[i];l=max(1,l);l=min(n+1,l);r=min(n,r);r=max(0,r);
if(l<=r)sl[l]++,sl[r+1]--;
l=i+a[i],r=i+b[i];l=max(1,l);l=min(n+1,l);r=min(n,r);r=max(0,r);
if(l<=r)sr[l]++,sr[r+1]--;
}
for(int i=1;i<=n;i++){
sl[i]+=sl[i-1],sr[i]+=sr[i-1];
if(sl[i]&&sr[i])stk[++cnt]=i;
}
for(int i=1;i<=n+cnt;i++) fa[i]=i;
for(int i=1,l,r;i<=n;i++){
l=i-b[i],r=i-a[i];l=findl(l),r=findr(r);
if(l<=r)s[l]++,s[r]--,merge(i+cnt,l);
l=i+a[i],r=i+b[i];l=findl(l),r=findr(r);
if(l<=r)s[l]++,s[r]--,merge(i+cnt,l);
}
for(int i=1;i<=cnt;i++){
s[i]+=s[i-1];if(s[i])merge(i,i+1);
}
int ans=0;
for(int i=1;i<=n;i++){
int f=find(i+cnt);if(!vis[f])vis[f]=1,ans++;
}
cout<<ans<<'\n';
}
void init(){for(int i=1;i<=2*n+1;i++) sl[i]=sr[i]=s[i]=vis[i]=stk[i]=0;cnt=0;}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--)solve(),init();
return 0;
}

浙公网安备 33010602011771号