为什么要写这种如此简单的东西呢 就是因为菜啊
首先给出关于贪心的三个定义
符合贪心选择的特性(Greedy Choice Property)
我们需要证明我们的第一个选择(贪心选择 Greedy Choice,First Choice)包含在某些最优解中
符合归纳法结构(Inductive Structure)
我们需要证明第一个选择(贪心选择)之后,子问题和原问题还是同一类问题,意味着我们的选择不改变问题的结构,
并且子问题的解可以和第一个选择(贪心选择)合并
最优子结构(Optimal Substructure)
如果我们可以最优的解决子问题,我们可以将子问题的解和贪心选择得到原问题的解
(以上摘自\(CSDN\)博主小郭(ω))
先谈谈我自己的理解
上面的专业术语看着都应该头大才对,那怎么说会让它变得简单一些呢,我们从一个简单地例子来解释上面的第一条和第三条。
现在\(Overwatch2\)非常火,众所周知,许多英雄都被大改,比如原版查莉雅有两个盾,一个只能套给自己,一个只能套给队友,两个分开冷却;现在的查莉雅可以存两个盾,每个盾可以选择套给自己,也可以套给队友,两个盾共享冷却(注意可以存两个)。那么现在和原来哪种查莉雅更强呢?很明显是当前版本的更强,我们尝试用贪心的思路来证明一下:
搬出我么喜闻乐见的反证法:假设当前版本并不比之前的强,那么对于之前的版本,查莉雅最多可以做到同时给自己和队友套盾,而当前版本的查莉雅显然也可以做到,这便是“我们需要证明我们的第一个选择(贪心选择 Greedy Choice,First Choice)包含在某些最优解中”,不仅当前版本的查莉雅包含于我们假设的最优解(之前的版本),她还可以做到给两个队友套盾或者连续给自己套两个盾,也就证明了现在版本的查莉雅至少不会比我们假设为更优的之前版本的查莉雅更差,这就体现了一种包含关系。
如何理解第三条呢,很简单,对于我们套出的每一个盾,新版都能做到和老版一样,并且还可以做出更灵活的操作,这就是“如果我们可以最优的解决子问题,我们可以将子问题的解和贪心选择得到原问题的解”
至于第二条嘛。。反正我没有看懂。
来个例子 P3942 将军令
题意就不赘述了,在这个题里面我们采用每次找深度最深的点的第\(k\)个父亲,这种贪心策略是正确的,且看下图
三角形表示一棵很大的子树,图中深度关系已大致体现
这时我们取出最深的\(E\),我们要求的\(k\)为\(5\),那么按照贪心策略找到的父亲节点就是\(I\),我们来证明一下这个贪心策略是正确的:
假设我们往上找\(k\)个父亲不是最优的,最优解应选择同样距离\(E\)点距离为\(5\)的\(J\)点,假设\(K,L\)的深度是与\(E\)相同的,那么毫无疑问的是我们假设的最优解一定可以覆盖整个三角形子树,而\(I\)可以覆盖到\(E\)的深度,所以他也能一样覆盖到三角形子树,这时我们就证明到了贪心策略是包含在某个最优解中的了,但是对于\(I\)的右子树来说,\(J\)一定会比\(I\)少覆盖两个深度的点,因为\(J\)还需要花费两个距离的代价来到达\(I\),多假设几个最优解的点,你会发现对于一个这样的树形结构,我们的贪心策略至少不会比假设的最优解差,这时我们就可以放心的使用贪心来解决这道题了。
每次找到一个点,往他的周围暴力扩展\(k\)个点,再维护一个以深度为第一关键字的优先队列就差不多能够解决了,如果真的想不出正解或者打不出\(dp\)这种高级做法,可以大胆地使用自己认为正确并且举不出反例的的贪心,毕竟\(OI\)并不在于让你证明某个结论,再贴个代码吧
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6;
template <typename T>inline void re(T &x)
{
x=0;
int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-f;
for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
x*=f;
return;
}
int n,k,t,cnt;
struct Edge{int u,v,nex;}E[maxn];
int tote,head[maxn];
void add(int u,int v)
{
E[++tote].u=u,E[tote].v=v;
E[tote].nex=head[u],head[u]=tote;
}
int fa[maxn];
int dep[maxn],vis[maxn];
struct node{int x,dep;};
bool operator <(node a,node b){return a.dep<b.dep;}
priority_queue<node> q;
void dfsbasic(int x)
{
for(int i=head[x];i;i=E[i].nex)
{
int v=E[i].v;
if(dep[v]) continue;
fa[v]=x;dep[v]=dep[x]+1;
dfsbasic(v);
}
q.push((node){x,dep[x]});
}
int f(int x){for(register int i=1;i<=k;++i){x=fa[x];if(x==1)return 1;}return x;}
void dfsupd(int x,int fa,int com)/*x,x,0*/
{
if(com==k+1)return ;
if(!vis[x])vis[x]=1,cnt++;
for(register int i=head[x];i;i=E[i].nex)
{
int v=E[i].v;
if(v==fa)continue;
dfsupd(v,x,com+1);
}
}
int main()
{
re(n),re(k),re(t);
int u,v;
for(register int i=1;i<n;++i)
{
re(u),re(v);
add(u,v),add(v,u);
}
int rep=1;
dep[rep]=1,fa[rep]=rep,dfsbasic(rep);
int pick=0;
while(!q.empty())
{
node tmp=q.top();q.pop();
int x=tmp.x;
if(vis[x])continue;
int sfa=f(x);
dfsupd(sfa,sfa,0);
pick++;
if(cnt==n)break;
}
printf("%d",pick);
return 0;
}