P4319 变换的道路 题解
很显然,题目要求每一天的最小生成树的边权和 +1。
天数很少,刚开始肯定会想到直接按时间顺着推,但其实仔细一想会发现有些边被弃掉之后想再找回来用是很困难的,所以顺着推是肯定不行的。
正解:动态树+线段树分治。
这种题一般都是可以对时间(或是询问等)建一个线段树,在每一个节点上记这个区间包含的边,然后利用标记永久化的思想来处理每一个时间点。这样最后线段树上会有 \(O(n\log n)\) 条边。
然后遍历整个线段树来得到答案。
具体实现:
每到一个节点时,当这个节点上的边比现在最小生成树上的边更优时,就替换掉它,否则不改变。可以用一个栈来记录每次操作的加边和删边,这样回溯的时候可以回退到原来的状态。因为有动态加边和删边操作,这里可以用 LCT 来维护最小生成树。至于如何用 LCT 维护最小生成树,可以去看看[WC2006]水管局长和[NOI2014]魔法森林这两题,都是 LCT 维护最小生成树的好题。
最后本题的时间复杂度为 \(O(n\log^2n)\)。
代码:
#include<cstdio>
#include<vector>
#define pc(x) putchar(x)
#define ll long long
#define ls (pos<<1)
#define rs (pos<<1|1)
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(ll x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
int n,m,top,cnt;ll ans[40000];
struct edge{int u,v;ll w;}e[200005];
struct STK{int id,op;}stk[200005];
int fa[200005],rev[200005],ch[200005][2],mx[200005];ll val[200005];
//LCT begin
inline void swap(int &x,int &y){x^=y;y^=x;x^=y;}
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushrev(int x){swap(ch[x][0],ch[x][1]);rev[x]^=1;}
inline void pushup(int x)
{
mx[x]=x;
if(ch[x][0]&&val[mx[x]]<val[mx[ch[x][0]]])mx[x]=mx[ch[x][0]];
if(ch[x][1]&&val[mx[x]]<val[mx[ch[x][1]]])mx[x]=mx[ch[x][1]];
}
inline void pushdown(int x)
{
if(rev[x])
{
if(ch[x][0])pushrev(ch[x][0]);
if(ch[x][1])pushrev(ch[x][1]);
}rev[x]=0;
}
inline void update(int x)
{
if(!isroot(x))update(fa[x]);
pushdown(x);
}
inline void rotate(int x)
{
int y=fa[x],z=fa[y];
int k=ch[y][1]==x,w=ch[x][k^1];
if(!isroot(y))ch[z][ch[z][1]==y]=x;
ch[x][k^1]=y;ch[y][k]=w;
if(w){fa[w]=y;}fa[y]=x;fa[x]=z;
pushup(y);
}
inline void splay(int x)
{
update(x);
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
rotate(x);
}pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
{splay(x);ch[x][1]=y;pushup(x);}
}
inline void makeroot(int x){access(x);splay(x);pushrev(x);}
inline void split(int x,int y){makeroot(x);access(y);splay(y);}
inline void link(int x,int y){makeroot(x);fa[x]=y;}
inline int findroot(int x)
{
access(x);splay(x);
while(ch[x][0])pushdown(x),x=ch[x][0];
splay(x);return x;
}
inline void cut(int x,int y)
{
makeroot(x);access(y);splay(x);
fa[y]=ch[x][1]=0;pushup(x);
}
//LCT end
ll sum;
vector<int>tr[200005];
inline void ret()
{
int op=stk[top].op,id=stk[top].id;
int u=e[id].u,v=e[id].v;top--;
if(op==0)cut(u,id),cut(v,id);
else link(u,id),link(v,id);
}
inline void modify(int pos,int l,int r,int L,int R,int id)
{
if(L<=l&&r<=R){tr[pos].push_back(id);return;}
int mid=(l+r)>>1;
if(L<=mid)modify(ls,l,mid,L,R,id);
if(R>mid)modify(rs,mid+1,r,L,R,id);
}
inline void solve(int pos,int l,int r)
{
int nowtop=top;ll nowsum=sum;
for(int i=0;i<(int)tr[pos].size();++i)
{
int id=tr[pos][i],u=e[id].u,v=e[id].v;
split(u,v);int mxid=mx[v];
if(val[mxid]<=val[id])continue;
stk[++top]=(STK){mxid,1};stk[++top]=(STK){id,0};
cut(e[mxid].u,mxid);cut(e[mxid].v,mxid);
link(u,id);link(v,id);sum=sum-val[mxid]+val[id];
}int mid=(l+r)>>1;
if(l==r)ans[l]=sum; else solve(ls,l,mid),solve(rs,mid+1,r);
sum=nowsum;while(top>nowtop)ret();
}
int main()
{
n=read();cnt=n;
for(int i=1;i<n;++i)
{
int u=read(),v=read(),w=read();
e[++cnt]=(edge){u,v,w};val[cnt]=w;
link(u,cnt);link(v,cnt);sum+=w;
}m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read(),w=read(),l=read(),r=read();
e[++cnt]=(edge){u,v,w};val[cnt]=w;
modify(1,1,32766,l,r,cnt);
}solve(1,1,32766);
for(int i=1;i<=32766;++i)write(ans[i]+1),pc('\n');
return 0;
}