[NOIP2015]运输计划

NOIP的神题......其难度与质量媲美之后一年的天天爱跑步......被奉为神题......

题意简述:给定一颗$N$个点边带权的树,再给定$M$条链,问将树上的某条边权值改为0后,这些链中最长的至少有多长。

由于答案显然具有单调性,因此可以考虑二分答案。

二分答案后就变成了这样一个问题:将一条边的边权变为0后,是否可以让所有m条路径中每条的权值都小于$mid$;

这个问题乍一看还是没啥思路,不妨更加深入的分情况来看。

对于权值和小于$mid$的链,怎么改他也是符合条件的,因此可以不考虑。

对于权值和大于$mid$的链,由于只能修改一条边,因此我们必须找到一条边使得其为所有链长大于$mid$的指定链的公共部分。

光是公共部分还不够,还需要有长度限制。设$m$条链中长度最长的为$mx$,则我们找到的边必须要大于等于$mx-mid$

对于第二个问题十分好解决,直接看这条边的边权即可。

对于第一个问题,我们使用树上差分的方式,将每条边被覆盖的次数进行统计,此外,我们可以提前处理出来所有链的$LCA$,这样可以省去许多时间

参考代码如下:

  1 #pragma GCC optinize(3)
  2 #include<iostream>
  3 #include<cstdio>
  4 #include<cstring>
  5 #define N 600005
  6 using namespace std;
  7 int read()
  8 {
  9     int x=0,f=1;char ch=getchar();
 10     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 11     while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
 12     return x*f;
 13 }
 14 int n,m,v[N],w[N],head[N],nxt[N],cnt,fa[N][22],dep[N],maxn,_maxn,s[N],t[N],dist[N],lca[N],distt[N];
 15 int cf[N],scf[N];
 16 void dfs(int x,int f,int dis)
 17 {
 18     dep[x]=dep[f]+1;
 19     distt[x]=dis;
 20     for(int i=0;i<=19;i++)fa[x][i+1]=fa[fa[x][i]][i];
 21     for(int i=head[x];i;i=nxt[i])
 22     {
 23         if(v[i]==f)continue;
 24         fa[v[i]][0]=x;
 25         dfs(v[i],x,dis+w[i]);
 26     }
 27 }
 28 void dfs1(int x,int f)
 29 {
 30     scf[x]=cf[x];
 31     for(int i=head[x];i;i=nxt[i])
 32     {
 33         if(v[i]==f)continue;
 34         dfs1(v[i],x);
 35         scf[x]+=scf[v[i]];
 36     }
 37 }
 38 bool dfs2(int x,int f,int c,int len)
 39 {
 40     for(int i=head[x];i;i=nxt[i])
 41     {
 42         if(v[i]==f)continue;
 43         if(scf[v[i]]==c&&w[i]>=len)return 1;
 44         if(dfs2(v[i],x,c,len))return 1;
 45     }
 46     return 0;
 47 }
 48 int LCA(int x,int y)
 49 {
 50     if(dep[x]<dep[y])swap(x,y);
 51     for(int i=20;i>=0;i--)
 52     {
 53         if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
 54         if(x==y)return x;
 55     }
 56     for(int i=20;i>=0;i--)
 57     {
 58         if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
 59     }
 60     return fa[x][0];
 61 }
 62 void add(int a,int b,int c)
 63 {
 64     v[++cnt]=b;
 65     w[cnt]=c;
 66     nxt[cnt]=head[a];
 67     head[a]=cnt;
 68 }
 69 bool check(int x)
 70 {
 71     if(x>=_maxn)return 1;
 72     memset(cf,0,sizeof(cf));
 73     memset(scf,0,sizeof(scf));
 74     int _cnt=0;
 75     for(int i=1;i<=m;i++)
 76     {
 77         if(dist[i]>x)
 78         {
 79             _cnt++;
 80             cf[s[i]]++;cf[t[i]]++;
 81             cf[lca[i]]-=2;
 82         }
 83     }
 84     dfs1(1,0);
 85     return dfs2(1,0,_cnt,_maxn-x);
 86 }
 87 int main()
 88 {
 89     n=read();m=read();
 90     for(int x,y,z,i=1;i<n;i++)
 91     {
 92         x=read();y=read();z=read();
 93         maxn=max(maxn,z);
 94         add(x,y,z);add(y,x,z);
 95     }
 96     dfs(1,0,0);
 97     for(int i=1;i<=m;i++)
 98     {
 99         s[i]=read();t[i]=read();
100         lca[i]=LCA(s[i],t[i]);
101         dist[i]=distt[s[i]]+distt[t[i]]-2*distt[lca[i]]; 
102         _maxn=max(_maxn,dist[i]);
103     }
104     int l=_maxn-maxn,r=_maxn+1,mid;
105     while(l<=r)
106     {
107         mid=(l+r)>>1;
108         if(check(mid))r=mid-1;
109         else l=mid+1; 
110     }
111     printf("%d\n",l);
112     return 0;
113 }
[NOIP2015]运输计划

 

转载于:https://www.cnblogs.com/szmssf/p/11604774.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值