将军令:题解

  来源于模拟10

Description:

历史/落在/赢家/之手
至少/我们/拥有/传说
谁说/败者/无法/不朽
拳头/只能/让人/低头
念头/却能/让人/抬头
抬头/去看/去爱/去追
你心中的梦

  又想起了四月。
  如果不是省选,大家大概不会这么轻易地分道扬镳吧?
  只见一个又一个昔日的队友离开了机房。
  凭君莫话封侯事,一将功成万骨枯。
  梦里,小 F 成了一个给将军送密信的信使。
  现在, 有两封关乎国家生死的密信需要送到前线大将军帐下, 路途凶险,时间紧迫。小F 不因为自己的祸福而避趋之,勇敢地承担了这个任务。
  不过,小 F 实在是太粗心了,他一不小心把两封密信中的一封给弄掉了。
  小 F 偷偷打开了剩下的那封密信。他发现一副十分详细的地图,以及几句批文——原来这是战场周围的情报地图。 他仔细看后发现, 在这张地图上标记了n个从 1 到n标号的驿站, 条长度为 1 里的小道,每条小道双向连接两个不同的驿站,并且驿站之间可以通过小道两两可达。
  小 F 仔细辨认着上面的批注,突然明白了丢失的信的内容了。 原来, 每个驿站都可以驻扎一个小队,每个小队可以控制距离不超过k里的驿站。 如果有驿站没被控制,就容易产生危险——因此这种情况应该完全避免。而那封丢失的密信里,就装着朝廷数学重臣留下的精妙的排布方案,也就是用了最少的小队来控制所有驿站。
  小 F 知道,如果能计算出最优方案的话,也许他就能够将功赎过,免于死罪。他找到了你,你能帮帮他吗?
  当然,小 F 在等待你的支援的过程中,也许已经从图上观察出了一些可能会比较有用的性质,他会通过一种特殊的方式告诉你。

输入格式

  输入第 1 行一个正整数n,代表驿站数, 一支小队能够控制的最远距k, 以及特殊性质所代表的编号。 关于特殊性质请参照数据范围。
  输入第 2 行至第n+1行,每行两个正整数u,v,表示在u和v间,有一条长度为一里的小道。

输出格式

输出一行, 为最优方案下需要的小队数。
样例
样例输入1
4 1 0
1 2
1 3
1 4
样例输出1
1

基本思路:

  考场上观察了样例后想起了一个性质:

  在一棵树上,任意两点间的的距离不超过树的直径。

  所以我就直接打了个求直径然后除以2*k就走了,只有50分。
  50分代码:

#include<bits/stdc++.h>
using namespace std;
namespace Decleration
{
    #define ll long long
    #define rr register 
    const int SIZE=1e5+4;
    int n,k,t,en1,en2,D;
    int to[SIZE<<1],dire[SIZE<<1],head[SIZE];
    int depth[SIZE],fa[SIZE],top[SIZE];
    inline void add(rr int f,rr int t)
    {
        static int num=0;
        to[++num]=t;
        dire[num]=head[f];
        head[f]=num;
    }
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    void dfs1(rr int x)
    {
        if(fa[x]==1) top[x]=x;
        else top[x]=top[fa[x]];
        for(rr int i=head[x];i;i=dire[i])
        {
            if(to[i]==fa[x]) continue;
            fa[to[i]]=x;
            depth[to[i]]=depth[x]+1;
            dfs1(to[i]);
        }
        
        if(depth[x]>depth[en1]) en1=x;
    }
    void dfs2(rr int x)
    {
        for(rr int i=head[x];i;i=dire[i])
        {
            if(to[i]==fa[x]) continue;
            dfs2(to[i]);
        }
        if(top[x]!=top[en1]&&depth[x]>depth[en2]) en2=x;
    }
}
using namespace Decleration;
int main()
{
    n=read(),k=read(),t=read();
    for(rr int i=1;i<n;i++)
    {
        rr int f=read(),t=read();
        add(f,t),add(t,f);
    } 
    if(n==1)
    {
        printf("1\n");
        return 0;
    }
    if(!k)
    {
        printf("%d\n",n);
        return 0;
    }
    dfs1(1);
    dfs2(1);    
    D=depth[en1]+depth[en2];    
    double d=D,k_=k;
    double ans=ceil(d/(k_*2));
    printf("%.0lf\n",ans);
}

  说正解:
  贪心,蒟蒻作者现在根本不敢用贪心了,因为为不会证正确性……
  对于每一个点,他的最优决策是驻扎他的k级父亲
  然后我们将每一个点按深度递减排列,每次选取没有被看守到的点然后驻扎他的k级父亲,如果没有k级父亲,驻扎1号点。
  其实这是一个图的可重叠全覆盖问题,他的最优决策就是每一个覆盖都覆盖最大面积,这个正确性很好证,请自行完成。
  直接上代码好了:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    const int SIZE=1e5+4;
    struct dot{int id,depth;}a[SIZE];
    int n,k,t;
    int fa[SIZE];
    int head[SIZE],to[SIZE<<1];
    int dire[SIZE<<1];
    int fk[SIZE],po[SIZE];
    bool vis[SIZE];
    ll ans;
    inline void add(rr int f,rr int t)
    {
        static int num=0;
        to[++num]=t;
        dire[num]=head[f];
        head[f]=num;
    }
    void dfs(rr int x)
    {
        for(rr int i=head[x];i;i=dire[i])
        {
            if(to[i]==fa[x]) continue;
            fa[to[i]]=x;
            a[to[i]].depth=a[x].depth+1;
            a[to[i]].id=to[i];
            dfs(to[i]);
        }
    }
    int cmp(const dot a_cmp,const dot b_cmp){return a_cmp.depth>b_cmp.depth;}
    int max(rr int x,rr int y){return x>y?x:y;}
    int min(rr int x,rr int y){return x<y ?x:y;}
    void update(rr int x,rr int f,rr int dis)
    {
        if(dis>k) return;
        vis[x]=1;
        for(rr int i=head[x];i;i=dire[i])
        {
            if(to[i]==f) continue;            
            update(to[i],x,dis+1);
        } 
    }
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
};
using namespace STD;
int main()
{
    n=read(),k=read(),t=read();
    for(rr int i=1;i<n;i++)
    {
        rr int x=read(),y=read();
        add(x,y),add(y,x);
    }
    if(n==1){printf("1\n");return 0;}
    if(!k){printf("%d\n",n);return 0;}
    a[1].id=1;
    fa[1]=1;
    dfs(1);
    for(rr int i=1;i<=n;i++)
    {
        rr int x=fa[i];
        while(a[i].depth-a[x].depth!=k&&x!=1) x=fa[x];
        fk[i]=x;
    }
    sort(a+1,a+1+n,cmp);
    for(rr int i=1;i<=n;i++) po[a[i].id]=i;
    for(rr int i=1;i<=n;i++)
    {
        if(!vis[a[i].id])
        {
            vis[a[i].id]=1;
            ans++;
            update(fk[a[i].id],fk[a[i].id],0);
            //这里直接暴力更新即可,因为实际上每个点只会被访问一次,所以直接暴力即可
            //看似暴力,其实O(n);
        }
    }
    printf("%lld\n",ans);
}

总结:

  这题主要是贪心法,并不是很生疏,但是想不起来;
  还暴露出一个问题是不会算复杂度,普通的还好,这里的暴力是 O O O ( n ) (n) (n),自己没算对,就没打。
2021.6.30 现役

posted @ 2021-06-30 12:08  Geek_kay  阅读(37)  评论(0编辑  收藏  举报