CF603E Pastoral Oddities
一、题目
二、解法
挺开心的,这道题基本上是自己想出来的 \(\tt ovo\)
首先有一个基本的 \(\tt observation\):翻转一条路径的选取情况,可以只改变路径端点的度数奇偶性。所以问题转化成把这些点两两配对使得每对之间都联通,显然它的充要条件是每个连通块大小都为偶数。
上述结论还有另一种导出方式,因为选取一条边对度数的贡献是 \(2\),所以原问题的必要条件是每个连通块大小为偶数。然后考虑在这个基础上给出构造,考虑原图的 \(\tt dfs\) 树,可以从叶子开始考虑,如果它的度数为偶数就保留父边。最后除了根的所有点度数都是奇数,而总度数贡献是偶数,所以根的度数也是奇数。
考虑加边不会让合法性变差,因为本题要求最小化最大边权,而合法性关于边是有单调性的,所以我们可以尝试使用均摊法。每次新加边的时候考虑替换环上的最大边(类似最小生成树动态维护),输出答案之前尝试删除现有的最大边。
可以用 \(\tt lct\) 维护这个过程,需要用到维护虚子树信息的技巧(在 \(\tt access/link\) 虚实切换的时候修改),因为每条边最多被删除一次所以时间复杂度 \(O(n\log n)\)
还有一种方法是因为答案单调可以考虑决策性单调分治,我们考虑不进行删除操作使用并查集维护。
在计算答案在 \([x,y]\) 的区间 \([l,r]\),我们需要保证位置 \(<l\) 并且权值 \(<x\) 的边都已经被加入,这样在计算 \(mid\) 的答案时可以先暴力加入位置 \(\leq mid\) 并且权值 \(<x\) 的边,然后按权值大小加入位置 \(\leq mid\) 的边,当第一次合法的时候就得到了答案。
为了维护那些需要被提前加入的边,我们在递归下去的时候(偏序关系变化了)新加一些边即可,回溯的时候再回退掉多加入的边,所以使用可撤销并查集,时间复杂度 \(O(n\log^2n)\),但是比 \(\tt lct\) 快了一倍。
//link-cut-tree
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 400005;
#define ls ch[x][0]
#define rs ch[x][1]
#define pii pair<int,int>
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x<=9)
{
putchar(x+'0');
return ;
}
write(x/10);
putchar(x%10+'0');
}
int n,m,ch[M][2],fa[M],fl[M],siz[M],ss[M],pos[M];
int cnt,a[M],b[M],c[M],st[M],out[M];priority_queue<pii> q;
int nrt(int x)
{
return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
int chk(int x)
{
return ch[fa[x]][1]==x;
}
void flip(int x)
{
if(!x) return ;
swap(ch[x][0],ch[x][1]);fl[x]^=1;
}
void up(int x)
{
siz[x]=siz[ls]+siz[rs]+(x<=n)+ss[x];
int tmp=c[pos[ls]]>c[pos[rs]]?pos[ls]:pos[rs];
pos[x]=tmp;if(x>n) pos[x]=c[tmp]>c[x-n]?tmp:x-n;
}
void down(int x)
{
if(!fl[x]) return ;
flip(ls);flip(rs);fl[x]=0;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;fa[w]=y;
if(nrt(y)) ch[z][chk(y)]=x;fa[x]=z;
ch[x][k^1]=y;fa[y]=x;
up(y);up(x);
}
void splay(int x)
{
int y=x,z=0;st[++z]=y;
while(nrt(y)) st[++z]=y=fa[y];
while(z) down(st[z--]);
while(nrt(x))
{
y=fa[x];
if(nrt(y))
{
if(chk(x)==chk(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);ss[x]+=siz[rs];
ss[x]-=siz[rs=y];up(x);
}
}
void makert(int x)
{
access(x);splay(x);flip(x);
}
int findrt(int x)
{
access(x);splay(x);
while(ls) down(x),x=ls;
splay(x);return x;
}
void split(int x,int y)
{
makert(x);access(y);splay(y);
}
void link(int x,int y)
{
makert(x);makert(y);
cnt-=siz[x]&1;cnt-=siz[y]&1;
fa[x]=y;ss[y]+=siz[x];up(y);
cnt+=siz[y]&1;
}
void cut(int x,int y)
{
split(x,y);cnt-=siz[y]&1;
ch[y][0]=fa[x]=0;up(y);
cnt+=siz[x]&1;cnt+=siz[y]&1;
}
int zxy(int i)
{
int x=a[i],y=b[i],z=c[i],ok=1;
if(findrt(x)==findrt(y))
{
split(x,y);
int o=pos[y];
if(c[o]>z)
{
cut(a[o],o+n);
cut(b[o],o+n);
out[o]=1;
}
else ok=0;
}
if(ok)
{
pos[i+n]=i;
link(x,i+n);link(y,i+n);
q.push(make_pair(z,i));
}
if(cnt) return -1;
while(!q.empty())
{
int o=q.top().second;q.pop();
if(out[o]) continue;
cut(a[o],o+n);cut(b[o],o+n);
if(cnt)//become illegal
{
link(a[o],o+n),link(b[o],o+n);
q.push(make_pair(c[o],o));
return c[o];
}
}
return 0;
}
signed main()
{
cnt=n=read();m=read();
for(int i=1;i<=n;i++) siz[i]=1;
for(int i=1;i<=m;i++)
{
a[i]=read();b[i]=read();c[i]=read();
write(zxy(i)),puts("");
}
}
//divide and conquer optimize
#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 300005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans[M],rk[M],cnt,top,fa[M],siz[M],st[M];
struct node
{
int u,v,c,id;
bool operator < (const node &b) const
{
return c<b.c;
}
}e[M],E[M];
int find(int x)
{
return x==fa[x]?x:find(fa[x]);
}
void add(int u,int v)
{
int x=find(u),y=find(v);
if(x==y) return ;
cnt-=siz[x]&1;cnt-=siz[y]&1;
if(siz[x]>siz[y]) swap(x,y);
fa[x]=y;siz[y]+=siz[x];
cnt+=siz[y]&1;st[++top]=x;
}
void back()
{
int x=st[top--],y=fa[x];
cnt-=siz[y]&1;
fa[x]=x;siz[y]-=siz[x];
cnt+=siz[x]&1;cnt+=siz[y]&1;
}
void cdq(int l,int r,int x,int y)
{
if(l>r) return ;
int mid=(l+r)>>1,zxy=top;
for(int i=l;i<=mid;i++)
if(rk[i]<x) add(E[i].u,E[i].v);
for(int i=x;i<=y;i++)
{
if(e[i].id<=mid) add(e[i].u,e[i].v);
if(!cnt) {ans[mid]=i;break;}
}
while(top>zxy) back();
if(!ans[mid])
{
for(int i=l;i<=mid;i++)
if(rk[i]<x) add(E[i].u,E[i].v);
cdq(mid+1,r,x,y);
while(top>zxy) back();
return ;
}
for(int i=l;i<=mid;i++)
if(rk[i]<x) add(E[i].u,E[i].v);
cdq(mid+1,r,x,ans[mid]);
while(top>zxy) back();
for(int i=x;i<ans[mid];i++)
if(e[i].id<l) add(e[i].u,e[i].v);
cdq(l,mid-1,ans[mid],y);
while(top>zxy) back();
}
signed main()
{
cnt=n=read();m=read();
for(int i=1;i<=n;i++) siz[i]=1,fa[i]=i;
for(int i=1;i<=m;i++)
{
e[i].u=read(),e[i].v=read(),e[i].c=read();
e[i].id=i;E[i]=e[i];
}
sort(e+1,e+1+m);
for(int i=1;i<=m;i++) rk[e[i].id]=i;
cdq(1,m,1,m);
for(int i=1;i<=m;i++)
printf("%d\n",(!ans[i])?-1:e[ans[i]].c);
}