bzoj 5100: [POI2018]Plan metra 构造

题意

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

分析

先把1到n路径上的节点找出来。
这里分两种情况,一种是1到n有连边。这种情况特判一下就好。
另一种是1到n没有连边,那么就找出其路径上的点,然后对于其他每个点,很容易找到它可以挂在链上的哪个节点下。
然后就做完了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

const int N=500005;
const int inf=1000000000;

int n,d1[N],d2[N],cnt,a[N];
bool vis[N];
struct data{int x,y,w;}ans[N];

bool cmp(int x,int y)
{
    return d1[x]<d1[y];
}

bool check(int len)
{
    if (!len) return 0;
    for (int i=2;i<n;i++)
        if (abs(d1[i]-d2[i])!=len) return 0;
    puts("TAK");
    printf("%d %d %d\n",1,n,len);
    for (int i=2;i<n;i++)
        if (d1[i]<d2[i]) printf("%d %d %d\n",1,i,d1[i]);
        else printf("%d %d %d\n",i,n,d2[i]);
    return 1;
}

int main()
{
    scanf("%d",&n);
    if (n==2) {printf("TAK\n1 2 1");return 0;}
    for (int i=2;i<n;i++) scanf("%d",&d1[i]);
    for (int i=2;i<n;i++) scanf("%d",&d2[i]);
    if (check(abs(d1[2]-d2[2]))) return 0;
    int mn=inf;
    for (int i=2;i<n;i++)
        if (d1[i]+d2[i]<mn) mn=d1[i]+d2[i];
    int tot=0;a[++tot]=1;
    for (int i=2;i<n;i++)
        if (d1[i]+d2[i]==mn) a[++tot]=i,vis[i]=1;
    std::sort(a+1,a+tot+1,cmp);
    for (int i=1;i<tot;i++)
        if (d1[a[i]]==d1[a[i+1]]) {puts("NIE");return 0;}
    a[++tot]=n;
    d1[n]=d1[a[2]]+d2[a[2]];
    for (int i=2;i<n;i++)
    {
        if (vis[i]) continue;
        if ((d1[i]+d2[i]-d1[n])%2==1) {puts("NIE");return 0;}
        int dis=(d1[i]+d2[i]-d1[n])/2;
        int l=1,r=tot,p=0;
        while (l<=r)
        {
            int mid=(l+r)/2;
            if (d1[i]-dis==d1[a[mid]]) {p=mid;break;}
            if (d1[i]-dis<d1[a[mid]]) r=mid-1;
            else l=mid+1;
        }
        if (!p) {puts("NIE");return 0;}
        ans[++cnt]=(data){i,a[p],dis};
    }
    for (int i=1;i<tot;i++) ans[++cnt]=(data){a[i],a[i+1],d1[a[i+1]]-d1[a[i]]};
    puts("TAK");
    for (int i=1;i<=cnt;i++) printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].w);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值