【今天没有标题】2024.3.8

文章讨论了一种编程竞赛中的问题,涉及在给定树结构中,如何通过二分查找和倍增策略,使得所有节点间不存在直接联通路径,同时计算最小时间消耗。作者详细介绍了DFS和BFS算法的应用以及如何避免不必要的棋子位置考虑,优化了求解方法。
摘要由CSDN通过智能技术生成

2024.3.8 【“草色烟光残照里,无言谁会凭阑意”】

Friday 正月二十八 妇女节


<BGM = “Light Above The Sky–Delos”>

P1084 NOIP2012 提高组 疫情控制
//2024.3.8
//by white_ice
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int oo=6e4;
int n,m,t;
int at,bt,ct;
int d[oo],p[oo],f[oo][20];

bool flag,vis[oo],need[oo];
ll ans,idx[oo],use[oo],dis[oo][20];

pair<ll,int> h[oo];
queue<int> q;

int v[oo<<1],w[oo<<1],nxt[oo<<1],head[oo],top;
void add(int x,int y,int z){v[++top]=y,w[top]=z,nxt[top]=head[x],head[x]=top;}

void bfs(){
	q.push(1);
	d[1]=1;
	while(q.size()){
		int x=q.front();q.pop();
		for(int i=head[x];i;i=nxt[i]){
			int y=v[i];
			if(d[y])
				continue;
			d[y]=d[x]+1;
			f[y][0]=x,dis[y][0]=w[i];
			for(int j=1;j<=t;j++){
				f[y][j]=f[f[y][j-1]][j-1];
				dis[y][j]=dis[y][j-1]+dis[f[y][j-1]][j-1];
			}
			q.push(y);
		}
	}
}

bool dfs(int x){
	bool pson=0;
	if(vis[x])
		return 1;
	for(int i=head[x];i;i=nxt[i]){
		int y=v[i];
		if(d[y]<d[x])
			continue;
		pson=1;
		if(!dfs(y))
			return 0;
	}
	if(!pson)
		return 0;
	return 1;
}

bool check(ll lim){
	memset(vis,0,sizeof(vis));
	memset(idx,0,sizeof(idx));
	memset(use,0,sizeof(use));
	memset(h,0,sizeof(h));
	memset(need,0,sizeof(need));
	at=0,bt=0,ct=0;
	for(int i=1;i<=m;i++){
		ll x=p[i],cnt=0;
		for(int j=t;j>=0;j--)
			if(f[x][j]>1 && cnt+dis[x][j]<=lim){
				cnt+=dis[x][j];
				x=f[x][j];
			}
		if(f[x][0]==1 && cnt+dis[x][0]<=lim)
			h[++ct]=make_pair(lim-cnt-dis[x][0],x);
		else vis[x]=1;
	}
	for(int i=head[1];i;i=nxt[i])
		if(!dfs(v[i]))
			need[v[i]]=1;
	sort(h+1,h+ct+1);
	for(int i=1;i<=ct;i++)
		if(need[h[i].second]&&h[i].first<dis[h[i].second][0])
			need[h[i].second]=0;
		else idx[++at]=h[i].first;
	for(int i=head[1];i;i=nxt[i])
		if(need[v[i]])
			use[++bt]=dis[v[i]][0];
	if(at<bt)
		return 0;

	sort(idx+1,idx+at+1);
    sort(use+1,use+bt+1);
	int i=1,j=1;
	while(i<=bt && j<=at)
		if(idx[j]>=use[i])
			i++,j++;
		else
			j++;
	if(i>bt)
		return 1;
	return 0;
}

int main(){
	ll l=0,r=0,mid;
	cin>>n;
	t=log2(n)+1;
	for(int i=1;i<n;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
        add(y,x,z);
		r+=z;
	}
	bfs();
	cin>>m;
	for(int i=1;i<=m;i++)
		scanf("%d",&p[i]);
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)){
			r=mid-1;
			ans=mid;
			flag=1;
		}
		else l=mid+1;
	}
	if(!flag) cout<<-1;
	else cout<<ans;
	return 0;
}

从题目可以看出,这是一道十分明显的树上问题,将特定点上的棋子沿路径移动,使根节点(1号节点)与任何叶子节点之间都没有联通路,将运动棋子的时间规定为路径长度,多个棋子可同时移动,求达到目标的最短耗时。
先思考最终答案求解,不难发现对于一个时间,如果比它短的可以实现,那么它也必然可以实现,所以可以通过食用二分的方法求解答案我不喜欢二分!
这是二分过程:

	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)){
			r=mid-1;
			ans=mid;
			flag=1;
		}
		else l=mid+1;
	}
	if(!flag) cout<<-1;
	else cout<<ans;

下面我们对树的性质进行考虑,对于一棵树,我们可以让棋子的位置尽量高(靠近根)以实现覆盖更多叶子节点,所以等时间下,我们只需要考虑棋子向上移动就可以了。
那我们对于每一个棋子,在二分过程中,只要让它上升到最大高度,如果到了根节点的子节点就停下就好了呀。
但是我们很快会发现,对于一颗本来没有棋子的子树,是不能通过上移实现目标的,所以需要从其他子树“借”棋子。
通过上移后,对已经实现覆盖的子节点进行记录,再移动最近的一颗棋子到未覆盖的子树上就可以了
然后我就止步于此了。。。。
在仔细研读大佬的文章后,我得到了一个十分好用的方法:
记录下每一个棋子移动后的剩余时间,不需要考虑棋子所在的位置,只要时间上足够就可以。
以下是dfs求可行性过程:

bool dfs(int x){
	bool pson=0;
	if(vis[x])
		return 1;
	for(int i=head[x];i;i=nxt[i]){
		int y=v[i];
		if(d[y]<d[x])
			continue;
		pson=1;
		if(!dfs(y))
			return 0;
	}
	if(!pson)
		return 0;
	return 1;
}

同时我们不难看出,使棋子向上移动,是十分浪费时间的,我们便可以使用这个:

倍增!

通过bfs预处理祖先距离,使用倍增存储就会显得时间十分充裕了。

void bfs(){
	q.push(1);
	d[1]=1;
	while(q.size()){
		int x=q.front();q.pop();
		for(int i=head[x];i;i=nxt[i]){
			int y=v[i];
			if(d[y])
				continue;
			d[y]=d[x]+1;
			f[y][0]=x,dis[y][0]=w[i];
			for(int j=1;j<=t;j++){
				f[y][j]=f[f[y][j-1]][j-1];
				dis[y][j]=dis[y][j-1]+dis[f[y][j-1]][j-1];
			}
			q.push(y);
		}
	}
}
  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值