2024.3.8 【“草色烟光残照里,无言谁会凭阑意”】
Friday 正月二十八 妇女节
<BGM = “Light Above The Sky–Delos”>
P1084 NOIP2012 提高组 疫情控制
//2024.3.8
//by white_ice
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int oo=6e4;
int n,m,t;
int at,bt,ct;
int d[oo],p[oo],f[oo][20];
bool flag,vis[oo],need[oo];
ll ans,idx[oo],use[oo],dis[oo][20];
pair<ll,int> h[oo];
queue<int> q;
int v[oo<<1],w[oo<<1],nxt[oo<<1],head[oo],top;
void add(int x,int y,int z){v[++top]=y,w[top]=z,nxt[top]=head[x],head[x]=top;}
void bfs(){
q.push(1);
d[1]=1;
while(q.size()){
int x=q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=v[i];
if(d[y])
continue;
d[y]=d[x]+1;
f[y][0]=x,dis[y][0]=w[i];
for(int j=1;j<=t;j++){
f[y][j]=f[f[y][j-1]][j-1];
dis[y][j]=dis[y][j-1]+dis[f[y][j-1]][j-1];
}
q.push(y);
}
}
}
bool dfs(int x){
bool pson=0;
if(vis[x])
return 1;
for(int i=head[x];i;i=nxt[i]){
int y=v[i];
if(d[y]<d[x])
continue;
pson=1;
if(!dfs(y))
return 0;
}
if(!pson)
return 0;
return 1;
}
bool check(ll lim){
memset(vis,0,sizeof(vis));
memset(idx,0,sizeof(idx));
memset(use,0,sizeof(use));
memset(h,0,sizeof(h));
memset(need,0,sizeof(need));
at=0,bt=0,ct=0;
for(int i=1;i<=m;i++){
ll x=p[i],cnt=0;
for(int j=t;j>=0;j--)
if(f[x][j]>1 && cnt+dis[x][j]<=lim){
cnt+=dis[x][j];
x=f[x][j];
}
if(f[x][0]==1 && cnt+dis[x][0]<=lim)
h[++ct]=make_pair(lim-cnt-dis[x][0],x);
else vis[x]=1;
}
for(int i=head[1];i;i=nxt[i])
if(!dfs(v[i]))
need[v[i]]=1;
sort(h+1,h+ct+1);
for(int i=1;i<=ct;i++)
if(need[h[i].second]&&h[i].first<dis[h[i].second][0])
need[h[i].second]=0;
else idx[++at]=h[i].first;
for(int i=head[1];i;i=nxt[i])
if(need[v[i]])
use[++bt]=dis[v[i]][0];
if(at<bt)
return 0;
sort(idx+1,idx+at+1);
sort(use+1,use+bt+1);
int i=1,j=1;
while(i<=bt && j<=at)
if(idx[j]>=use[i])
i++,j++;
else
j++;
if(i>bt)
return 1;
return 0;
}
int main(){
ll l=0,r=0,mid;
cin>>n;
t=log2(n)+1;
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
r+=z;
}
bfs();
cin>>m;
for(int i=1;i<=m;i++)
scanf("%d",&p[i]);
while(l<=r){
mid=(l+r)>>1;
if(check(mid)){
r=mid-1;
ans=mid;
flag=1;
}
else l=mid+1;
}
if(!flag) cout<<-1;
else cout<<ans;
return 0;
}
从题目可以看出,这是一道十分明显的树上问题,将特定点上的棋子沿路径移动,使根节点(1号节点)与任何叶子节点之间都没有联通路,将运动棋子的时间规定为路径长度,多个棋子可同时移动,求达到目标的最短耗时。
先思考最终答案求解,不难发现对于一个时间,如果比它短的可以实现,那么它也必然可以实现,所以可以通过食用二分的方法求解答案我不喜欢二分!
这是二分过程:
while(l<=r){
mid=(l+r)>>1;
if(check(mid)){
r=mid-1;
ans=mid;
flag=1;
}
else l=mid+1;
}
if(!flag) cout<<-1;
else cout<<ans;
下面我们对树的性质进行考虑,对于一棵树,我们可以让棋子的位置尽量高(靠近根)以实现覆盖更多叶子节点,所以等时间下,我们只需要考虑棋子向上移动就可以了。
那我们对于每一个棋子,在二分过程中,只要让它上升到最大高度,如果到了根节点的子节点就停下就好了呀。
但是我们很快会发现,对于一颗本来没有棋子的子树,是不能通过上移实现目标的,所以需要从其他子树“借”棋子。
通过上移后,对已经实现覆盖的子节点进行记录,再移动最近的一颗棋子到未覆盖的子树上就可以了
然后我就止步于此了。。。。
在仔细研读大佬的文章后,我得到了一个十分好用的方法:
记录下每一个棋子移动后的剩余时间,不需要考虑棋子所在的位置,只要时间上足够就可以。
以下是dfs求可行性过程:
bool dfs(int x){
bool pson=0;
if(vis[x])
return 1;
for(int i=head[x];i;i=nxt[i]){
int y=v[i];
if(d[y]<d[x])
continue;
pson=1;
if(!dfs(y))
return 0;
}
if(!pson)
return 0;
return 1;
}
同时我们不难看出,使棋子向上移动,是十分浪费时间的,我们便可以使用这个:
倍增!
通过bfs预处理祖先距离,使用倍增存储就会显得时间十分充裕了。
void bfs(){
q.push(1);
d[1]=1;
while(q.size()){
int x=q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=v[i];
if(d[y])
continue;
d[y]=d[x]+1;
f[y][0]=x,dis[y][0]=w[i];
for(int j=1;j<=t;j++){
f[y][j]=f[f[y][j-1]][j-1];
dis[y][j]=dis[y][j-1]+dis[f[y][j-1]][j-1];
}
q.push(y);
}
}
}