[51nod1558][CF468D]树中的配对

#题目大意
一颗n个节点的树,边有边权。
求一个字典序最小的排列p
使得 ∑ d i s ( i , p i ) \sum dis(i,pi) dis(i,pi)最小
#思考
∑ d i s ( i , p i ) = ∑ d e p i + d e p p i − 2 ∗ d e p l c a ( i , p i ) = 2 ∗ ∑ d e p i − 2 ∗ ∑ d e p l c a ( i , p i ) \sum dis(i,pi)=\sum dep_i+dep_{pi}-2*dep_{lca(i,pi)}=2*\sum dep_i-2*\sum dep_{lca(i,pi)} dis(i,pi)=depi+deppi2deplca(i,pi)=2depi2deplca(i,pi)
假如我们选定重心作为根,前面部分已经固定了,而显然可以使得后面部分为0。
接下来解决字典序最小的问题。
我们把root删去,剩余若干颗子树。
贪心选取,每次都希望给pi选一个最小的j。
但我们还得保证合法,即i和j不能在同一个子树里。
同时,还得保证之后存在合法解。
思考什么时候有合法解。
假设目前已经确定了排列的前i项,一个子树里有t个大于i的节点,这些节点对应配对一定还没找好,这个子树里还有x个未配对的节点,目前总共有n-i个未配对节点,不能配对同一个子树里的,因此实际可配对的有n-i-x个,还未配对的t个节点必须有配对。
所以 t + x &lt; = n − i t+x&lt;=n-i t+x<=ni
那么当一个子树出现 t + x = n − i t+x=n-i t+x=ni时,就很关键了,因为i在不断变大,因此右边会减小,如果左边不能随之减小,就会不合法。而可以知道每次一个子树t和x只可能其中一个减了1,因此这个等式其实就一直满足了!
所以我们用set维护所有子树的t+x,每次查看是否存在满足等式的子树,设该子树为p,那么接下来,如果i’在p子树内(t会减一),则可以正常做,否则,我们为其寻找的配对j’必须在p子树内(x会减一)。
找配对的话,可以用线段树维护,同一个子树内按编号从小到大放入一个序列中,每次指针往后移即可。
至于根是一个棘手的问题,根和任意都能配对,所以根要特殊考虑一下。
注意特判n=1。

