[NOIP2012]疫情控制
详细的注释已经写到了代码里面。
以后这种码量多的最好都写成函数再调用,确定好每个函数的作用。
然后变量名最好也是有实际意义的qwq
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define MAXN 500010
using namespace std;
int n,m,edge_number,cnta,cntb;
int head[MAXN],f[MAXN][33],dist[MAXN][33];
int pos[MAXN],done[MAXN],used[MAXN],rest_node[MAXN],rest_dis[MAXN];
long long l,r;
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
struct Node{int id,rest;}node[MAXN],b[MAXN],a[MAXN];
bool cmp(struct Node x,struct Node y)
{
if(x.rest>y.rest) return 1;
else return 0;
}
//按照到根节点之后的剩余路程从大到小排序
inline void add(int from,int to,int dis)
{
edge[++edge_number].dis=dis;
edge[edge_number].to=to;
edge[edge_number].nxt=head[from];
head[from]=edge_number;
}
inline void init(int x,int pre,long long disdis)
{
f[x][0]=pre;
dist[x][0]=disdis;
for(int k=1;k<=31;k++)
{
f[x][k]=f[f[x][k-1]][k-1];
dist[x][k]=dist[f[x][k-1]][k-1]+dist[x][k-1];
//f数组处理的是从x开始跳2^j个跳到的节点
//dist数组处理的是从x开始跳,跳2^j跳的距离
}
for(int i=head[x];i;i=edge[i].nxt)
{
if(edge[i].to==pre) continue;
init(edge[i].to,x,edge[i].dis);
//继续向下搜索
}
}
//预处理倍增,没什么好说的
//一个子树如果每个叶子节点(边境城市)到根节点的路径上都设立了检查点
//我们称它被封锁(blocked)
inline bool is_blocked(int x,int pre){
int son_all_blocked=1,i,not_leaf=0;
if(done[x]) return 1;
//如果这个点已经设立了检查点了,自然是没有被封锁掉的 ,直接return 1即可
for(i=head[x];i;i=edge[i].nxt)
{
if(edge[i].to==pre)continue;
//注意不要搜索到父亲节点上去
not_leaf=1;
//如果有子节点,那么它不叫边境城市
if(!is_blocked(edge[i].to,x))//如果有子节点没有被封锁
{
son_all_blocked=0;//那么就没有被完全封锁
if(x==1) b[++cntb].id=edge[i].to,b[cntb].rest=edge[i].dis;
else return 0;
}
}
if(!not_leaf) return 0;//如果它是边境城市,前面却没有返回1(就是设立检查点),那么它一定没有被封锁
return son_all_blocked;//之后如果完全被封锁了就返回1,没有被完全封锁就返回0
}
inline bool check(long long limit)
{
cntb=cnta=0;
long long sum=0;
memset(done,0,sizeof(done));
memset(rest_node,0,sizeof(rest_node));
memset(used,0,sizeof(used));
//每次二分check时记得初始化qwq
for(int i=1;i<=m;i++)
{
int x=pos[i];
sum=0;
for(int k=31;k>=0;k--)
if(f[x][k]>1&&sum+dist[x][k]<=limit)
sum+=dist[x][k],x=f[x][k];
//倍增加速跳的过程
//一般树上向上跳的过程都可以采用倍增思想进行加速
if(f[x][0]==1&&sum+dist[x][0]<=limit)
{
a[++cnta].id=i;
a[cnta].rest=limit-sum-dist[x][0];
//a数组记录的是可以到达根节点的节点
if(!rest_node[x]||a[cnta].rest<rest_dis[x])
{
rest_node[x]=i;
rest_dis[x]=limit-sum-dist[x][0];
}
//rest_node记录的是该子树中距离根节点最近的那个点
//rest_dis记录的是该子树中距离根节点最近的那个点到根节点的距离
}
else
done[x]=1;
}
if(is_blocked(1,0)) return 1;
//如果根节点被封锁了 那么就已经满足了条件
sort(a+1,a+1+cnta,cmp);
//我们按照到了根节点之后剩余可用距离从长到短进行排序
sort(b+1,b+1+cntb,cmp);
//我们按照和根节点从远到近的顺序进行排序
int now=1;
//从根节点开始跳
used[0]=1;
for(int i=1;i<=cntb;i++)
{
if(!used[rest_node[b[i].id]])
{
used[rest_node[b[i].id]]=1;
continue;
}
//rest_node里面记录的是该子树里面距离根节点最近的节点
//按照贪心的策略来看,明显是用子树中距离根节点最近的节点来封锁该子树是最佳选择
//如果没有(或者已经使用过该节点)
//因为我们b数组已经进行过排序,现在是从离根节点最远的开始遍历的
//所以显然我们选择到了根节点之后剩余距离最多的节点跳过来封锁该子树是最佳选择
while(now<=cnta&&(used[a[now].id]||a[now].rest<b[i].rest))
++now;
if(now>cnta) return 0;
//如果距离剩下最多的可用点也无法到达该子树中的根节点
//那么二分的距离过小,需要调整
used[a[now].id]=1;
//如果可以跳过来,记得记录该节点已经被使用过
}
return 1;
}
int main()
{
scanf("%d",&n);
for(register int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
r+=w;
}
init(1,0,0);
scanf("%d",&m);
for(register int i=1;i<=m;i++)
scanf("%d",&pos[i]);
while(l<r)
{
long long mid=(l+r)>>1;
if(!check(mid)) l=mid+1;
else r=mid;
}
printf("%lld\n",l);
return 0;
}