NOIP模拟赛20191026 T3 Repulsed【树上控制型贪心】
题目描述:
n个点的树,每个节点可以放置任意个灭火器,每个灭火器可以浇灭距离不超过k条边的火,且最多只能浇灭s个点的火。现在n个点全都有火,问把火全部浇灭的最少灭火器数量。
n<=100000,k<=20,s<=109
题目分析:
这个题,如果没有s的限制,就是个提高-的贪心题。可以dfs从下往上传最浅的灭火器和最深的没有被扑灭的火,然后匹配或者新加灭火器,复杂度O(n)。或者按照深度从大到小枚举点,检查往上k个点是否有灭火器,如果没有就放一个然后更新再往上的k个点(查询时检验深度之和,具体方法见下),复杂度O(nk)。
现在有了s的限制怎么做,考虑同样的方法。
先看看dfs是否可行,我们需要知道子树中还未被扑灭的火的深度,除此之外还要知道对应的数量,以及灭火器的深度及还可以用的数量。
先考虑从下往上传时用堆维护,首先,如果有离当前点距离为k的点时,显然需要在当前点放置灭火器。
然后,很自然地会想到让子树内的火和灭火器配对,但是在有个数限制的情况下子树内配对并不是最优的,会出现这样的情况:
所以O(nlogn)的方法行不通,但是当灭火器和火的距离之和为k以及k-1时,一定会在子树内匹配:
而当灭火器和火的距离<=k-2时,我们可以把问题留到当前点的父亲去解决,并不会有什么影响。
所以我们可以记录和分别表示子树内距离为的灭火器和火的数量,然后将距离和为k的配对,再将和为k-1的配对,然后往上传即可,复杂度O(nk)
Code:
#include<bits/stdc++.h>
#define maxn 100005
#define maxk 25
using namespace std;
int n,s,k,dep[maxn],f[maxn][maxk],g[maxn][maxk],ans;//f:put out g:fire need
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void dfs(int u,int ff){
for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff){
dfs(v,u);
for(int j=0;j<k;j++) f[u][j+1]=min(f[u][j+1]+f[v][j],n),g[u][j+1]+=g[v][j];
}
g[u][0]=1; int x;
if(g[u][k]) ans+=(x=(g[u][k]+s-1)/s),f[u][0]=min(s*x,n);
for(int i=0;i<=k;i++) x=min(f[u][i],g[u][k-i]),f[u][i]-=x,g[u][k-i]-=x;
for(int i=0;i<k;i++) x=min(f[u][i],g[u][k-i-1]),f[u][i]-=x,g[u][k-i-1]-=x;
}
int main()
{
scanf("%d%d%d",&n,&s,&k);
for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
dfs(1,0);
for(int i=0,x;i<=k;i++)
for(int j=k-i;j>=0&&f[1][i];j--)
x=min(f[1][i],g[1][j]),f[1][i]-=x,g[1][j]-=x;
int sum=0; for(int i=0;i<=k;i++) sum+=g[1][i];
printf("%d\n",ans+(sum+s-1)/s);
}
再来看看向上检测k的方法是否可行。
按深度由大到小枚举点,向上查找是否有灭火器,如果没有,假设当前火向上k个点为,那么在点新加一个灭火器,同时用u点的灭火器编号插入u往上1~k个点的set中,set按编号对应深度排序,查找时就在set中查找能够配对的最深的灭火器配对(不删除),一个全局数组记录每个编号灭火器的个数,当个数减为0时就将该编号从u往上1~k个点的set中删去。
看似十分可行,需要检验正确性的就是"找能够配对的最深的灭火器配对"这一点:
(%Freopen的Code):
#include<bits/stdc++.h>
#define maxn 100005
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
int n,s,k;
int dep[maxn],fa[maxn],id[maxn];
int info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
bool cmp(const int &u,const int &v){ return dep[u]>dep[v]; }
void dfs(int u,int ff){
id[u] = u;
dep[u] = dep[fa[u] = ff] + 1;
for(int i=info[u],v;i;i=Prev[i])
if((v=to[i])!=ff)
dfs(v,u);
}
int hd[maxn],cnt;
vector<pair<set<pii>::iterator,int> >cp[maxn];
set<pii>st[maxn];
int main(){
freopen("repulsed.in","r",stdin);
freopen("repulsed.out","w",stdout);
scanf("%d%d%d",&n,&s,&k);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),Node(u,v),Node(v,u);
dfs(1,0);
sort(id+1,id+1+n,cmp);
for(int i=1;i<=n;i++){
int u = id[i];
pii ret = mp(0,-1);
int pr=0;
for(int j=u,stp=0;stp<=k && j;stp++,j=fa[pr=j]){
set<pii>::iterator it = st[j].upper_bound(mp(k-stp+dep[j],0x3f3f3f3f));
if(it == st[j].begin()) continue;
it--;
ret = max(ret , *it);
}
if(ret.second == -1){
hd[cnt] = s - 1;
if(hd[cnt])
for(int j=pr,stp=0;stp<=k && j;stp++,j=fa[j])
cp[cnt].pb(mp(st[j].insert(mp(dep[pr],cnt)).first,j));
cnt++;
}
else{
int v;
if(!--hd[v=ret.second]){
for(int j=0,sz=cp[v].size();j<sz;j++)
st[cp[v][j].second].erase(cp[v][j].first);
}
}
}
printf("%d\n",cnt);
}