我A紫题了耶
这题乍一看好像不难啊就一个树形DP啊最多加上LCA处理一下啊。。。想来想去不太行。
feelings:有一点是确定的,就是我们要让军队尽量离根节点近一点,这样子就可以让这个军队控制的节点更多。而答案就是用时最大的那个军队的时间,而我们要让这个最大值最小,所以可能是二分答案。
这题思路线比较长,从这两个点入手,大致分几个步骤,我一个一个写出。
“让军队向上移动”的“上提”操作,就很容易想到用倍增优化。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 为
i
i
i 往上
2
j
2^j
2j 个节点,容易知道
f
[
i
]
[
0
]
f[i][0]
f[i][0] 就是
i
i
i 的父亲。设
d
i
s
[
i
]
[
j
]
dis[i][j]
dis[i][j] 为
i
i
i 到
i
i
i 往上
2
j
2^j
2j 个节点的距离。由类似求LCA前的预处理那样子,递推式显然,用个DFS遍历很容易求出。不会的先去学倍增LCA先。
void dfs(int x,int fx,int d)//倍增预处理
{
f[x][0]=fx;
dis[x][0]=d;
for(int i=1;i<=16;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
dis[x][i]=dis[x][i-1]+dis[f[x][i-1]][i-1];
}
for(int i=hd[x];i;i=e[i].next)
{
if(e[i].to!=fx) dfs(e[i].to,x,e[i].w);
}
}
直接二分一个时间,作为本次每支军队移动的最大时间,判断行不行,然后再次二分。这一部分很简单。
int l=1,r=1000000;
while(l<=r)
{
int mid=(l+r)>>1;
if(pd(mid))
{
fff=1;//有解标记
ans=mid;
r=mid-1;
}
else l=mid+1;
}
枚举每一个军队,让ta尽量往根节点跳,不能跳到根节点,这个跟LCA跳是一样的,同时记录跳的过程中花费的代价,如果最后是有能力跳到根节点的就记录成“剩余军队”,否则直接在原地驻守。对于所有能跳到根节点的军队,记录所有从根节点的儿子跳过来的军队中的最小值,后面有用。
for(int i=1;i<=m;i++)
{
ll x=army[i],sum=0;
for(int j=18;j>=0;j--)//倍增跳
{
if(f[x][j]>1&&sum+dis[x][j]<=lim)
{
sum+=dis[x][j];
x=f[x][j];
}
}
if(f[x][0]==1&&lim-sum-dis[x][0]>=0)//如果能跳到根
{
rest[++rest_num].s=lim-sum-dis[x][0];
rest[rest_num].p=i;
if(!resnum[x]||rest[rest_num].s<resmin[x])//记录最小
{
resnum[x]=i;
resmin[x]=rest[rest_num].s;
}
}
else stationed[x]=1;//否则直接确定驻守
}
这个地方其实就是用DFS实现,如果该节点被封死,那就不用继续递归了,ta的子树也已经被封死。如果一个点是叶子并且没被驻守,直接返回0,如果不是根节点且自己没人驻守,那子树中只要有一条空隙(到达叶子结点的)就是不行的。我们只需要记录所有根的儿子当中哪个有空隙(emp结构体),因为显然我们用剩余的军队去补空隙的时候只会去到根的子节点,才用时最小,再往下跑没意义。
int checktree(int x,int fa)//判断有没有空隙并记录空隙
{
int ff=1,leaf=1;
if(stationed[x]) return 1;
for(int i=hd[x];i;i=e[i].next)
{
if(e[i].to==fa) continue;
leaf=0;//非叶子节点
if(!checktree(e[i].to,x))
{
ff=0;//有least一个空隙
if(x==1)
{
emp[++emp_num].p=e[i].to;//只记录根的子节点哪个有空隙
emp[emp_num].s=e[i].w;
}
else return 0;
}
}
if(leaf&&!stationed[x]) return 0;
else return ff;
}
我们将剩余军队按剩余时间从大到小排序(省枚举时间),空隙当中到根的距离也按大到小排序。显然剩余多的军队应该尽量去补“空隙”权值大的地方。枚举空隙,匹配军队给他并记录用过了,优先选本身就在那棵子树上的最小的那个点看能不能到,如果不能就暴力枚举军队看看哪个可以,只要有一个空隙每个军队都没法不上,就判断不成功。
/*--------------贪心转移军队---------------*/
sort(rest+1,rest+rest_num+1,cmp1);
sort(emp+1,emp+emp_num+1,cmp2);
v[0]=1;
int now=1;
for(int i=1;i<=emp_num;i++)
{
if(!v[resnum[emp[i].p]])
{
v[resnum[emp[i].p]]=1;
continue;
}
while(now<=rest_num&&(v[rest[now].p]||rest[now].s<emp[i].s)) now++;//枚举军队
if(now>rest_num) return 0;//都不行
else v[rest[now].p]=1;
}
return 1;
很考验码力啊
#include
#include
#include
#include
using namespace std;
typedef long long ll;
struct node
{
int to,next,w;
}e[100010];
struct donkey
{
ll s;int p;
}rest[100010];
struct godcat
{
ll s;int p;
}emp[100010];
int n,m,army[50010],fff;
int hd[100010],tot;
ll f[100010][20],dis[100010][20],ans;
int resnum[50010],resmin[50010];
int stationed[50010],v[50010];
ll emp_num,rest_num;
void add(int x,int y,int w)
{
e[++tot]=(node){y,hd[x],w};
hd[x]=tot;
}
int cmp1(donkey l,donkey r)
{
return l.s>r.s;
}
int cmp2(godcat l,godcat r)
{
return l.s>r.s;
}
void dfs(int x,int fx,int d)//倍增预处理
{
f[x][0]=fx;
dis[x][0]=d;
for(int i=1;i<=16;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
dis[x][i]=dis[x][i-1]+dis[f[x][i-1]][i-1];
}
for(int i=hd[x];i;i=e[i].next)
{
if(e[i].to!=fx) dfs(e[i].to,x,e[i].w);
}
}
int checktree(int x,int fa)//判断有没有空隙并记录空隙
{
int ff=1,leaf=1;
if(stationed[x]) return 1;
for(int i=hd[x];i;i=e[i].next)
{
if(e[i].to==fa) continue;
leaf=0;//非叶子节点
if(!checktree(e[i].to,x))
{
ff=0;//有least一个空隙
if(x==1)
{
emp[++emp_num].p=e[i].to;//只记录根的子节点哪个有空隙
emp[emp_num].s=e[i].w;
}
else return 0;
}
}
if(leaf&&!stationed[x]) return 0;
else return ff;
}
int pd(int lim)
{
rest_num=emp_num=0;
memset(resnum,0,sizeof(resnum));
memset(stationed,0,sizeof(stationed));
memset(v,0,sizeof(v));
for(int i=1;i<=m;i++)
{
ll x=army[i],sum=0;
for(int j=18;j>=0;j--)//倍增跳
{
if(f[x][j]>1&&sum+dis[x][j]<=lim)
{
sum+=dis[x][j];
x=f[x][j];
}
}
if(f[x][0]==1&&lim-sum-dis[x][0]>=0)//如果能跳到根
{
rest[++rest_num].s=lim-sum-dis[x][0];
rest[rest_num].p=i;
if(!resnum[x]||rest[rest_num].s<resmin[x])//记录最小
{
resnum[x]=i;
resmin[x]=rest[rest_num].s;
}
}
else stationed[x]=1;//否则直接确定驻守
}
if(checktree(1,0)) return 1;//找空隙并记录
/*--------------贪心转移军队---------------*/
sort(rest+1,rest+rest_num+1,cmp1);
sort(emp+1,emp+emp_num+1,cmp2);
v[0]=1;
int now=1;
for(int i=1;i<=emp_num;i++)
{
if(!v[resnum[emp[i].p]])
{
v[resnum[emp[i].p]]=1;
continue;
}
while(now<=rest_num&&(v[rest[now].p]||rest[now].s<emp[i].s)) now++;
if(now>rest_num) return 0;
else v[rest[now].p]=1;
}
return 1;
}
int main()
{
cin>>n;
for(int i=1;i<=n-1;i++)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
add(y,x,w);
}
cin>>m;
for(int i=1;i<=m;i++)
{
scanf("%d",&army[i]);
}
dfs(1,0,0);
/*------二分答案------*/
int l=1,r=1000000;
while(l<=r)
{
int mid=(l+r)>>1;
if(pd(mid))
{
fff=1;
ans=mid;
r=mid-1;
}
else l=mid+1;
}
if(fff) cout<<ans;
else cout<<-1;
return 0;
}