CF1555F Good Graph 题解
RedLycoris · · 题解
晚上有点累就没打Edu,感觉亏大本了
首先,我们需要一个性质:
合法的图中不存在两个有着公共边的环
证明:
设环
那么,有:
-
x \oplus z = 1 -
y \oplus z = 1 -
x \oplus y = 1
(对应了环
将这三个式子一起异或起来,就可以得到
所以说,每次询问,就相当于询问,加上这条边后,会不会生成一个新环,且这个环与已经存在的环有着公共边。
还有一个性质:
如果我们把所有边离线下来,按照顺序加边,如果当前这条边不会生成一个环,那么它一定是可以加入的。
证明:
因为只有加上这条边后,会生成一个新环,且这个环与已经存在的环有着公共边,才会不能加,故在不生成环的情况下,是可以随便加边的。(也可以感性理解下)
所以我们就可以先按照第二个性质建出一棵树来,然后对于非树边一个一个判断。
于是,我们对于每一条可以加入的非树边,就对
看到是对树上的路径操作就很容易的想到了树剖。
至于题目要求是对边权进行加,我们可以把边权转换到点上:点
然后就可以码码码了
我想练练手,所以代码里判断异或和就单独写了个倍增,没有再在树剖里搞,导致巨大多常数,1497ms
Code: 5130 Byte
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
const int mxn=5e5+5;
int n,q,root;
int val[mxn];
int bg[mxn],ed[mxn],top[mxn],sz[mxn],dep[mxn],dfsc;
int pa[mxn],ord[mxn],P[mxn][22],xo[mxn][22];
vector<int>g[mxn];
int par_val[mxn];
inline int dfs1(int x,int par=0,int deep=1){ //树剖基操
dep[x]=deep;
sz[x]=1;pa[x]=par;
P[x][0]=par;
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==par)continue;
sz[x]+=dfs1(y,x,deep+1);
}
return sz[x];
}
inline void dfs(int x,int tpf=root,int par=0){
bg[x]=++dfsc;
top[x]=tpf;
ord[dfsc]=x;
int mx=-1,pos;
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==par)continue;
if(sz[y]>mx){
mx=sz[y];
pos=y;
}
}
if(mx==-1){
ed[x]=dfsc;
return;
}
dfs(pos,tpf,x);
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==par or y==pos)continue;
dfs(y,y,x);
}
ed[x]=dfsc;
}
struct segt{ //线段树维护树点权
int sum[mxn<<3];
int laz[mxn<<3];
int siz[mxn<<3];
inline void init(){
memset(siz,0,sizeof(siz));
memset(laz,0,sizeof(laz));
memset(sum,0,sizeof(sum));
}
inline void pushup(int x){sum[x]=sum[x<<1]+sum[x<<1|1];}
inline void pushdown(int x){
if(laz[x]){
sum[x]+=1ll*laz[x]*siz[x];
laz[x<<1]+=laz[x];
laz[x<<1|1]+=laz[x];
laz[x]=0;
}
}
inline void build(int x,int l,int r){
siz[x]=r-l+1;
if(l==r){
sum[x]=val[ord[l]];
return;
}
int md=l+r>>1;
build(x<<1,l,md);
build(x<<1|1,md+1,r);
pushup(x);
}
inline void add(int x,int l,int r,int a,int b,int d){
pushdown(x);
if(a<=l and r<=b){
laz[x]+=d;
pushdown(x);
return;
}
if(r<a or b<l)return;
int md=l+r>>1;
add(x<<1,l,md,a,b,d);
add(x<<1|1,md+1,r,a,b,d);
pushup(x);
}
inline int ask(int x,int l,int r,int a,int b){
pushdown(x);
if(a<=l and r<=b)return sum[x];
if(r<a or b<l)return 0;
int md=l+r>>1;
return ask(x<<1,l,md,a,b)+ask(x<<1|1,md+1,r,a,b);
}
}seg;
inline void updRange(int x,int y,int d){ //路径修改
for(;top[x]!=top[y];){
if(dep[top[x]]>dep[top[y]])swap(x,y);
seg.add(1,1,n,bg[top[y]],bg[y],d);
y=top[y];
y=pa[y];
}
if(dep[x]>dep[y])swap(x,y);
seg.add(1,1,n,bg[x],bg[y],d);
}
inline int qryRange(int x,int y){ //路径查询
int ans=0;
for(;top[x]!=top[y];){
if(dep[top[x]]>dep[top[y]])swap(x,y);
ans+=seg.ask(1,1,n,bg[top[y]],bg[y]);
y=top[y];
y=pa[y];
}
if(dep[x]>dep[y])swap(x,y);
ans+=seg.ask(1,1,n,bg[x],bg[y]);
return ans;
}
inline void addSub(int x,int d){ //闲着无聊把子树和也写了下,但没用
seg.add(1,1,n,bg[x],ed[x],d);
}
inline int qrySub(int x){return seg.ask(1,1,n,bg[x],ed[x]);}
struct dsu{ //并查集,用来离线后把所有树边提前加上
int fa[mxn];
inline void init(){for(int i=1;i<mxn;++i)fa[i]=i;}
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline int merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return 0;
fa[x]=y;
return 1;
}
}d;
int u[mxn],v[mxn],w[mxn];
int ans[mxn];
int roots[mxn];
inline void dfs_xor(int x,int P=0){ //写的倍增处理异或
xo[x][0]=par_val[x];
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(y==P)continue;
dfs_xor(y,x);
}
}
inline void init_xor(){
for(int j=1;j<=20;++j){
for(int i=1;i<=n;++i){
P[i][j]=P[P[i][j-1]][j-1];
xo[i][j]=xo[i][j-1]^xo[P[i][j-1]][j-1];
}
}
}
inline int qryXor(int x,int y){
int rt=0;
if(dep[x]>dep[y])swap(x,y);
for(int i=20;~i;--i)if(dep[P[y][i]]>=dep[x])rt^=xo[y][i],y=P[y][i];
for(int i=20;~i;--i)if(P[x][i]!=P[y][i])rt^=xo[x][i],rt^=xo[y][i],x=P[x][i],y=P[y][i];
if(x!=y){
rt^=xo[x][0],rt^=xo[y][0];
x=P[x][0],y=P[y][0];
}
return rt;
}
inline int lca(int x,int y){ //又用倍增写了个LCA
if(dep[x]>dep[y])swap(x,y);
for(int i=20;~i;--i)if(dep[P[y][i]]>=dep[x])y=P[y][i];
for(int i=20;~i;--i)if(P[x][i]!=P[y][i])x=P[x][i],y=P[y][i];
if(x!=y)x=P[x][0],y=P[y][0];
return x;
}
inline void solve(){
cin>>n>>q;
d.init();
for(int i=1;i<=q;++i){ //处理树边
cin>>u[i]>>v[i]>>w[i];
ans[i]=d.merge(u[i],v[i]);
if(ans[i])g[u[i]].push_back(v[i]),g[v[i]].push_back(u[i]);
}
for(int i=1;i<=n;++i)if(!top[i]){ //可能会有多个边
roots[i]=1;
dfs1(i);
dfs(i,i);
}
for(int i=1;i<=q;++i){
if(!ans[i])continue;
if(u[i]==pa[v[i]])par_val[v[i]]=w[i];
else par_val[u[i]]=w[i];
}
for(int i=1;i<=n;++i)
if(roots[i])
dfs_xor(i);
init_xor();
seg.build(1,1,n);
for(int i=1;i<=q;++i){ //边权转点权
if(ans[i]){
if(u[i]==pa[v[i]])val[v[i]]=w[i];
else val[u[i]]=w[i];
}
}
for(int i=1;i<=q;++i){
if(ans[i])continue;
int X=qryXor(u[i],v[i])^w[i];
if(X==0)continue;
int LCA=lca(u[i],v[i]);
// cerr<<u[i]<<' '<<v[i]<<' '<<w[i]<<'\n';
// for(int i=1;i<=n;++i)cerr<<qryRange(i,i)<<' ';cerr<<'\n';
int t=qryRange(u[i],v[i])-qryRange(LCA,LCA); //一定要减掉LCA处的点权 因为它对应的是LCA到LCA的父亲这条边的边权,不在路径上
// cerr<<"??? "<<qryRange(u[i],v[i])<<' '<<qryRange(u[i],u[i])<<' '<<qryRange(v[i],v[i])<<' '<<qryRange(LCA,LCA)<<'\n';
if(t!=0);
else{
ans[i]=1;
updRange(u[i],v[i],1); //同理
updRange(LCA,LCA,-1);
}
}
for(int i=1;i<=q;++i){
if(ans[i])cout<<"YES\n";
else cout<<"NO\n";
}
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;T=1;
// cin>>T;
for(;T--;)solve();
return 0;
}