P1084 疫情控制
题目描述
思路
这道题目的思路比较好想,就是实现起来比较困难。
首先我们可以发现如果军队可以跳到当前节点的父亲一定不会使答案更劣,但我们需要一个步骤来限制跳的时间,所以我们想到了二分,比较显然答案满足单调性。
所以有了每次二分的答案之后我们就可以对于每一支军队如果能往父亲跳就暴力跳,如果不能就在当前节点打上标记,表示有军队驻扎。因为根节点不能驻军,所以我们只能把可以跳到根节点的军队进行调整。
第一次写代码的时候想错了,以为对每一个非叶子节点都需要进行调整,但发现是自己 $SB $ 了,最优解只会对根节点进行调整。
我们考虑怎样进行调整,对于每一支可以到达根节点的军队,我们需要先对他们到达根节点还剩余的时间取一个 \(\min\) 然后将他们的剩余时间插入一个 \(multiset\),方便以后调整用。
之后我们 \(dfs\) 一遍这棵树,记录一下哪些节点需要进行调整。
然后我们进行调整,对于深度为 \(2\) 的被标记的需要封死的节点,我们发现如果有军队是从这里上来的,我们有两种选择,一种是让军队不上来,一种是派另一个到达根节点的军队去封死。这取决于大于或等于当前边的所剩时间 \(x\) 和从这个点上去军队所剩时间的最小值 \(y\) 。
当 \(x<y\) 时,显然用新军队去封死这个子树比较优,因为从这个点上去军队剩余的时间更多,可能可以用来封死其他子树,所直接在 \(multiset\) 中删掉 \(x\) 。
那当 \(x>y\) 时,就是用原军队去封死比较优,需要删掉 \(y\) 。不过需要判断一下在 \(multiset\) 中 \(y\) 是否存在,如果不存在就删掉比 \(y\) 大的一个值。 \(y\) 不存在证明 \(y\) 被封死别的子树了,删掉比 \(y\) 大的一个值就是相当于你用一个比 \(y\) 大的值去封死这个用 \(y\) 封死的子树,这肯定是可以的。
上面的情况如果无法满足就直接 \(return 0\) ,代表这次的 \(mid\) 不可行,如果所有调整都可以实现就 \(return 1\) ,代表这次的 \(mid\) 可行。
Code
#include<bits/stdc++.h>
#define ll long long
#define pii pair<ll,int>
//#define mp make_pair
#define one first
#define two second
using namespace std;
const int N=5e4+100;
const ll INF=1e15;
struct edge{
int s,e;
ll v;
int net;
}ed[N<<1];
int n,m,tot,size;
int head[N],in[N],point[N],father[N];
bool mark[N],fix[N];
multiset<ll>s;
ll sum;
ll fv[N],minn[N];
inline void clear()
{
size=0;
for (int i=1;i<=n;i++)
{
mark[i]=fix[i]=0;
minn[i]=INF;
}
s.clear();
return ;
}
inline void up(int x,int num,ll now)
{
if (father[x]==1&&now-fv[x]>=0)
{
if (now-fv[x]>0)
for (int i=1;i<=num;i++)
s.insert(now-fv[x]);
minn[x]=min(minn[x],now-fv[x]);
if (now-fv[x]==0) mark[x]=1;
return ;
}
if (now-fv[x]>=0) up(father[x],num,now-fv[x]);
else mark[x]=1;
return ;
}
inline bool dfs_check(int x)
{
if (mark[x]) return 1;
bool p=0,pp=1;
for (int i=head[x];i;i=ed[i].net)
if (ed[i].e!=father[x])
{
if (!dfs_check(ed[i].e))
{
pp=0;
fix[x]=1;
}
p=1;
}
if (!p)
{
fix[x]=1;
return 0;
}
return pp;
}
inline void outset()
{
printf("multiset=\n");
for (multiset<ll>::iterator it=s.begin();it!=s.end();++it)
printf("%lld ",(*it));
printf("\n");
return ;
}
inline void outmark()
{
printf("mark=\n");
for (int i=1;i<=n;i++)
printf("%d ",mark[i]);
printf("\n");
return ;
}
inline void out()
{
printf("fix\n");
for (int i=1;i<=n;i++)
printf("%d ",fix[i]);
printf("\n");
return ;
}
inline bool check(ll mid)
{
clear();
for (int i=1;i<=n;i++)
if (point[i]) up(i,point[i],mid);
dfs_check(1);
multiset<ll>::iterator it,now,net;
for (int i=head[1];i;i=ed[i].net)
if (fix[ed[i].e])
{
if (!(int)s.size()) return 0;
ll val=ed[i].v;
it=s.lower_bound(val);
if (it!=s.end()&&(*it)==val)
{
if ((*it)<minn[ed[i].e]) s.erase(it);
else
{
now=s.lower_bound(minn[ed[i].e]);
if (now!=s.end()) s.erase(now);
else s.erase(it);
}
continue;
}
it=s.upper_bound(val);
if (it==s.end()&&minn[ed[i].e]==INF) return 0;
if (it==s.end())
{
now=s.lower_bound(minn[ed[i].e]);
if (now==s.end()) return 0;
else
{
s.erase(now);
continue;
}
}
if ((*it)<minn[ed[i].e]) s.erase(it);
else
{
now=s.find(minn[ed[i].e]);
if (now!=s.end()) s.erase(now);
else s.erase(it);
}
}
return 1;
}
inline void dfs(int x,int fa)
{
father[x]=fa;
for (int i=head[x];i;i=ed[i].net)
if (ed[i].e!=fa) dfs(ed[i].e,x);
else fv[x]=ed[i].v;
return ;
}
inline void add(int s,int e,int v)
{
ed[++tot]=(edge){s,e,v,head[s]};
in[s]++;
head[s]=tot;
return ;
}
int main()
{
freopen("testdata.in","r",stdin);
freopen("tesrdata1.ans","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n-1;i++)
{
int s,e,v;
scanf("%d%d%d",&s,&e,&v);
add(s,e,v);add(e,s,v);
sum+=v;
}
dfs(1,0);
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
point[x]++;
}
if (in[1]>m)
{
printf("-1\n");
return 0;
}
ll l=0,r=sum,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;
}
\(Important:\) \(lower\_bound\) 查询的是等于 \(val\) 的第一个值的位置,如果没有等于 \(val\) 的值,返回第一个大于 \(val\) 的值。