1. 程式人生 > 其它 >P4319 變換的道路 題解

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;
}