将军令:题解
来源于模拟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 现役