题目链接
题意,给你一个无向图,边分黑白,而且有权值。固定使用k个白边,n-1-k个黑边。问:最小生成树。
题解:
不看题解,再给本憨批800年也想不出这种解法。首先说做法:我们把所有白边都加上一个值C(可为负数),则此时显然在排序后白边的位置会整体往前/后”偏移“一段距离。我们二分C的值,然后求最小生成树,直到而分出一个刚好用k条白边的情况。这个最小生成树的值即为所求。
粗略证明:
此部分证明是我自己推定的,,,可能会有错误,酌情参考
首先考虑最小生成树,但是这时白边的数量可能不符合要求。假定现在白边选多了,我们就试图把所有的白边权值加上一个值C,显然可能会导致白边的数量减少。因为,我们在给白边加值的时候,会导致一些白边被换到某些黑边后面,也就是说,再考虑这条白边的时候,会优先考虑被换过的黑边。这样做是不是最优的?也就是说会不会存在这么一个生成树:把二分的结果(即使用了k条白边的结果)中,个别白边先前或者向后移动,得到的最小生成树,它仍可以保证使用K条白边,而且比整体+C所产生的生成树权值要小。——答案是不存在的,
此时存在两种情况:
(1)一个没有被选的白边向前移动后被选中了。
(2)一个被选中的白边向后移动被黑边枪了位置。
先看第一种情况:
首先说一个引理:
根据kruskal算法性质,在考虑到第i条时,只要保证前i条边集合是不考虑权值相同的,那么此时图的联通性一样。
这点在最小生成树计数有应有。
所有如果我们考虑第i个边,如果它是一条白边,我们把它的位置先前移动,到了第j位置,那么此时,考虑前i条边,因为考虑的边集没有发生变化,所有在考虑完前i条边时,图的联通性没有发生改变。所有对于从i+1到m的边,我们的选择不会发生改变。而对于1到j-1的边,显然我们的选择不会发生改变,而对于区间j到i的边,我们产生了如下改变:多选了一个白边,少选了一个黑边。显然此时,白边选了k+1个,不合要求。
同理可判第二中情况不符合要求。
ps.如果你是在中石油大学oj上交题,请注意,中石油大学oj时限奇短(HDU此题时限为15s,石油大学是1s),以至于
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)的算法是被卡掉的。所有每次二分要把里面的排序提到外面来。
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)可以过,但是700+ms。注意常数!
下面是ac代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include <map>
#include <queue>
#include <set>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <bitset>
#include <array>
#include <cctype>
#include <time.h>
#pragma GCC optimize(2)
void read_f() { freopen("1.in", "r", stdin); freopen("1.out", "w", stdout); }
void fast_cin() { std::ios::sync_with_stdio(false); std::cin.tie(); }
void run_time() { std::cout << "ESC in : " << clock() * 1000.0 / CLOCKS_PER_SEC << "ms" << std::endl; }
template <typename T>
bool bacmp(const T & a, const T & b) { return a > b; }
template <typename T>
bool pecmp(const T & a, const T & b) { return a < b; }
#define ll long long
#define ull unsigned ll
#define _min(x, y) ((x)>(y)?(y):(x))
#define _max(x, y) ((x)>(y)?(x):(y))
#define max3(x, y, z) ( max( (x), max( (y), (z) ) ) )
#define min3(x, y, z) ( min( (x), min( (y), (z) ) ) )
#define pr(x, y) (make_pair((x), (y)))
#define pb(x) push_back(x);
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5+5;
const int dir[2][4] = {{1, -1, 0, 0}, {0, 0, 1, -1}};
int fa[N];
int fi(int x)
{
if (fa[x] == x) return x;
return fa[x] = fi(fa[x]);
}
struct Node
{
int x, y, w, z;
}ed[N], f[N], g[N];
bool cmp(Node a, Node b)
{
if (a.w != b.w) return a.w < b.w;
return a.z < b.z;
}
ll ans = 0;
int n, m, k;
int cntg, cntf;
int che(int mid)
{
int c = 0;
for (int i = 1; i <= cntf; i++)
f[i].w += mid;
int l = 1, r = 1;
for (int i = 1; i <= m; i++)
{
if (f[l].w <= g[r].w)
{
ed[i] = f[l]; l++;
ed[i].z = 0;
}
else
{
ed[i] = g[r]; r++;
ed[i].z = 1;
}
}
int sum = 0;
int cnt = 0;
int cp = 0;
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++)
{
int _x = fi(ed[i].x), _y = fi(ed[i].y);
if (_x != _y)
{
fa[_x] = _y;
sum += ed[i].w;
if (ed[i].z == 0)
cnt++;
cp++;
}
if (cp == n - 1) break;
}
for (int i = 1; i <= cntf; i++) f[i].w -= mid;
ans = sum;
return cnt;
}
int main()
{
int t0 = 1;
while(scanf("%d%d%d", &n, &m, &k) != EOF)
{
cntg = cntf = 0;
for (int i = 1; i <= m; i++)
{
int x, y, z, w;
scanf("%d%d%d%d", &x, &y, &w, &z);
x++; y++;
if (z == 0) f[++cntf].x = x, f[cntf].y = y, f[cntf].w = w;
if (z == 1) g[++cntg].x = x, g[cntg].y = y, g[cntg].w = w;
}
sort(f+1, f+cntf+1, cmp);
sort(g+1, g+cntg+1, cmp);
f[cntf+1].w = inf; g[cntg+1].w = inf;
int res;
int l = -100, r = 100;
while(l <= r)
{
int mid= (l +r) >> 1;
if (che(mid) >= k)
{
l = mid + 1;
res = ans - k * mid;
}
else
{
r = mid - 1;
}
}
printf("Case %d: %d\n",t0++, res);
}
return 0;
}