NOIP2012疫情控制 二分答案+贪心+树上倍增

NOIP2012疫情控制

细节比较多,思维难度还是有的(主要是考虑情况的完整性),可能重点在实现上面吧。

Solution:

最少需要多少? 很明显的可以二分答案是吧。

然后重点考虑如何check。可以来模拟一下这个过程:

首先,一个很显然的贪心策略: 一支军队能往上走就尽量往上走。

由于现在知道每支军队能够向上走多少步,

所以可以用树上倍增,把每支军队跳到根节点下或停在它不能继续向上的位置。

这个时候进行一次统计,看还有多少个叶子节点没有被覆盖的掉,记录一下它的距离根最近的祖先,即根的某个儿子。

为什么记录它是很显然的:它所在的子树还有未被覆盖的叶子节点,那么必然需要其他子树中的过来,显然走到它就停下是最优的。

无解的情况:记录的点数>多余的军队数。

然后,

I:会了会了,直接把所有这样的根的儿子找出来后,再统计一下其他子树有多少可以空出来。

    都用个数组存着,从小到大排个序,用剩余时间少的军队去距离近的子树,只要保证其他子树至少有一个就好了!

    很对很对恩。。    

码码码。。。。

I:Wocao,60!怎么回事⊙ o ⊙ 。

如果您也在纳闷,那么不妨看这样一组数据:

Input:
4
1 2 9
1 3 1
1 4 2
3 
1 1 2
Output:
10

如果您犯了这个错误,应该已经明白问题在哪了。

所以说这个地方还是很容易成为一个思维漏洞的,这应该也是此题最关键的地方了。

这个时候我们需要遵循一个贪心策略 :

如果一些满足上述条件的军队Ai(都在S处),其中最小的不能满足他剩余的距离能够到root又回来,即小于2*dis(root,S);
那么必须留下那个剩余时间最短的军队守S,其余的当做跨根的用,否则可以都当做跨跟的用。

至此,我们就可以开心的解决这个问题了,代码实现过程中可能需要注意一下下吧。

Code↓:

// luogu-judger-enable-o2
#include<queue>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;

IL int gi() {
    char ch=getchar(); RG int x=0,w=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=1e5+10;

queue<int> q;
vector<LL> s[N];
LL L,R=1e15,mid,d[N][21];
int n,m,Lef,num,cnt,PaPa,tot,head[N],army[N],f[N][21],vis[N],tag[N],leaf[N],Pa[N],occ[N];

struct ARMY{int bel;LL res;}A[N],Need[N];
struct EDGE{int next,to,v;}e[N<<1];

IL bool CMP(ARMY a,ARMY b) {return a.res<b.res;}

IL void make(int a,int b,int c) {
    e[++tot]=(EDGE){head[a],b,c},head[a]=tot;
    e[++tot]=(EDGE){head[b],a,c},head[b]=tot;
}

void dfs(int x,int fx) {
    RG int i,y,fl=0;
    for (i=head[x];i;i=e[i].next)
        if ((y=e[i].to)!=fx) {
            if (x==1) PaPa=y;
            fl=1,f[y][0]=x,d[y][0]=e[i].v,dfs(y,x);
        }
    if (!fl) leaf[++Lef]=x,Pa[Lef]=PaPa;
}

IL void work() {
    RG int i,j,p;
    for (i=1;i<=17;++i)
        for (j=1;j<=n;++j)
            p=f[j][i-1],f[j][i]=f[p][i-1],d[j][i]=d[j][i-1]+d[p][i-1];
}

IL void BFS(int S) {
    RG int i,x,y;
    q.push(S);
    while (!q.empty()) {
        x=q.front(),q.pop();
        for (i=head[x],vis[x]=1;i;i=e[i].next)
            if ((y=e[i].to)!=f[x][0]&&!vis[y]) q.push(y); 
    }
}
 
IL int check() {
    RG LL sum;
    RG int i,j,x;
    memset(occ,0,sizeof(occ));
    memset(vis,0,sizeof(vis));
    memset(tag,0,sizeof(tag));
    for (i=head[1];i;i=e[i].next) s[e[i].to].clear();
    for (i=1,cnt=0,num=0;i<=m;++i) {
        for (j=17,x=army[i],sum=0;j>=0;--j)
            if (f[x][j]>1&&sum+d[x][j]<=mid) sum+=d[x][j],x=f[x][j];
        if (f[x][0]==1) s[x].push_back(mid-sum);
        else if (!vis[x]) BFS(x);
    }
    for (i=head[1];i;i=e[i].next) sort(s[e[i].to].begin(),s[e[i].to].end());	
    for (i=1;i<=Lef;++i)
        if (!vis[leaf[i]]&&!occ[x=Pa[i]]) {
            occ[x]=1;
            if (!s[x].size()||s[x][0]>=2*d[x][0]) Need[++num]=(ARMY){x,d[x][0]};
            else tag[x]=1;
        }
    for (i=head[1];i;i=e[i].next)
        for (x=e[i].to,j=tag[x];j<s[x].size();++j)
            if (s[x][j]>d[x][0]) A[++cnt]=(ARMY){x,s[x][j]-d[x][0]};
    sort(A+1,A+cnt+1,CMP);	
    sort(Need+1,Need+num+1,CMP);
    for (i=1,j=1;i<=num;++i,++j) {
        while (j<=cnt&&Need[i].res>A[j].res) ++j;
        if (j==cnt+1) break;
    }
    return i>num;	
}	

int main()
{
    RG int i,x,y,z;
    for (i=1,n=gi();i<n;++i) x=gi(),y=gi(),z=gi(),make(x,y,z);
    for (i=1,m=gi();i<=m;++i) army[i]=gi();
    dfs(1,1),work();
    while (L<R) {
        mid=L+R>>1;
        if (check()) R=mid;
        else L=mid+1;
    }
    printf("%lld\n",R);
    return 0;
}

// 程序有点慢
// 一定要注意有一个很重要的点:
// 那就是如果只有一个军队A驻扎在S,S∈SON(root),那么有两种可能性
// 一另是A守住S这一棵子树,另一种是A去别的子树S',别的A'来S.
// 此时有一个结论需要遵守:
// 如果一些满足上述条件的军队Ai(都在S处),其中最小的不能满足他剩余的距离能够到root又回来,即小于2*dis(root,S);
// 那么必须留下那个剩余时间最短的军队守S,其余的当做跨根的用.
    

The End

posted @ 2019-03-28 19:59  薄荷凉了夏  阅读(180)  评论(0编辑  收藏  举报