Luogu1084 疫情控制
https://www.luogu.com.cn/problem/P1084
二分/倍增/贪心
阅读题面,很容易发现,答案具有可以二分的性质,所以我们首先二分答案
又有一个比较显然的结论,我们假设让一支军队在一个节点或其父亲(非\(1\))上,选择其父亲显然更优(可以覆盖更多叶子节点)
那我们就倍增跳呗!
问题是,对于一支跳到最远的祖先后,还有更多步数军队,它还有一个选择,就是穿过\(1\)去访问其他节点
由于我们不容易决策,姑且先把军队都堆积到最远的祖先上(跳不到的,在最远的位置建立检查站即可)
这里有个错误的想法,就是取堆积的点中剩余步数最小的军队,让该军队建立检查站
因为可以存在交叉建检查站更优的情况,因为拥有更多剩余步数的军队可以去更远的未覆盖的点建立检查站
我尝试了一下,居然可以水到\(80pts\),\(loj90pts\)
那怎么办呢?
我们考虑一个更简单的问题,假如把军队全部堆积到\(1\)号节点,怎么做呢?
很显然的贪心,让拥有更多剩余步数的军队去较远的点建立检查站
考虑原问题,我们仍然可以把军队全部堆积到\(1\)号节点
但是这些军队本来还有一个选择,就是驻守在原来的地方,而不跨越\(1\)号节点
也就是说,对于每支军队,它都有权不用任何花费驻守在原来的地方
我们还是先从大到小排序,一个个判断
假设现在是\(a_i\)与\(b_i\)匹配(\(a_i\)之后的\(a_j\)都小于\(a_i\),\(b_i\)同理)
若\(b_i\)有原本驻守在该地的\(a_k\),我们找到的\(a_k\)应当是权值最小的,由于\(a_k \le a_i\),选择\(a_k\)更优,直接选择\(a_k\)
若\(b_i\)无原本驻守在该地的\(a_k\),那么直接比较\(a_i,b_i\),若\(a_i<b_i\),说明匹配失败,当前情况不可行
权值最小的\(a_k\),直接用\(multiset\)维护(注:菜死,每个节点最多覆盖一次,直接记录覆盖一个节点的拥有最小剩余步数的军队就好了)
然后本题就解决了
\(Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#define ll long long
#define N 600005
#define RN 300005
#define pr pair<int,int>
#define mp make_pair
using namespace std;
int n,m,x,y,z,mxz,tot,mn[RN],kz[RN],f[RN][22],fr[RN],nxt[N],d1[N],d2[N];
ll ans,l=0,r=0,len[RN];
bool wro[RN],tag[RN],al[RN];
multiset< pr >s;
multiset< pr >t[RN];
pr xx[RN],q[RN];
void add(int x,int y,int z)
{
tot++;
d1[tot]=y;
d2[tot]=z;
nxt[tot]=fr[x];
fr[x]=tot;
}
void dfs(int u)
{
for (int i=fr[u];i;i=nxt[i])
{
int v=d1[i];
int cost=d2[i];
if (v==f[u][0])
continue;
f[v][0]=u;
len[v]=len[u]+cost;
r=max(r,len[v]);
dfs(v);
}
}
void judge(int u)
{
if (tag[u])
return;
if (u!=1 && !nxt[fr[u]])
wro[u]=true;
for (int i=fr[u];i;i=nxt[i])
{
int v=d1[i];
if (v==f[u][0])
continue;
judge(v);
wro[u]|=wro[v];
if (u!=1 && wro[u])
return;
}
}
bool cmp(pr x,pr y)
{
return x.first>y.first;
}
bool check(ll mid)
{
int q0=0,x0=0;
s.clear();
for (int i=1;i<=n;i++)
wro[i]=tag[i]=false,mn[i]=mxz+mxz+1,t[i].clear();
for (int i=1;i<=m;i++)
{
int g=kz[i];
if (g==1)
{
int qw;
if (mid>mxz)
qw=mxz; else
qw=mid;
s.insert(mp(qw,i));
al[i]=true;
continue;
}
ll o=mid;
for (int j=20;j>=0;j--)
if (f[g][j] && f[g][j]!=1 && len[g]-len[f[g][j]]<=o)
{
o-=len[g]-len[f[g][j]];
g=f[g][j];
}
if (o<=len[g])
al[i]=false,tag[g]=true; else
{
al[i]=true;
if (o>len[g]+mxz)
o=len[g]+mxz;
int qw=o-len[g];
mn[g]=min(mn[g],qw);
t[g].insert(mp(qw,i));
s.insert(mp(qw,i));
}
}
judge(1);
if (!s.empty())
{
multiset< pr > :: iterator it=s.end();
it--;
for (;it!=s.begin();it--)
q[++q0]=*it;
q[++q0]=*it;
}
for (int i=fr[1];i;i=nxt[i])
{
int v=d1[i];
int cost=d2[i];
if (wro[v])
xx[++x0]=mp(cost,v);
}
if (q0<x0)
return false;
sort(xx+1,xx+x0+1,cmp);
int l1=1,l2=1;
while (l2<=x0)
{
int y=xx[l2].second;
bool flag=false;
for (multiset< pr > :: iterator it=t[y].begin();it!=t[y].end();++it)
if (al[(*it).second])
{
al[(*it).second]=false;
while (l1<=q0 && !al[q[l1].second])
l1++;
l2++;
flag=true;
break;
}
if (flag)
continue;
if (q[l1].first<xx[l2].first)
return false;
l2++;
if (l2>x0)
return true;
al[q[l1].second]=false;
l1++;
while (l1<=q0 && !al[q[l1].second])
l1++;
if (l1>q0)
return false;
}
return true;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
scanf("%d",&m);
for (int i=1;i<=m;i++)
scanf("%d",&kz[i]);
dfs(1);
for (int i=fr[1];i;i=nxt[i])
mxz=max(mxz,d2[i]);
for (int j=1;j<=20;j++)
for (int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
r+=mxz;
ans=-1;
while (l<=r)
{
ll mid=(l+r) >> 1;
if (check(mid))
{
ans=mid;
r=mid-1;
} else
l=mid+1;
}
printf("%lld\n",ans);
return 0;
}