#include<cstdio>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=100000+10;
struct dong{
	int x,y;
	friend bool operator <(dong a,dong b){
		return a.x<b.x||a.x==b.x&&a.y<b.y;
	}
} zlt;
multiset<dong> s;
multiset<dong>::iterator it;
int tree[maxn*4];
int h[maxn],go[maxn*2],dis[maxn*2],next[maxn*2],belong[maxn];
int a[maxn],L[maxn],R[maxn],zz[maxn],tt[maxn],xx[maxn],size[maxn],ans[maxn],id[maxn],dy[maxn];
ll dep[maxn];
int i,j,k,l,t,n,m,top,tot,cnt,root,czy,p,q;
bool pp;
ll now;
void add(int x,int y,int z){
	go[++tot]=y;
	dis[tot]=z;
	next[tot]=h[x];
	h[x]=tot;
}
void dg(int x,int y){
	int t=h[x];
	size[x]=1;
	while (t){
		if (go[t]!=y){
			dg(go[t],x);
			size[x]+=size[go[t]];
		}
		t=next[t];
	}
}
void dfs(int x,int y){
	int t=h[x];
	while (t){
		if (go[t]!=y){
			dep[go[t]]=dep[x]+(ll)dis[t];
			dfs(go[t],x);
		}
		t=next[t];
	}
}
void travel(int x,int y){
	R[belong[x]]=++top;
	a[top]=x;
	tt[belong[x]]++;xx[belong[x]]++;
	int t=h[x];
	while (t){
		if (go[t]!=y){
			belong[go[t]]=belong[x];
			travel(go[t],x);
		}
		t=next[t];
	}
}
void change(int p,int l,int r,int a,int b){
	if (l==r){
		tree[p]=b;
		return;
	}
	int mid=(l+r)/2;
	if (a<=mid) change(p*2,l,mid,a,b);else change(p*2+1,mid+1,r,a,b);
	tree[p]=min(tree[p*2],tree[p*2+1]);
}
int query(int p,int l,int r,int a,int b){
	if (a>b) return n+1;
	if (l==a&&r==b) return tree[p];
	int mid=(l+r)/2;
	if (b<=mid) return query(p*2,l,mid,a,b);
	else if (a>mid) return query(p*2+1,mid+1,r,a,b);
	else return min(query(p*2,l,mid,a,mid),query(p*2+1,mid+1,r,mid+1,b));
}
int main(){
	scanf("%d",&n);
	if (n==1){
		printf("0\n");
		printf("1\n");
		return 0;
	}
	fo(i,1,n-1){
		scanf("%d%d%d",&j,&k,&l);
		add(j,k,l);add(k,j,l);
	}
	dg(1,0);
	root=1;k=0;
	while (1){
		t=h[root];
		while (t){
			if (go[t]!=k&&size[go[t]]>n/2){
				k=root;
				root=go[t];
				break;
			}
			t=next[t];
		}
		if (!t) break;
	}
	dfs(root,0);
	fo(i,1,n) now+=dep[i];
	now*=2;
	t=h[root];
	while (t){
		id[go[t]]=++cnt;
		dy[cnt]=go[t];
		belong[go[t]]=go[t];
		L[go[t]]=zz[go[t]]=top+1;R[go[t]]=top;
		travel(go[t],root);
		sort(a+L[go[t]],a+R[go[t]]+1);
		t=next[t];
	}
	fo(i,1,cnt) change(1,1,cnt,i,a[L[dy[i]]]);
	fo(i,1,cnt){
		zlt.x=tt[dy[i]]+xx[dy[i]];zlt.y=dy[i];
		s.insert(zlt);
	}
	zlt.x=n;zlt.y=0;
	it=s.lower_bound(zlt);
	if (it!=s.end()&&(*it).x==n){
		czy=1;
		p=(*it).y;
	}
	pp=0;
	fo(i,1,n){
		if (!czy){
			zlt.x=n-i+1;zlt.y=0;
			it=s.lower_bound(zlt);
			if (it!=s.end()&&(*it).x==n-i+1){
				czy=1;
				p=(*it).y;
			}
		}
		if (i==root){
			if (!czy){
				j=tree[1];
				if (!pp) j=min(j,root);
				ans[i]=j;
				if (j==root){
					pp=1;
					continue;
				}
				l=belong[j];
				xx[l]--;
				zlt.x=tt[l]+xx[l]+1;zlt.y=l;
				s.erase(s.find(zlt));
				zlt.x--;
				s.insert(zlt);
				zz[l]++;
				if (zz[l]<=R[l]) change(1,1,cnt,id[l],a[zz[l]]);else change(1,1,cnt,id[l],n+1);
			}
			else{
				j=a[zz[p]++];
				ans[i]=j;
				if (zz[p]<=R[p]) change(1,1,cnt,id[p],a[zz[p]]);else change(1,1,cnt,id[p],n+1);
			}
			continue;
		}
		if (!czy){
			k=belong[i];
			tt[k]--;
			zlt.x=tt[k]+xx[k]+1;zlt.y=k;
			s.erase(s.find(zlt));
			zlt.x--;
			s.insert(zlt);
			j=min(query(1,1,cnt,1,id[k]-1),query(1,1,cnt,id[k]+1,cnt));
			if (!pp) j=min(j,root);
			ans[i]=j;
			if (j==root){
				pp=1;
				continue;
			}
			l=belong[j];
			xx[l]--;
			zlt.x=tt[l]+xx[l]+1;zlt.y=l;
			s.erase(s.find(zlt));
			zlt.x--;
			s.insert(zlt);
			zz[l]++;
			if (zz[l]<=R[l]) change(1,1,cnt,id[l],a[zz[l]]);else change(1,1,cnt,id[l],n+1);
		}
		else if (czy==1){
			k=belong[i];
			if (k==p){
				j=min(query(1,1,cnt,1,id[k]-1),query(1,1,cnt,id[k]+1,cnt));
				if (!pp) j=min(j,root);
				ans[i]=j;
				if (j==root){
					pp=1;
					continue;
				}
				l=belong[j];
				zz[l]++;
				if (zz[l]<=R[l]) change(1,1,cnt,id[l],a[zz[l]]);else change(1,1,cnt,id[l],n+1);
			}
			else{
				j=a[zz[p]++];
				ans[i]=j;
				if (zz[p]<=R[p]) change(1,1,cnt,id[p],a[zz[p]]);else change(1,1,cnt,id[p],n+1);
			}
		}
	}
	printf("%I64d\n",now);
	fo(i,1,n) printf("%d ",ans[i]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值