牛客练习赛108 E.琉焰(非树边性质/线段树分治+可撤销并查集 or LCT)

题目

牛客练习赛108 E.琉焰(非树边性质/线段树分治+可撤销并查集 or LCT)_第1张图片 

思路来源

官方题解

题解

牛客练习赛108 E.琉焰(非树边性质/线段树分治+可撤销并查集 or LCT)_第2张图片

针对每个连通块,单独考虑:

一方面,

任取连通块的某棵生成树,

对于任意非树边(u,v),把树边u到v上的所有边都选中,即被覆盖1次,

任取某个非树边集合S,会导致树边有些被覆盖奇数次,有些被覆盖偶数次,

仅保留覆盖奇数次的树边,连通块内的点的度数就均为偶数了

另一方面,

度数为偶数的点有欧拉回路,

可以取走一个环,使得剩下的边仍然满足存在欧拉回路的条件,

即欧拉回路可以被拆成若干个环,并与刚才选取非树边时所取的环的方式一一对应

所以,答案即为所有非树边任取非空子集,

即若非树边条数为x,答案为2^{x}-1

由于涉及到类似图的连通块的动态维护,可以在线LCT做

也可以离线下来线段树分治+可撤销并查集做,

非树边即为成环边,即并查集合并时已经在同一个集合里的边,维护其条数tot即可

代码

#include
#define pb push_back
#define fi first
#define se second
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int maxn=2e5,N=maxn+10,mod=998244353;
typedef pair P;
map last;
vector

dat[4*N]; int n,m,cnt,par[N],sz[N]; P a; int tot,ans[N],two[N]; int find(int x){ return par[x]==x?x:find(par[x]); } bool merge(int x,int y,stack

&q){ x=find(x),y=find(y); if(x==y)return 1; if(sz[x] &q){ while(!q.empty()){ int x=q.top().fi; int y=q.top().se; q.pop(); sz[x]-=sz[y]; par[y]=y; } } void update(int p,int l,int r,int ql,int qr,P v){ if(ql<=l&&r<=qr){ dat[p].pb(v); return; } int mid=(l+r)/2; if(ql<=mid)update(lson,ql,qr,v); if(qr>mid)update(rson,ql,qr,v); } void dfs(int p,int l,int r){ stack

q; int cnt=0; for(auto v:dat[p]) cnt+=merge(v.fi,v.se,q); // 非树边的数量的变更 tot+=cnt; if(l==r)ans[l]=two[tot]-1; else{ int mid=(l+r)/2; dfs(lson); dfs(rson); } undo(q); tot-=cnt; } int main(){ scanf("%d%d",&n,&m); two[0]=1; for(int i=1;i<=m;++i){ two[i]=two[i-1]*2%mod; scanf("%d%d",&a.fi,&a.se); if(last.count(a)){ update(1,1,m,last[a],i-1,a); last.erase(a); } else last[a]=i; } for(auto v:last) update(1,1,m,v.se,m,v.fi); for(int i=1;i<=m;++i) par[i]=i,sz[i]=1; dfs(1,1,m); for(int i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }#include #define pb push_back #define fi first #define se second #define lson p<<1,l,mid #define rson p<<1|1,mid+1,r using namespace std; typedef long long ll; const int maxn=2e5,N=maxn+10,mod=998244353; typedef pair P; map last; vector

dat[4*N]; int n,m,cnt,par[N],sz[N]; P a; int tot,ans[N],two[N]; int find(int x){ return par[x]==x?x:find(par[x]); } bool merge(int x,int y,stack

&q){ x=find(x),y=find(y); if(x==y)return 1; if(sz[x] &q){ while(!q.empty()){ int x=q.top().fi; int y=q.top().se; q.pop(); sz[x]-=sz[y]; par[y]=y; } } void update(int p,int l,int r,int ql,int qr,P v){ if(ql<=l&&r<=qr){ dat[p].pb(v); return; } int mid=(l+r)/2; if(ql<=mid)update(lson,ql,qr,v); if(qr>mid)update(rson,ql,qr,v); } void dfs(int p,int l,int r){ stack

q; int cnt=0; for(auto v:dat[p]) cnt+=merge(v.fi,v.se,q); // 非树边的数量的变更 tot+=cnt; if(l==r)ans[l]=two[tot]-1; else{ int mid=(l+r)/2; dfs(lson); dfs(rson); } undo(q); tot-=cnt; } int main(){ scanf("%d%d",&n,&m); two[0]=1; for(int i=1;i<=m;++i){ two[i]=two[i-1]*2%mod; scanf("%d%d",&a.fi,&a.se); if(last.count(a)){ update(1,1,m,last[a],i-1,a); last.erase(a); } else last[a]=i; } for(auto v:last) update(1,1,m,v.se,m,v.fi); for(int i=1;i<=m;++i) par[i]=i,sz[i]=1; dfs(1,1,m); for(int i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }

你可能感兴趣的:(#,#,线段树/树状数组,连通块,线段树分治,可撤销并查集)