搜索处理第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();
}
}