[noip2012]疫情控制(二分+贪心)

本文介绍了一个竞赛题目的解题思路,利用二分法和贪心策略进行解答。首先通过二分法确定可行的时间范围,再采用贪心算法分配军队,确保每个边境城市都有足够的防御力量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

我是超链接

题解:

首先是-1的情况,如果军队数小于1节点的儿子数就-1
这种解一看没法直接求的就二分啊
我的r设成从根节点引出的最长链+根节点到ta儿子节点的最长链(比起那些无脑5000000好科学啊)
具体怎么判断二分的数值能不能达到呢?
在这些时间内军队可以一起行动,那我让所有的军队都往上蹦,蹦到根节点还有剩余时间的记录下来,顺便记录从这个节点(根节点的哪个儿子)出来的剩余时间的最小值及其军队序号

dfs找出来蹦完之后还有哪个边境城市没有驻守,则需要走到从这个边境城市出发向首都走所需 经过的最后一个点(ta所属的 根节点儿子),按这个点到首都距离从大到小排序

然后把剩余的按照时间从大到小排序,如果有从这个(根节点的儿子节点)出来的就直接用ta好了,这里的原理是贪心!因为剩余时间大的就是要匹配距离大的,可以留下剩余时间多的,用从自己出来的军队何乐而不为?

我的题解描述真是鬼畜啊

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define sz 19
using namespace std;
const int N=50005;
int tot,nxt[N*2],point[N],v[N*2],c[N*2],n,m,mi[sz],dis[N][sz],f[N][sz],inc[N],num,ez;
int h[N],son[N],dg[N],a[N],l,r,maxx,restmin[N],restzb[N];
bool vis[N],viv[N],pq[N];
struct hh{int id,tim;}sy[N],jl[N];
int cmp(hh a,hh b){return a.tim>b.tim;}
void addline(int x,int y,int z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
void dfs(int x,int fa,int id)
{
    h[x]=h[fa]+1;
    for (int i=1;i<sz;i++)
      if (h[x]<mi[i]) break;
      else 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=point[x];i;i=nxt[i])
      if (v[i]!=fa)
      {
        dg[v[i]]=dg[x]+c[i];
        if (!fa) id++,son[id]=v[i],maxx=max(maxx,c[i]);
        r=max(r,dg[v[i]]);
        inc[v[i]]=son[id];
        f[v[i]][0]=x;
        dis[v[i]][0]=c[i];
        dfs(v[i],x,id);
      }
}
int jump(int x,int tim)
{
    for (int i=sz-1;i>=0;i--)
      if (f[x][i] && dis[x][i]<=tim) 
        tim-=dis[x][i],x=f[x][i];
    return x;
}
void ss(int x,int fa,int id,bool bai)
{
    vis[x]=bai || vis[x];int size=0;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa)
      {
        size++;
        if (x==1) id++;
        if (vis[x]) ss(v[i],x,id,1);
        else ss(v[i],x,id,0); 
      }
    if (!size && !vis[x] && !viv[id]) viv[id]=1,jl[++num].id=son[id],jl[num].tim=dg[son[id]];
    //没有被覆盖的叶子,把ta属于的子树记录下来 
}
bool check(int tim)
{
    memset(vis,0,sizeof(vis));
    memset(viv,0,sizeof(viv));
    memset(pq,0,sizeof(pq));
    memset(restmin,0x7f,sizeof(restmin));
    memset(restzb,0,sizeof(restzb));
    num=0;
    int cnt=0,i,j=1;
    for (i=1;i<=m;i++)
      if (dg[a[i]]<=tim)
      {
        sy[++cnt].id=i;
        sy[cnt].tim=tim-dg[a[i]];
        int x=inc[a[i]];//x表示所属子树的节点号 
        if (!restzb[x] || sy[cnt].tim<restmin[x])
          restzb[x]=i,restmin[x]=sy[cnt].tim;
      }
      else vis[jump(a[i],tim)]=1;
    ss(1,0,0,0);
    sort(jl+1,jl+num+1,cmp);
    sort(sy+1,sy+cnt+1,cmp);
    pq[0]=1;
    for (i=1;i<=num;i++)
    {
        if (!pq[restzb[jl[i].id]]) {pq[restzb[jl[i].id]]=1;continue;}
        while (j<=cnt && (pq[sy[j].id] || sy[j].tim<jl[i].tim)) j++;
        if (j>cnt) return 0;
        pq[sy[j].id]=1;
    }    
    return 1;
}
int main()
{
    int i,j;
    mi[0]=1;for (i=1;i<sz;i++) mi[i]=mi[i-1]*2;
    scanf("%d",&n);
    for (i=1;i<n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        addline(x,y,z);
        if (x==1 || y==1) ez++;
    }
    dfs(1,0,0); r+=maxx;
    scanf("%d",&m);
    if (ez>m) {printf("-1");return 0;}
    for (i=1;i<=m;i++) scanf("%d",&a[i]);
    int ans=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d",ans);  
}

吐槽:

真是一道充满挫折的题目:
一开始想了40mins想出正解,然后开写后死也编译不过,UPD 1天后把一个叫做search的过程改成ss就编译过了(╯‵□′)╯︵┻━┻
然后一交90pts,woccccc你还卡常?
卡了一波卡不进去,这居然不是卡常能解决的问题。。。。。
又学习了check中一种比较科学的方法才AC
但是112ms,除了打表的就是我快啊hahaha

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值