修路好像不是很容易
TimeLimit: 50000/15000 MS (Java/Others) MemoryLimit: 32768/32768 K(Java/Others) 64-bit integer IO format:%I64d Problem Description
道一打算在他的城市修路,路有n-1条(M条路选出n-1条)必须经过n个点,每条路有两种情况,一是有顺便造了公厕的,二是没有造公厕的,每条路都有它自己的花费,道一想让k条路是有公厕的,并且花费最少。
Input 每组数据第一行包括 N (1 <= N <= 50,000), 道路数量M(N-1 <= M <= 100,000)
和要求有公厕的道路数量K (0 <= K <= N-1).接下来M行,每行包括 a, b, c, x (0 <= a, b <= N-1,
a != b, 1 <= c <= 100, x 属于{0,1},a,b表示道路连接的两个点,c表示道路的花费,x表示有公厕或者没公厕.
x=0 代表有公厕,x=1代表没公厕)。 Output
对于每组数据,输出最小花费。 SampleInput
2 2 1
0 1 1 1
0 1 2 0
2 2 0
0 1 1 1
0 1 2 0
SampleOutput
Case 1: 2
Case 2: 1
首先,因为要输出花费,所以要求最小生成树,但是题目中路的类型有两种,要保证生成树中特殊路的数量大于等于目标数量k显然不是贪心,用贪心的话,优先生成k条特殊路,若遇上以下情况会wan。
3 4 1
1 2 9 0
1 2 1 1
0 1 11 0
0 1 8 1
会先生成1 2 9 0
再生成 0 1 8 1
解为 17
最优解应该为 12
如果新增一个修正指数int Modified
使特殊边的权重减少(或增加)Modified
就能减小(或增加)特殊边的优先级
此时再生成最小生成树,就能含有不同特殊边数目的最小生成树。
观察可以发现最小生成树含有特殊边的数目和修正指数正相关。故可以用二分来求解修正指数 Modified。
while (l <= r)
{
m = (l + r) / 2;
Modified = m;
Weight = 0;
Nowsum = 0;
if (Minimum_spanning_tree())
{
l = m + 1;
}
else
{
r = m - 1;
if (Nowsum == Aimsum)
ans = Weight + Aimsum * Modified;
}
}
printf("Case %d: %lld\n", k++, ans);
}
int Minimum_spanning_tree()
{
Unionclear();
for (int i = 0; i < Edgesum; i++)
{
if (Edge[i].use == 0) { Edge[i].weight -= Modified; }
}
sort(Edge, Edge + Edgesum, cmp);
Unionsum = Pointsum;
int i = 0;
while (i < Edgesum) {
if (Find(Edge[i].x) != Find(Edge[i].y)) {
UnionConnect(Edge[i].x, Edge[i].y);
if (Edge[i].use == 0) Nowsum++;
Weight += Edge[i].weight;
}i++;
if (Unionsum == 1) break;
}
for (int i = 0; i < Edgesum; i++)
{
if (Edge[i].use == 0)
Edge[i].weight += Modified;
}
return Nowsum < Aimsum;
}
若Nowsum>=Aimsum(不是等于,可能有一种样例,使得我们不可能生成Nowsum==Aimsum)。我们就可以认为这个方案可行,所以记录它的 权重 ,但是这个可行的方案可能不是最优解,但是若Nowsum<Aimsum,则这个方案不可行,因为最小生成树的权重和修正指数程正相关,所以我们找到它的边界的最小上界就行。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
#include<string>
#include <math.h>
#include<cstring>
using namespace std;
#define MaximumOfPoint 100010
#define MaximumOfEdge 100010
int Unionsum;
int Edgesum;
int Pointsum;
int Aimsum; int Modified;
int per[MaximumOfPoint];//并查集
long long Weight;
typedef struct EDGE {
int x, y, weight;
int use;
EDGE(int a, int b, int c,int mode) :weight(c), x(a), y(b), use(mode) {}
EDGE() :weight(), x(), y(), use() {}
}edge;//边类
edge Edge[MaximumOfEdge];//边集
int Find(int x)
{
int tt = x;
while (x != per[x]) x = per[x];
while (tt != x)
{
int t = per[tt];
per[tt] = x;
tt = t;
}
return x;
}//并查集用
void UnionConnect(int x, int y)
{
x = Find(x);
y = Find(y);
if (x != y) {
per[x] = y;
Unionsum--;
}
}//把x所在集合连到y
bool cmp(edge x, edge y) {
if (x.weight != y.weight) {
return x.weight < y.weight;
}
return x.use < y.use;
}
void Unionclear()
{
for (int i = 0; i < Pointsum; i++)
{
per[i] = i;
}
}
int Nowsum = 0;
int Minimum_spanning_tree()
{
Unionclear();
for (int i = 0; i < Edgesum; i++)
{
if (Edge[i].use == 0) { Edge[i].weight -= Modified; }
}
sort(Edge, Edge + Edgesum, cmp);
Unionsum = Pointsum;
int i = 0;
while (i < Edgesum) {
if (Find(Edge[i].x) != Find(Edge[i].y)) {
UnionConnect(Edge[i].x, Edge[i].y);
if (Edge[i].use == 0) Nowsum++;
Weight += Edge[i].weight;
}i++;
if (Unionsum == 1) break;
}
for (int i = 0; i < Edgesum; i++)
{
if (Edge[i].use == 0)
Edge[i].weight += Modified;
}
return Nowsum < Aimsum;
}
int main()
{
int n, m, i, x, y, w, mode, k;
k = 1;
while (~scanf("%d%d%d", &n, &m, &Aimsum))
{
i = 0;
Pointsum = n;
Edgesum = m;
Unionsum = n;
while (i < m)
{
scanf("%d%d%d%d", &x, &y, &w, &mode);
Edge[i] = (edge(x, y, w, mode));
i++;
}
int l = -102, m, r = 102; long long ans = 0, o;
while (l <= r)
{
m = (l + r) / 2;
Modified = m;
Weight = 0;
Nowsum = 0;
if (Minimum_spanning_tree())
{
l = m + 1;
}
else
{
r = m - 1;
if (Nowsum == Aimsum)
ans = Weight + Aimsum * Modified;
}
}
printf("Case %d: %lld\n", k++, ans);
}
}
题目来自FJUT-1510