5433. 【NOIP2017提高A组集训10.28】图

题目

题目大意

给你一个无向连通图,有两种边,为\(k-x\)\(k+x\)的形式。
有一堆询问,问\(x\)为某个值的时候的最小生成树。


思考历程

有过一些猜想,但被自己推翻了。
没有想出来,于是只能打暴力。


正解

首先有个比赛时就想到的一个很显然的结论:
肯定是正边和负边分别做一次最小生成树,然后用这些树边来生成新的最小生成树。
当时我是这么想的:可以先对正边做一次最小生成树,然后一条一条负边加进去,可以用\(LCT\)维护。最终形成的一定是最优的。所以只有正边的最小生成树的边有意义。反过来,只有负边的最小生成树的边有意义。
于是就只有两棵树的边有意义。

所以就先做一遍最小生成树。然后对正边建立\(LCT\)
将负边排序,一个一个加进去。每次替换路径上权值最大的正边,并且记录替换的时候的\(x\)的下界。
搞完之后将\(x\)的下界排个序,就可以得到各个时段的最小生成树。

为什么是正确的呢?
如果有两条边,它们端点覆盖的路径的最大边是相同的(在没有进行任何加边操作和删边操作的时候)。很显然,更小的那个会优先替代(时间上也是它更小),而更大的那个会替代路径上其它的比较大的边。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
#define M 200010
#define INF 2000000000
#define ll long long
int n,A,B;
struct edge{
	int u,v,len;
} ea[M],eb[M];
bool cmpe(const edge &a,const edge &b){return a.len<b.len;}
int ufs[N];
int getufs(int x){return ufs[x]==x?x:ufs[x]=getufs(ufs[x]);}
inline void init(edge *e,int &m){
	for (int i=1;i<=m;++i)
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].len);
	sort(e+1,e+m+1,cmpe);
	int tmp=0;
	for (int i=1;i<=n;++i)
		ufs[i]=i;
	for (int i=1;i<=m;++i){
		int x=getufs(e[i].u),y=getufs(e[i].v);
		if (x!=y){
			ufs[x]=y;
			e[++tmp]=e[i];
		}
	}
	m=tmp;
}
struct Node *null;
struct Node{
	Node *fa,*c[2];
	bool rev,isr;
	int len;
	Node *mx;
	inline void rvs(){swap(c[0],c[1]),rev^=1;}
	inline void push(){if (!isr) fa->push();if (rev) rev=0,c[0]->rvs(),c[1]->rvs();}
	inline bool getson(){return fa->c[0]!=this;}
	inline void upd(){mx=(c[0]->mx->len>c[1]->mx->len?c[0]->mx:c[1]->mx),mx=(mx->len<len?this:mx);}
	inline void rotate(){
		Node *y=fa;
		if (y->isr)
			y->isr=0,isr=1;
		else
			y->fa->c[y->getson()]=this;
		bool k=getson();
		fa=y->fa;
		y->c[k]=c[!k],c[!k]->fa=y;
		c[!k]=y,y->fa=this;
		mx=y->mx,y->upd();
	}
	inline void splay(){
		push();
		for (;!isr;rotate())
			if (!fa->isr)
				getson()!=fa->getson()?rotate():fa->rotate();
	}
	inline Node *access(){
		Node *x=this,*y=null;
		for (;x!=null;y=x,x=x->fa){
			x->splay();
			x->c[1]->isr=1;
			y->isr=0;
			x->c[1]=y;
			x->upd();
		}
		return y;
	}
	inline Node *mroot(){
		Node *res=access();
		res->rvs();
		return res;
	}
	inline void link(Node *y){y->mroot()->fa=this;}
} d[N],eda[N],edb[N];
int tim[N],con[N],p[N];
bool cmpp(int a,int b){return tim[a]<tim[b];}
struct Query{
	int x,num;
} q[N];
bool cmpq(Query a,Query b){return a.x<b.x;}
ll ans[N];
int main(){
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	int Q;
	scanf("%d%d%d%d",&n,&A,&B,&Q);
	init(ea,A),init(eb,B);
	null=new Node;
	*null={null,null,null,0,0,-INF,null};
	for (int i=1;i<=n;++i)
		d[i]={null,null,null,0,1,-INF,null};
	for (int i=1;i<=A;++i){
		eda[i]={null,null,null,0,1,ea[i].len,&eda[i]};
		d[ea[i].u].link(&eda[i]);
		d[ea[i].v].link(&eda[i]);
	}
	ll sum=0;
	for (int i=1;i<=A;++i)
		sum+=ea[i].len;
	tim[0]=-INF;
	con[0]=0;
	for (int i=1;i<=B;++i){
		p[i]=i;
		int u=eb[i].u,v=eb[i].v;
		d[u].mroot();
		Node *t=d[v].access();
		if (t->mx->len==-INF){
			tim[i]=INF;
			continue;
		}
		tim[i]=(eb[i].len-t->mx->len-1>>1)+1;
		con[i]=eb[i].len-t->mx->len;
		t=t->mx;
		d[ea[t-eda].u].mroot();
		d[ea[t-eda].v].access();
		t->splay();
		t->c[0]->isr=t->c[1]->isr=1;
		t->c[0]->fa=t->c[1]->fa=null;
		edb[i]={null,null,null,0,1,-INF,null};
		edb[i].link(&d[u]);
		edb[i].link(&d[v]);
	}
	sort(p+1,p+B+1,cmpp);
	for (int i=1;i<=Q;++i)
		scanf("%d",&q[i].x),q[i].num=i;
	sort(q+1,q+Q+1,cmpq);
	for (int i=1,j=1,k=n-1;i<=Q;++i){
		for (;j<=B && tim[p[j]]<=q[i].x;++j){
			sum+=con[p[j]];
			k-=2;
		}
		ans[q[i].num]=sum+(ll)k*q[i].x;
	}
	for (int i=1;i<=Q;++i)
		printf("%lld\n",ans[i]);
	return 0;
}

总结

有了结论,一切都好说……
我一开始就应该猜结论,拍验证……

posted @ 2019-09-24 17:34  jz_597  阅读(156)  评论(0编辑  收藏  举报