2018 SEERC C. Tree (树的直径的性质)

题意:
给定你 n n n个节点,分别涂有黑色或者白色, 0 0 0表示白色, 1 1 1表示黑色。然后给定你一个 m m m,问你能找到 m m m个黑色节点使得他们之间任意两点间的距离的最大值最下为多少,输出这个最小值。
输入保证一定存在答案,保证 n n n个节点构成一棵树。
思路:
首先应该知道树的直径的定义:

a. 树上任意两点间的简单路径的最大值
b.若树的直径的端点为是 s , t s,t st,则任意点 x x x到其余点的最远点一定在 s s s或者 t t t
c.若直径端点为 s , t s,t s,t,两点 x , y , d i s [ s ] [ x ] ≤ d i s [ s ] [ t ] & & d i s [ x ] [ t ] ≤ d i s [ s ] [ t ] x,y,dis[s][x] \leq dis[s][t] \&\&dis[x][t] \leq dis[s][t] xydis[s][x]dis[s][t]&&dis[x][t]dis[s][t],同理 d i s [ s ] [ y ] ≤ d i s [ s ] [ t ] & & d i s [ y ] [ t ] ≤ d i s [ s ] [ t ] dis[s][y] \leq dis[s][t] \&\&dis[y][t] \leq dis[s][t] dis[s][y]dis[s][t]&&dis[y][t]dis[s][t],则 d i s [ x ] [ y ] ≤ d i s [ s ] [ t ] dis[x][y] \leq dis[s][t] dis[x][y]dis[s][t]

第三个定理呢画画图就可证明了。

解法一:
有了上面三个定理支持,我们就可以枚举两点 i , j i,j i,j之间的距离并假设其为树的直径的端点,然后再去枚举其他点 k k k,如果这个点 k k k i , j i,j i,j的距离满足第三个式子,那么就把它加入当前点的集合,如果最后的点的集合内的黑色点的个数超过了 m m m,那么就更新一遍答案,最后求最小值即可。

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 100 + 10;
int col[N],n,m;
int head[N],tot;
struct edge {
	int next,to;
}edge[N<<1];
void addedge(int u,int v) {
	edge[++tot].to = v,edge[tot].next = head[u],head[u] = tot;
	edge[++tot].to = u,edge[tot].next = head[v],head[v] = tot;
}
//要用到一个类似于树的直径的性质
int dis[N][N];
void dfs(int rt,int u,int fa,int d) {
	dis[u][rt] = d;
	for(int i = head[u];i;i = edge[i].next) {
		int v = edge[i].to;
		if(v == fa) continue;
		dfs(rt,v,u,d+1);
	}
}
int ans;
void solve() {
	int cnt;
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= n;j ++) {//假设枚举的这两个点间的距离就是最远距离
			if(j == i) continue;
			if(col[j] && col[i]) {
				// cout << i << ' ' << j << ' ' << dis[i][j] << '\n';
				if(j != i) cnt = 2;
				else cnt = 1;
				for(int k = 1;k <= n;k ++) {
					if(k == i || k == j) continue;
					if(col[k] && dis[i][k] <= dis[i][j] && dis[k][j] <= dis[i][j]) {//这个判断保证了假如进来的k不破坏这个最长距离
						cnt++;
					}
				}
				if(cnt >= m) {
					ans = min(ans,dis[i][j]);
				}
			}
		}
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i ++) {
		scanf("%d",&col[i]);
	}
	int a,b;
	for(int i = 1;i < n;i ++) {
		scanf("%d%d",&a,&b);
		addedge(a,b);
	}
	for(int i = 1;i <= n;i ++) {
		if(col[i]) dfs(i,i,0,0);
	}
	ans = INF;
	solve();
	printf("%d\n",ans);
	return 0;
}

解法二:

看到最大值最小我们可以想到,二分解决,我们去二分这个最大值 x x x,很明显加入我们能保证当前的点组成的一棵树的直径的值等于 x x x,那么这棵树终点的任意两点距离一定比 x x x小,此时我们再统计这棵树的黑色节点个数,与 m m m比较,即可得知是否合法。

那么怎么确定一棵直径为 x x x的树呢,这里要设计树的直径怎么求,其中一种方法就是,随机选取一个点 o o o,然后 b f s bfs bfs求出这个点能到达的最远点 s s s,然后再从 s s s出发做一次 b f s bfs bfs求出到达的最远点 t t t,此时 s − t s-t st即为树的直径。

这样我们对于每次二分的最大值 x x x,随机选一个点开始做 b f s bfs bfs,然后每走到一个点,我们就可以假设这个点为当前能到达的最远点,此时再去对之前遍历过的节点做 b f s bfs bfs,注意一定是前面遍历过的点,否则就无法保证这个点是最远点了,然后如果某一层到当前的点的距离大于 x x x后就退出,此时就找到了,当前这棵树以 x x x为直径能到的最远点,此时遍历过的点都处于一个集合中,并且任意两点的距离不会大于 x x x,然后就看集合中的黑色节点大于 m m m嘛,大于就是合法。

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 100 + 10;
int n,m,col[N];
std::vector<int>G[N];
void addedge(int u,int v) {
	G[u].pb(v);
	G[v].pb(u);
}
int vis[N],vis2[N],dep[N];
int bfs(int s,int aim) {
	memset(dep,0,sizeof(dep));
	memset(vis2,0,sizeof(vis2));
	int cnt = 0;
	queue<int>q;
	q.push(s);
	vis2[s] = 1;
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		if(dep[u] > aim) break;
		if(col[u]) cnt++;
		for(int i = 0;i < sz(G[u]);i ++) {
			int v = G[u][i];
			if(vis2[v] || !vis[v]) continue;
			vis2[v] = 1;
			dep[v] = dep[u] + 1;
			q.push(v);
		}
	}
	return cnt;
}
bool check(int x) {
	memset(vis,0,sizeof(vis));
	queue<int>q;
	vis[1] = 1;
	q.push(1);
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		for(int i = 0;i < sz(G[u]);i ++) {
			int v = G[u][i];
			if(vis[v]) continue;
			// cout << u << ' ' << v << '\n';
			int tt = bfs(v,x);
			// cout << "*****" << v << ' ' << tt << ' ' << x << '\n';
			if(tt >= m) return true;
			vis[v] = 1;
			q.push(v);
		}
	}
	return false;
}

int main() {
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i ++) scanf("%d",&col[i]);
	int a,b;
	for(int i = 1;i < n;i ++) {
		scanf("%d%d",&a,&b);
		addedge(a,b);
	}
	if(n == 1) {
		return printf("0\n"),0;
	}
	//二分最长距离 
	//但其实还是利用了树的直径那个方法和思想 对于每个点相当于重建一棵树 保证这棵树的直径小于等于k
	//那么这棵树之间的任意点的距离是不会超过k的
	int l = 0,r = n,ans;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(check(mid)) {
			ans = mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	printf("%d\n",ans);
	return 0;
}
/*
6 3 
1 1 0 1 1 1
1 2
1 3
1 4
3 5
3 6

9 4 
1 0 1 0 1 0 0 1 1
1 2
2 4
2 3
4 5
1 6
6 7
6 8
7 9

1 1
1
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值