欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NOIP模拟赛20191026 T3 Repulsed【树上控制型贪心】

程序员文章站 2022-03-25 20:26:31
...

题目描述:

n个点的树,每个节点可以放置任意个灭火器,每个灭火器可以浇灭距离不超过k条边的火,且最多只能浇灭s个点的火。现在n个点全都有火,问把火全部浇灭的最少灭火器数量。

n<=100000,k<=20,s<=109

题目分析:

这个题,如果没有s的限制,就是个提高-的贪心题。可以dfs从下往上传最浅的灭火器和最深的没有被扑灭的火,然后匹配或者新加灭火器,复杂度O(n)。或者按照深度从大到小枚举点,检查往上k个点是否有灭火器,如果没有就放一个然后更新再往上的k个点(查询时检验深度之和,具体方法见下),复杂度O(nk)。

现在有了s的限制怎么做,考虑同样的方法。

先看看dfs是否可行,我们需要知道子树中还未被扑灭的火的深度,除此之外还要知道对应的数量,以及灭火器的深度及还可以用的数量。
先考虑从下往上传时用堆维护,首先,如果有离当前点距离为k的点时,显然需要在当前点放置灭火器。
然后,很自然地会想到让子树内的火和灭火器配对,但是在有个数限制的情况下子树内配对并不是最优的,会出现这样的情况:
NOIP模拟赛20191026 T3 Repulsed【树上控制型贪心】
所以O(nlogn)的方法行不通,但是当灭火器和火的距离之和为k以及k-1时,一定会在子树内匹配:
NOIP模拟赛20191026 T3 Repulsed【树上控制型贪心】
而当灭火器和火的距离<=k-2时,我们可以把问题留到当前点的父亲去解决,并不会有什么影响。
所以我们可以记录f[i][j]f[i][j]g[i][j]g[i][j]分别表示ii子树内距离iijj的灭火器和火的数量,然后将距离和为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个点为uu,那么在uu点新加一个灭火器,同时用u点的灭火器编号插入u往上1~k个点的set中,set按编号对应深度排序,查找时就在set中查找能够配对的最深的灭火器配对(不删除),一个全局数组记录每个编号灭火器的个数,当个数减为0时就将该编号从u往上1~k个点的set中删去。

看似十分可行,需要检验正确性的就是"找能够配对的最深的灭火器配对"这一点:
NOIP模拟赛20191026 T3 Repulsed【树上控制型贪心】
(%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);
}