二分+搜索 HDU6041 I Curse Myself 详解

搜索处理第k小值

- 首先二分目标值,比如最小生成树的权重
- 统计满足二分值搜索到的符合条件的个数
- 检验是否大于等于 k

HDU6041 I Curse Myself

题意

给出一个仙人掌图,令第k小生成树的权重为 V ( k ) V(k) V(k)
∑ i = 1 k V ( k ) \sum_{i=1}^{k}V(k) i=1kV(k)

分析

用到我们上面的方法,我们在搜索第k小的过程中肯定也能搜索1-k小的
所以问题转化成搜索第k小生成树

由于问题是个仙人掌树,求仙人掌树的最小生成树只需要找到仙人掌图上的环,删除环上一条边即可
(不知道仙人掌树的请百度)

那么所求问题的补集就是从每个环中删除一个元素,求出删除元素总和中的第 K 大
问题等价于从 m 个数组中每个数组选出一个数字求和,求这些 和中第 k 大元素

我们将每个数组中的元素转化为和数组中最大数字的差值,可以将数组中元素数量减少一,并将 k 大值问题转化为 k 小值问题

二分值,统计二分值的个数,检验 是否大于等于 k

代码详解

Tarjan判环+提取边

void Tarjan(int u) {	//提取环上边权
	dfn[u] = ++dfs_num;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].v, w = edge[i].w;
		if (low[u].first == v) continue;	//重边
		if (!dfn[v]) {
			low[v].first = u;		//记录前一个点和边权
			low[v].second = w;
			Tarjan(v);
		}
		else if (dfn[v] < dfn[u]) {	//说明成环
			E[cnt].push_back(w);
			for (int j = u; j != v; j = low[j].first)
				E[cnt].push_back(low[j].second);	//将边存入vector
			cnt++;
		}
	}
	return;
}

预处理+问题的转化

void Pre_Work() {	//预处理
	for (int i = 0; i < cnt; i++) {
		sort(E[i].begin(), E[i].end(), cmp_max);
		//将所有边处理成与最大边的差值(搜索时少处理一位)
		sum -= E[i][0];
		for (int j = E[i].size() - 1; ~j; j--)
			E[i][j] = E[i][0] - E[i][j];
	}
	sort(E, E + cnt, cmp_min);	//搜索顺序优化,已知最小生成树,先加最小的
	scanf("%d", &k);
	LL Max = 1;
	for (int i = 0; i < cnt; i++) {
		Max *= E[i].size();
		if (Max > 1000000) {
			Max = 1000000;
			break;
		}
	}
	k = min(k, int(Max));	//和最大的k取最小
}

核心,二分权重+搜索

int Left, Right, mid;
int res[maxans], p, t;	//p为方案总数
void dfs(int s, int x) {
	if (x == cnt || p >= k)return;	//超过环数或者找到超过k个方案
	if (s + E[x][1] > mid)return;
	for (int i = 1; i < E[x].size(); i++) {
		int ans = s + E[x][i];
		if (ans > mid)break;
		if (p >= k) return;
		res[++p] = ans;
		dfs(ans, x + 1);
	}
	dfs(s, x + 1);	//不取点
}
void Search() {
	Left = 0; Right = inf;		//枚举加上的权重
	while (Left <= Right) {
		mid = (Left + Right) >> 1;
		p = 1;
		dfs(0, 0);
		if (p >= k) {
			t = mid;
			Right = mid - 1;
		}
		else Left = mid + 1;
	}
}

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
typedef pair<int, int> pii;
typedef long long LL;
const int maxn = 1010;
const int maxm = 2005;
const int maxans = 100010;
const int inf = 1e9;
const LL mod = LL(1) << 32;
int n, m, sum;
int head[maxn], tot;
struct Edge
{
	int v;
	int w;
	int next;
}edge[maxm << 1];
inline void AddEdge(int u, int v, int w) {
	edge[++tot].v = v;
	edge[tot].w = w;
	edge[tot].next = head[u];
	head[u] = tot;
}
void Read() {	//读入数据,并求和
	int u, v, w;
	while (m--) {
		scanf("%d%d%d", &u, &v, &w);
		AddEdge(u, v, w);
		AddEdge(v, u, w);
		sum += w;
	}
}
int dfs_num, cnt, dfn[maxn];
pii low[maxn];
vector<int> E[maxn];
bool cmp_max(int a, int b) {
	return a > b;
}
void Tarjan(int u) {	//提取环上边权
	dfn[u] = ++dfs_num;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].v, w = edge[i].w;
		if (low[u].first == v) continue;	//重边
		if (!dfn[v]) {
			low[v].first = u;		//记录前一个点和边权
			low[v].second = w;
			Tarjan(v);
		}
		else if (dfn[v] < dfn[u]) {	//说明成环
			E[cnt].push_back(w);
			for (int j = u; j != v; j = low[j].first)
				E[cnt].push_back(low[j].second);	//将边存入vector
			cnt++;
		}
	}
	return;
}
int k;

bool cmp_min(const vector<int>& a, const vector<int>& b) {
	return a[1] < b[1];
}
void Pre_Work() {	//预处理
	for (int i = 0; i < cnt; i++) {
		sort(E[i].begin(), E[i].end(), cmp_max);
		//将所有边处理成与最大边的差值(搜索时少处理一位)
		sum -= E[i][0];
		for (int j = E[i].size() - 1; ~j; j--)
			E[i][j] = E[i][0] - E[i][j];
	}
	sort(E, E + cnt, cmp_min);	//搜索顺序优化,已知最小生成树,先加最小的
	scanf("%d", &k);
	LL Max = 1;
	for (int i = 0; i < cnt; i++) {
		Max *= E[i].size();
		if (Max > 1000000) {
			Max = 1000000;
			break;
		}
	}
	k = min(k, int(Max));	//和最大的k取最小
}
int Left, Right, mid;
int res[maxans], p, t;
LL Ans;
void dfs(int s, int x) {
	if (x == cnt || p >= k)return;	//超过环数或者找到超过p个方案
	if (s + E[x][1] > mid)return;
	for (int i = 1; i < E[x].size(); i++) {
		int ans = s + E[x][i];
		if (ans > mid)break;
		if (p >= k) return;
		res[++p] = ans;
		dfs(ans, x + 1);
	}
	dfs(s, x + 1);	//不取点
}
void Search() {
	Left = 0; Right = inf;		//枚举加上的权重
	while (Left <= Right) {
		mid = (Left + Right) >> 1;
		p = 1;
		dfs(0, 0);
		if (p >= k) {
			t = mid;
			Right = mid - 1;
		}
		else Left = mid + 1;
	}
	mid = t - 1; p = 0;		//重新计算加上权重t的情况
	if (mid >= 0)res[++p] = 0;
	dfs(0, 0);
	for (int i = p + 1; i <= k; i++)
		res[i] = t;
	sort(res + 1, res + 1 + k);
	for (int i = 1; i <= k; i++) {
		res[i] += sum;
		Ans = (Ans + LL(res[i]) * i % mod) % mod;
	}
}
void init() {
	fill(dfn, dfn + 1 + n, 0);
	fill(head, head + 1 + n, 0);
	fill(low, low + 1 + n, pii(0, 0));
	for (int i = 0; i < cnt; i++)E[i].clear();
	cnt = tot = dfs_num = sum = Ans = 0;
}
int main() {
	int g = 0;
	while (~scanf("%d%d", &n, &m)) {
		Read();
		Tarjan(1);
		Pre_Work();
		Search();
		printf("Case #%d: %lld\n", ++g, Ans);
		init();
	}
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值