1. 程式人生 > 實用技巧 >Gift

Gift

有一個國家有N個城市和M條道路,這些道路可能連線相同的城市,也有可能兩個城市之間有多條道路。有一天,有一夥強盜佔領了這個國家的所有的道路。他們要求國王獻給他們禮物,進而根據禮物的多少而放棄佔領某些邊。對於每一條道路,強盜都給出了相應的要求,金子gi的數量,銀子si的數量。也就是說若國王給強盜G個金子,S個銀子,那麼他們就會放棄佔領滿足gi<=G and si<=S 的道路。現在國王知道金子、銀子的單價,他想花費錢財購買金銀送給強盜,使強盜放棄一些道路,進而使N個城市能互相到達。但是國王又想花費最少。請你計算最少的花費。

Input

第一行有兩個整數N和M,表示有N個城市M條道路。
第二行有兩個整數G和S,表示購買金子和銀子的價格。

以後M行,每行4整數X,Y,g,s,表示這條道路連線X城市和Y城市,要求g個金子,s個銀子。
100% N<=200,M<=50000

Output

一個整數,表示最少花費。要是沒有滿足的情況,輸出-1。

Sample Input

3 3

2 1

1 2 10 15

1 2 4 20

1 3 5 1

Sample Output

30

sol:
題目大概是說,從一堆邊裡面選出若干條,使得整個圖聯通,且要求選出的邊中最大的g和s最小。
最小生成樹問題。

把所有的邊按照g值升序排序,然後從前往後列舉,每次列舉到一個新的值就把所有能用的邊全部放到另外一個數組裡,然後(kruskal)再把這個陣列按照s值升序排序,做一遍最小生成樹,直到符合條件為止。

幾個優化:

首先,在我們加入一條新的邊的時候,其實沒必要用sort掃一遍,可以把它當成一個棧,從棧頂往下掃,如果新加入的這條邊下面那條邊的s值大於新加入的這條邊的s值,那麼就把他們的位置互換。

然後,在過程中我們發現有一些邊如果這次沒用到,那麼接下來也不可能會用到,所以就沒有必要保留,直接捨棄就好,還能節約空間。也就是說,我們把每次選中的邊都放到陣列的最前端,加一個指標就好。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
struct edge {
    int x, y;
    long long g, s;
} e[
50010]; int n, m; long long g, s; int st[210], top, cnt; long long ans = 0x7fffffffffffffffll; int fa[210]; inline long long read() { long long x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();} while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } void init() { for (int i = 1; i <= n; i++) fa[i] = i; } bool cmp_g(edge a, edge b) { if (a.g == b.g) return a.s < b.s;//金子數量相同按銀子從小到大 return a.g < b.g; } int find(int x) { if (x == fa[x]) return x; return fa[x] = find(fa[x]); } int main() { // freopen("data.in", "r", stdin); n = read(), m = read(), g = read(), s = read(); for (int i = 1; i <= m; i++) { e[i].x = read(), e[i].y = read(), e[i].g = read(), e[i].s = read(); } sort(e + 1, e + m + 1, cmp_g); //按金子的數量,從小到大 for (int i = 1; i <= m; i++) { init();//初始化,並查集清空 ,因為每加一條邊就要求一次最小生成樹 //維護一個棧,棧裡是組成最小生成樹的邊 st[++top] = i, cnt = 0;//加入一條新邊 //對於新邊,所需金子數量增加,我們當然希望銀子數量減少才好 for (int j = top; j >= 2; j--) { if (e[st[j]].s < e[st[j - 1]].s) { swap(st[j], st[j - 1]); }//掃棧 ,所需金子數量確定的情況下,銀子所需數越小的越往棧底走 } //kruskal,每加一條邊,就做一次mst for (int j = 1; j <= top; j++) { int eu = find(e[st[j]].x), ev = find(e[st[j]].y); if (eu == ev) continue; fa[ev] = eu; st[++cnt] = st[j];//將用到的邊放到st陣列中 if (cnt == n - 1) break; } if (cnt == n - 1) { //cout<<e[i].g<<" "<<e[st[cnt]].s<<endl; ans = min(ans, e[i].g * g + e[st[cnt]].s * s); //更新答案 } top = cnt; } if (ans == 0x7fffffffffffffffll) puts("-1"); else printf("%lld", ans); return 0; }