洛谷 1084 疫情控制——二分答案+贪心(贪心思路!)

题目:https://www.luogu.org/problemnew/show/P1084

二分答案、倍增往上走都很好想,关键是怎么贪心……

先写了一个贪心,让能走到根的军队中可以待在原孩子的先待在那,然后看看根的哪些孩子未满足,从剩下的中双指针地走。

忘了给根的孩子的边权排序,40分。改了以后70分……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=5e4+5,Lm=16;
int n,m,pos[N],hd[N],xnt,nxt[N<<1],to[N<<1],w[N<<1];
int sta[N],top,tmp[N],tot,jl[N],jnt,pre[N][Lm+5],bh[N];
ll dis[N],l,r,mid,ans=-1;
bool vis[N],use[N];
int rdn()
{
    int ret=0;bool fx=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
    while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
    return fx?ret:-ret;
}
bool cmp(int u,int v)
{return dis[u]>dis[v];}//先走距离远的
void add(int x,int y,int z)
{
    to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt; w[xnt]=z;
    to[++xnt]=x; nxt[xnt]=hd[y]; hd[y]=xnt; w[xnt]=z;
    r+=z;
}
void dfs(int cr,int fa,int b)
{
    pre[cr][0]=fa; bh[cr]=b;//bh:记录在根的哪个孩子
    for(int i=1,d;i<=Lm;i++)
    {
        d=pre[cr][i-1]; if(!pre[d][i-1])break;
        pre[cr][i]=pre[d][i-1];
    }
    for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=fa)
        {
            dis[v]=dis[cr]+w[i]; dfs(v,cr,fa?b:v);
            //r=max(r,dis[v]);//不是!!!
        }
}
void dfsx(int cr,int fa)
{
    if(vis[cr]||!nxt[hd[cr]])return;
    for(int i=hd[cr],v=to[i];i;i=nxt[i],v=to[i])
        if(v!=fa)
        {
            dfsx(v,cr); if(!vis[v]) return;
        }
    vis[cr]=1;
}
bool solve()
{
    memset(vis,0,sizeof vis);
    top=0;
    for(int i=1,nw=pos[i],b=bh[nw];i<=m;i++,nw=pos[i],b=bh[nw])
    {
        ll lm=dis[nw]-mid;
        for(int j=Lm;j>=0;j--)
            if(dis[pre[nw][j]]>=lm&&pre[nw][j])
                nw=pre[nw][j];
        vis[nw]=1;
        //printf("i=%d nw=%d\n",i,nw);
        if(nw==1) sta[++top]=pos[i];//按剩余从小到大排序过了
    }

    for(int i=hd[1];i;i=nxt[i])
        dfsx(to[i],1);

    for(int i=1;i<=jnt;i++) use[jl[i]]=0;
    jnt=0;
    for(int i=1;i<=top;i++)
    {
        //printf("bh[%d]=%d vis=%d\n",sta[i],bh[sta[i]],vis[bh[sta[i]]]);
        if(!vis[bh[sta[i]]])
        {
            vis[bh[sta[i]]]=1,use[i]=1;
            jl[++jnt]=i;
        }
    }

    tot=0;
    for(int i=hd[1];i;i=nxt[i])
    {
        //printf("to[i]=%d vis[%d]=%d\n",to[i],to[i],vis[to[i]]);
        if(!vis[to[i]])tmp[++tot]=w[i];
    }

    sort(tmp+1,tmp+tot+1);//!
    int p0=1;
    //if(mid==1000)printf("tot=%d top=%d jnt=%d\n",tot,top,jnt);
    for(int i=1;i<=tot;i++)
    {
        //if(mid==1000)printf("tmp[%d]=%d\n",i,tmp[i]);
        while(p0<top&&(use[p0]||mid-dis[sta[p0]]<tmp[i]))
            p0++;
        //if(mid==1000)printf("sta[%d]=%d res=%lld\n",p0,sta[p0],mid-dis[sta[p0]]);
        if(p0>top||use[p0]||mid-dis[sta[p0]]<tmp[i])
        {
            //if(mid==1000)
            //    printf("tmp=%d sta[%d]=%d res=%lld(top=%d)\n",tmp[i],p0,sta[p0],mid-dis[sta[p0]],top);
            return 0;
        }
        p0++;
    }
    return 1;
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("testdata.in","r",stdin);
    //freopen("zj.out","w",stdout);
    n=rdn();
    for(int i=1,u,v,z;i<n;i++)
    {
        u=rdn();v=rdn();z=rdn();
        add(u,v,z);
    }
    dfs(1,0,0);
    m=rdn();
    for(int i=1;i<=m;i++) pos[i]=rdn();
    sort(pos+1,pos+m+1,cmp);
    while(l<=r)
    {
        mid=l+r>>1ll;
        if(solve()) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

然后想到了反例。就是可以让走到根、剩余较少的军队覆盖一个离根近的孩子,让那个孩子的军队走上来,去覆盖别的孩子。如果离根近的孩子的军队剩余得很多的话,就能覆盖掉别的军队覆盖不了的孩子了。

于是换贪心策略。先双指针地分配,如果一个军队走不了了,再看看它是否需要回到自己原来的孩子,不能的话就不管它了。

然而还是70分……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=5e4+5,Lm=16;
int n,m,pos[N],hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1];
int dis[N],pre[N][Lm+5],bh[N],fe[N];
int sta[N],top,tmp[N],tot;
ll ans,mid,l,r;
bool vis[N];
int rdn()
{
    int ret=0;bool fx=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
    while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
    return fx?ret:-ret;
}
void add(int x,int y,int z)
{
    to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt; w[xnt]=z;
    to[++xnt]=x; nxt[xnt]=hd[y]; hd[y]=xnt; w[xnt]=z;
}
void dfs(int cr,int f,int b)
{
    pre[cr][0]=f;
    for(int i=1,d;i<=Lm;i++)
    {
        d=pre[cr][i-1];
        if(!d)break;
        pre[cr][i]=pre[d][i-1];
    }

    bh[cr]=b;
    for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=f)
        {
            dis[v]=dis[cr]+w[i];
            fe[v]=w[i];
            dfs(v,cr,f?b:v);
        }
}
bool cmp(int u,int v){return dis[u]>dis[v];}
void dfsx(int cr,int f)
{
    if((cr>1&&vis[cr])||!nxt[hd[cr]])return;
    for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=f)
        {
            dfsx(v,cr);
            if(!vis[v]&&cr>1)return;
        }
    vis[cr]=1;
}
bool cmp2(int u,int v){return fe[u]<fe[v];}
bool solve()
{
    memset(vis,0,sizeof vis); top=0;
    for(int i=1,nw=pos[i];i<=m;i++,nw=pos[i])
    {
        ll lm=dis[nw]-mid;
        for(int j=Lm;j>=0;j--)
            if(pre[nw][j]&&dis[pre[nw][j]]>=lm)
                nw=pre[nw][j];
        vis[nw]=1;
        if(nw==1)sta[++top]=pos[i];
    }
    dfsx(1,0);
    tot=0;
    for(int i=hd[1];i;i=nxt[i])
        if(!vis[to[i]]) tmp[++tot]=to[i];
    sort(tmp+1,tmp+tot+1,cmp2);

    int p0=top;
    for(int i=tot;i;i--)
    {
        if(vis[tmp[i]])continue;
        while(p0>1&&mid-dis[sta[p0]]<fe[tmp[i]])
        {
            if(!vis[bh[sta[p0]]]) vis[bh[sta[p0]]]=1;
            p0--;
        }
        if(p0<=0||mid-dis[sta[p0]]<fe[tmp[i]])
            return 0;
        p0--;
    }
    return 1;
}
int main()
{
    //freopen("testdata.in","r",stdin);
    n=rdn();
    for(int i=1,u,v,z;i<n;i++)
    {
        u=rdn(); v=rdn(); z=rdn();
        add(u,v,z); r+=z<<1;
    }
    dfs(1,0,0);
    m=rdn();
    for(int i=1;i<=m;i++) pos[i]=rdn();
    sort(pos+1,pos+m+1,cmp);
    while(l<=r)
    {
        mid=l+r>>1;
        if(solve())ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

终于去查了题解。原来应该以军队为主,从小到大遍历军队,如果它的原来的孩子还没被管,就让它管它原来的孩子;否则让它管一个根的最小的孩子,即正常的双指针。

这样贪心的话,一个军队要么是双指针地管了最小的需要管的孩子,要么是更优地管了自己原来的孩子;既不像第一个贪心那样,可能浪费路程长的军队,又不像第二个贪心那样,可能浪费了军队可以随便回到自己原来孩子的性质。真是……

自己还要多锻炼思维呢。

代码里注意给双指针走过的孩子也打上 vis 标记。因为进来一个军队时要判断那个。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=5e4+5,Lm=16;
int n,m,pos[N],hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1];
int dis[N],pre[N][Lm+5],bh[N],fe[N];
int sta[N],top,tmp[N],tot;
ll ans=-1,mid,l,r;
bool vis[N];
int rdn()
{
    int ret=0;bool fx=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
    while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
    return fx?ret:-ret;
}
void add(int x,int y,int z)
{
    to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt; w[xnt]=z;
    to[++xnt]=x; nxt[xnt]=hd[y]; hd[y]=xnt; w[xnt]=z;
}
void dfs(int cr,int f,int b)
{
    pre[cr][0]=f;
    for(int i=1,d;i<=Lm;i++)
    {
        d=pre[cr][i-1];
        if(!d)break;
        pre[cr][i]=pre[d][i-1];
    }

    bh[cr]=b;
    for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=f)
        {
            dis[v]=dis[cr]+w[i];
            fe[v]=w[i];
            dfs(v,cr,f?b:v);
        }
}
bool cmp(int u,int v){return dis[u]>dis[v];}
void dfsx(int cr,int f)
{
    if((cr>1&&vis[cr])||(!nxt[hd[cr]]&&cr>1))return;//cr>1
    for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=f)
        {
            dfsx(v,cr);
            if(!vis[v]&&cr>1)return;
        }
    vis[cr]=1;
}
bool cmp2(int u,int v){return fe[u]<fe[v];}
bool solve()
{
    memset(vis,0,sizeof vis); top=0;
    for(int i=1,nw=pos[i];i<=m;i++,nw=pos[i])
    {
        ll lm=dis[nw]-mid;
        for(int j=Lm;j>=0;j--)
            if(pre[nw][j]&&dis[pre[nw][j]]>=lm)
                nw=pre[nw][j];
        vis[nw]=1;
        if(nw==1)sta[++top]=pos[i];
    }

    //for(int i=1;i<=top;i++) printf("sta[%d]=%d\n",i,sta[i]);
    
    dfsx(1,0);
    tot=0;
    for(int i=hd[1];i;i=nxt[i])
        if(!vis[to[i]]) tmp[++tot]=to[i];
    sort(tmp+1,tmp+tot+1,cmp2);

    //for(int i=1;i<=tot;i++) printf("tmp[%d]=%d\n",i,tmp[i]);

    int p0=1;
    for(int i=1;i<=top;i++)
    {
        if(!vis[bh[sta[i]]])
        {
            vis[bh[sta[i]]]=1;
            //if(mid==11)printf("vis[bh[%d]=%d]=1",sta[i],bh[sta[i]]);
            continue;
        }
        while(p0<tot&&vis[tmp[p0]])p0++;
        if(vis[tmp[p0]])return 1;
        //if(mid==11) printf("tmp[%d]=%d fe=%d mid-dis[%d]=%lld\n",p0,tmp[p0],fe[tmp[p0]],sta[i],mid-dis[sta[i]]);
        if(mid-dis[sta[i]]>=fe[tmp[p0]])
        {
            vis[tmp[p0]]=1; p0++;//vis!!
            if(p0>tot)return 1;
        }
    }
    while(p0<tot&&vis[tmp[p0]])p0++;
    return vis[tmp[p0]];
}
int main()
{
    //freopen("testdata.in","r",stdin);
    n=rdn();
    for(int i=1,u,v,z;i<n;i++)
    {
        u=rdn(); v=rdn(); z=rdn();
        add(u,v,z); r+=z<<1;
    }
    dfs(1,0,0);
    m=rdn();
    for(int i=1;i<=m;i++) pos[i]=rdn();
    sort(pos+1,pos+m+1,cmp);
    while(l<=r)
    {
        //printf("l=%lld r=%lld\n",l,r);
        mid=l+r>>1ll;
        if(solve())ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}

 

posted on 2018-09-22 14:49  Narh  阅读(266)  评论(0编辑  收藏  举报

导航