bzoj5100 [POI2018]Plan metra 构造+二分

2 篇文章 0 订阅

Description


有一棵n个点的无根树,每条边有一个正整数权值,表示长度,定义两点距离为在树上的最短路径的长度。
已知2到n-1每个点在树上与1和n的距离,请根据这些信息还原出这棵树。

第一行包含一个正整数n(2<=n<=500000),表示点数。
第二行包含n-2个正整数d(1,2),d(1,3),…,d(1,n-1),分别表示每个点到1的距离。
第三行包含n-2个正整数d(n,2),d(n,3),…,d(n,n-1),分别表示每个点到n的距离。
输入数据保证1<=d<=1000000。

若无解,输出NIE。
否则第一行输出TAK,接下来n-1行每行三个正整数u,v,c(1<=u,v<=n,1<=c<=1000000)
表示存在一条长度为c的连接u和v两点的树边。
若有多组解,输出任意一组。

Solution


考虑1到n这条链,设它的长度为mn,显然 m n = min ⁡ { d ( 1 , i ) + d ( i , n ) } mn=\min\left\{d(1,i)+d(i,n)\right\} mn=min{d(1,i)+d(i,n)}。证明的话可以考虑反证一下
那么把在这条链上的点抠出来,剩余的点我们依次判断。
对于点i,我们有 f = d ( 1 , i ) + d ( i , n ) − m n 2 f=\frac{d(1,i)+d(i,n)-mn}{2} f=2d(1,i)+d(i,n)mn,其中f表示i到链的距离,这样我们也就知道了与i相连的点到1的距离,可以二分出这个点来

当然还存在一种情况就是1和n直接相连,那么剩余的点 ∣ d ( 1 , i ) − d ( i , n ) ∣ |d(1,i)-d(i,n)| d(1,i)d(i,n)全部相等,其余点要么连向1要么连向n

无解的情况比较多,要细心。。

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fi first
#define se second

typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=500055;

struct pair {
	LL fi,se,id;
	bool operator <(const pair &b) const {
		return fi<b.fi;
	}
} p[N],v[N];

std:: vector <pair> ans;

bool vis[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void add_edge(LL x,LL y,LL w) {
	ans.push_back((pair) {x,y,w});
}

int main(void) {
	int n=read(),c=0; LL mn=1LL<<30;
	if (n==2) return 0&puts("TAK\n1 2 1");
	rep(i,2,n-1) p[i].fi=read();
	rep(i,2,n-1) p[i].se=read();
	rep(i,2,n-1) p[i].id=i;
	LL lxf=abs(p[2].fi-p[2].se);
	if (lxf) {
		bool flag=true;
		rep(i,3,n-1) {
			if (abs(p[i].fi-p[i].se)!=lxf) {
				flag=false;
				break;
			}
		}
		if (flag) {
			puts("TAK");
			printf("%d %d %lld\n", 1,n,lxf);
			rep(i,2,n-1) {
				if (p[i].fi<p[i].se) printf("%d %d %lld\n", 1,i,p[i].fi);
				else printf("%d %d %lld\n", n,i,p[i].se);
			}
			return 0;
		}
	}
	rep(i,2,n-1) mn=std:: min(mn,p[i].fi+p[i].se);
	rep(i,2,n-1) if (p[i].fi+p[i].se==mn) {
		v[++c]=p[i];
		vis[i]=true;
	}
	v[++c]=(pair) {0,mn,1};
	v[++c]=(pair) {mn,0,n};
	std:: sort(v+1,v+c+1);
	rep(i,2,c) {
		if (v[i-1].fi==v[i].fi) return 0&puts("NIE");
		add_edge(v[i-1].id,v[i].id,v[i].fi-v[i-1].fi);
	}
	rep(i,2,n-1) if (!vis[i]) {
		LL tmp=p[i].fi+p[i].se-mn;
		if (tmp&1) return 0&puts("NIE");
		tmp=p[i].fi-tmp/2;
		pair wjp=(pair) {tmp,0,0};
		int it=std:: lower_bound(v+1,v+c+1,wjp)-v;
		if (it>c) return 0&puts("NIE");
		if (v[it].fi!=tmp) return 0&puts("NIE");
		add_edge(v[it].id,i,p[i].fi-tmp);
	}
	puts("TAK");
	for (int i=0;i<ans.size();++i) {
		printf("%lld %lld %lld\n", ans[i].fi,ans[i].se,ans[i].id);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值