專題鏈接
A - 還是暢通工程
題解: n個村,m條路,要用最少的錢把所有村連接起來,MST的模板題,提供兩種算法模板。
//使用Kruskal算法
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 105;
int seed[N]; //構建並查集
int find_root(int x){
return seed[x] < 0? x : seed[x] = find_root(seed[x]);
}
int join_seed(int a, int b){
a = find_root(a), b = find_root(b);
if(a == b) return 0;
else seed[b] = a;
return 1;
}
struct E{ // 定義邊
int u, v, cost;
}edg[N*N];
int ecnt;
void init(){ // 初始化
memset(seed, -1, sizeof(seed));
ecnt = 0;
}
void add(int u, int v, int w){
edg[ecnt].u = u, edg[ecnt].v = v, edg[ecnt++].cost = w;
}
bool cmp(E a, E b){
return a.cost < b.cost;
}
int main(){
int n;
while(scanf("%d", &n) != EOF && n != 0){
init();
int a, b, c;
for(int i = n*(n-1)/2; i > 0; --i){
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
sort(edg, edg+ecnt, cmp);
int ans = 0;
for(int i = 0; i < ecnt; ++i){
if(join_seed(edg[i].u, edg[i].v)) ans += edg[i].cost;
}
printf("%d\n", ans);
}
}
//使用prim算法
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int INF = ~0u>>2;
const int N = 105;
int G[N][N];
int dis[N];
void init(int n){
for(int i = 0; i <= n; ++i) dis[i] = INF;
for(int i = 0; i <= n; ++i)
for(int j = 0; j <= n; ++j)
G[i][j] = INF;
}
int prim(int rt, int n){
int vis[N] = {0};
dis[rt] = 0;
int res = 0;
for(int i = 0; i < n; ++i){
int min_u, min_dis = INF;
for(int j = 1; j <= n; ++j){ //找最小花費的點
if(vis[j] == 0 && dis[j] < min_dis){
min_dis = dis[j];
min_u = j;
}
}
vis[min_u] = 1;
res += min_dis;
for(int j = 1; j <= n; ++j){ //用最小點去更新其他點
if(vis[j] == 0 && dis[j] > G[min_u][j]){
dis[j] = G[min_u][j];
}
}
}
return res;
}
int main(){
int n;
while(scanf("%d", &n) != EOF && n != 0){
init(n);
for(int i = n*(n-1)/2; i >= 1; --i){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(G[a][b] > c) G[a][b] = G[b][a] = c;
}
int ans = prim(1, n);
printf("%d\n", ans);
}
}
B - 暢通工程再續
題意: n個點,給出的是座標,需要考慮建圖了,按規則建圖,然後求MST。
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int N = 105;
int seed[N];
int find_root(int x){
return seed[x] < 0? x : seed[x] = find_root(seed[x]);
}
int join_seed(int a, int b){
a = find_root(a), b = find_root(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
struct E{
int u, v, cost;
}edg[N*N];
int ecnt;
void init(){
memset(seed, -1, sizeof(seed));
ecnt = 0;
}
void add(int u, int v, int w){
edg[ecnt].u = u, edg[ecnt].v = v, edg[ecnt++].cost = w;
}
bool cmp(E a, E b){
return a.cost < b.cost;
}
int pnt[N][2];
int CalDist(int a, int b){ //兩點距離的平方
int x = pnt[a][0] - pnt[b][0];
int y = pnt[a][1] - pnt[b][1];
return x*x + y*y;
}
int main(){
int n, T;
scanf("%d", &T);
while(T--){
init();
scanf("%d", &n);
for(int i = 1; i <= n; ++i){
scanf("%d%d", &pnt[i][0], &pnt[i][1]);
}
for(int i = 1; i <= n; ++i){
for(int j = i+1; j <= n; ++j){
int dist = CalDist(i, j);
if(dist >= 10*10 && dist <= 1000*1000){
add(i, j, dist); //避免浮點數誤差
}
}
}
sort(edg, edg+ecnt, cmp);
double ans = 0;
for(int i = 0; i < ecnt; ++i){
if(join_seed(edg[i].u, edg[i].v)) ans += sqrt(edg[i].cost);
}
int flag = 0;
for(int i = 1; i <= n; ++i) if(seed[i] < 0) flag += 1;
if(flag >= 2) puts("oh!"); //構成MST的條件是並查集中最多存在一個根節點
else printf("%.1f\n", 100*ans);
}
}
C - Highways
題意: n個點,給出的是座標,並且已經存在有m條邊。現在要加入另外一些邊,用最小的花費讓n個點都連通,容易想到貪心的方法,每次都先加花費最小的邊,就是Kruskal的過程。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 755;
int seed[N];
inline int find_root(int x){ return seed[x] < 0? x : seed[x] = find_root(seed[x]); }
inline int join_seed(int a, int b){
a = find_root(a), b = find_root(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
struct eg{
int u, v, w;
}tmp;
vector<eg>edg;
inline bool cmp(eg a, eg b){
return a.w < b.w;
}
int pnt[N][2];
inline int CalDist(int a, int b){
int x = pnt[a][0] - pnt[b][0];
int y = pnt[a][1] - pnt[b][1];
return x*x+y*y;
}
int main(){
int n, m;
while(scanf("%d", &n) != EOF){
memset(seed, -1, sizeof(seed));
edg.clear();
for(int i = 1; i <= n; ++i){
scanf("%d%d", &pnt[i][0], &pnt[i][1]);
}
scanf("%d", &m);
for(int i = 1; i <= m; ++i){
int a, b; scanf("%d%d", &a, &b);
join_seed(a, b); //ab已連通,直接join在一起
}
for(int i = 1; i <= n; ++i){
for(int j = i+1; j <= n; ++j){
if(find_root(i) == find_root(j)) continue;
//已有邊連通就不用加邊了
tmp.u = i, tmp.v = j, tmp.w = CalDist(i, j);
edg.push_back(tmp);
}
}
sort(edg.begin(), edg.end(), cmp);
for(int i = 0; i < edg.size(); ++i){
if(join_seed(edg[i].u, edg[i].v)){
printf("%d %d\n", edg[i].u, edg[i].v);
}
}
}
}
D - QS Network
題意:英文題,題意比較難懂,看懂之後還是模板題。
給出每個點的花費,給出圖的鄰接矩陣,一條邊的總花費就是兩個點的花費+邊花費,建完圖求MST就行了。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1005;
struct eg{
int u, v, w;
}tmp;
inline bool cmp(eg a, eg b){
return a.w < b.w;
}
vector<eg>edg;
int ada[N];
int seed[N];
int find_root(int x){
return seed[x] < 0? x : seed[x] = find_root(seed[x]);
}
int join_seed(int a, int b){
a = find_root(a), b = find_root(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
void init(){
edg.clear();
memset(seed, -1, sizeof(seed));
}
int main(){
int T;
scanf("%d", &T);
while(T--){
init();
int n;
scanf("%d", &n);
for(int i = 0; i < n; ++i) scanf("%d", &ada[i]);
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
int cost;
scanf("%d", &cost);
if(i == j) continue;
tmp.u = i, tmp.v = j, tmp.w = cost + ada[i] + ada[j];
edg.push_back(tmp);
}
}
sort(edg.begin(), edg.end(), cmp);
int ans = 0;
for(int i = 0; i < edg.size(); ++i){
if(join_seed(edg[i].u, edg[i].v)){
ans += edg[i].w;
}
}
printf("%d\n", ans);
}
}
E - 連接的管道
題解: 一個 的矩陣,每個元素可以與上下左右的元素相連,要用最小花費連接所有元素,道理也是建好圖然後求MST。
每個元素和上下左右元素建一條邊,這樣一個圖是很稀疏的,使用prim會超時。邊數極限在 以上,內存卡的也很緊,無用的邊儘量不加,能省就省,因爲無向圖的緣故,每個點只對下方和右方的元素加邊就夠了,數字範圍很小,用short省內存,還可以用數組代替vector。
題目來自去年的百度之星。
#include<stdio.h>
#include<vector>
#include<algorithm>
#include<string.h>
using namespace std;
const int MX = 1005;
inline short abs(short x){ return x < 0? -x:x; }
struct eg{
int u, v;
short w;
}tmp;
vector<eg>edg;
inline bool cmp(eg a, eg b){
return a.w < b.w;
}
int seed[MX*MX];
int find_root(int x){
return seed[x] < 0? x : seed[x] = find_root(seed[x]);
}
int join_seed(int a, int b){
a = find_root(a), b = find_root(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
void init(){
edg.clear();
memset(seed, -1, sizeof(seed));
}
void add(int a, int b, int c){
tmp.u = a, tmp.v = b, tmp.w = c;
edg.push_back(tmp);
}
short maz[MX][MX];
int main(){
int T, ca = 1;
scanf("%d", &T);
while(T--){
init();
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
scanf("%d", &maz[i][j]);
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(i+1 <= n){
add( (i-1)*m+j, i*m+j, abs(maz[i][j]-maz[i+1][j]) );
}
if(j+1 <= m){
add( (i-1)*m+j, (i-1)*m+j+1, abs(maz[i][j]-maz[i][j+1]));
}
}
}
sort(edg.begin(), edg.end(), cmp);
int ans = 0;
for(int i = 0; i < edg.size(); ++i){
if(join_seed(edg[i].u, edg[i].v)){
ans += edg[i].w;
}
}
printf("Case #%d:\n%d\n", ca++, ans);
}
}
F - Air Ports
題解: n個點,有m條無向路可以修,選擇一些點修機場,或者修路,用最少的花費讓所有點都能到機場。
如果一個點擁有機場或者可以間接到機場,就說這個點能到機場。
如果兩個點距離很遠,那麼更好的方法就是各修一個機場,否則就修路,這樣可以得到一個貪心的策略。
爲了更優雅的寫代碼,一種做法是當需要修機場時,在這兩個點裏任選一個點修就可以了,因爲並查集需要一個根,沒有修機場的就作爲並查集的根,修了機場的點就合併到根下面。
需要注意的是此時雖然合併了並查集,但只有一個機場,兩個點不一定都能到機場,所以求出MST之後,需要對所有的並查集根補修一個機場,所有的邊都用過了,已經沒有別的辦法讓點能夠到達機場,只能修機場了。
假如有一個點需要合併進來,如果它合併到有機場的部分,那它是連通的,如果它合併到沒修機場的部分,最後對所有根修機場的時候就處理了這種情況,同時還處理了原圖不連通的情況。
#include<bits/stdc++.h>
using namespace std;
struct eg{
int u, v, w;
bool operator < (eg a) const{ //定義eg的 < 符號
return w < a.w;
}
}edg[100005];
int seed[10005];
int find(int x) {
return seed[x] < 0? x : seed[x]=find(seed[x]);
}
int join(int a, int b){
a = find(a), b = find(b);
if(a == b) return 0;
if(seed[a] > seed[b]) seed[a] = b;
else seed[b] = a;
}
int main(){
int T, ca = 1;
scanf("%d", &T);
while(T--){
int n, m, air;
memset(seed, -1, sizeof(seed));
scanf("%d%d%d", &n, &m, &air);
for(int i = 0; i < m; ++i){
scanf("%d%d%d", &edg[i].u, &edg[i].v, &edg[i].w);
}
sort(edg, edg+m);
int ans = 0, cot = 0;
for(int i = 0; i < m; ++i){
if(join(edg[i].u, edg[i].v)){
if(edg[i].w >= air){ //修機場
ans += air, cot += 1;
}
else ans += edg[i].w; // 修路
}
}
for(int i = 1; i <= n; ++i){ //補修機場
if(seed[i] < 0) ans += air, cot += 1;
}
printf("Case %d: %d %d\n", ca++, ans, cot);
}
}
G - Arctic Network
題解: n個點,給出座標,當兩個點距離 時,這兩個點可以連通,另外還可以最多選 個點放置衛星通信,有衛星的點之間連通,現在要讓所有點直接或間接連通,求 的最小值。
注意到題目 ,不會出現 的陷阱,因爲安放一個衛星還不如一個都不安放。
一種容易想到的貪心策略是先求出MST,然後讓MST裏面距離最遠的 條邊,也就是 個點使用衛星通信,答案就是遞增排序後第S-2條邊的長度。
另外還有一種二分的方法。
我們要二分答案,也就是二分 值,二分的根據是什麼?
假如答案爲 ,現在我們的 ,會出現這樣一種狀況,用長度 的邊求出MST,還需要加一些衛星才能要讓所有點直接間接連通,並且我們需要的衛星的個數超過了 ,當 ,顯然需要的衛星個數就小於等於 。
對於任意一個 ,我們都可以知道它在 左邊還是右邊,這樣得到了一個二分策略,只需要不斷逼近臨界的 就可以了。複雜度 比第一種慢一點,但是這種二分答案的思想是很重要的。
其實 ,仍舊是非常高效的。
下面是二分的代碼。
#include<stdio.h>
#include<vector>
#include<algorithm>
#include<math.h>
#include<string.h>
using namespace std;
const int inf = ~0u>>2;
int sate, n;
struct pt{
int x, y;
pt(){}
pt(int a, int b){ x = a, y = b; }
};
struct eg{
int u, v;
double w;
eg(){}
eg(int a, int b, double c){ u = a, v = b, w = c; }
bool operator < (const eg &a) const{
return w < a.w;
}
};
vector<pt>pnt;
vector<eg>edg;
inline double CalDist(int i, int j){
double x = pnt[i].x - pnt[j].x;
double y = pnt[i].y - pnt[j].y;
return sqrt(x*x + y*y);
}
int seed[1005];
int find(int x){ return seed[x] < 0? x : seed[x] = find(seed[x]); }
int join(int a, int b){
a = find(a), b = find(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
int ok(double val){ //跑MST,求出需要的衛星個數
memset(seed, -1, sizeof(seed));
for(int i = 0; i < edg.size(); ++i){
if(edg[i].w <= val) join(edg[i].u, edg[i].v);
}
int res = 0;
for(int i = 0; i < n; ++i) if(seed[i] < 0) res += 1;
return res;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
edg.clear();
pnt.clear();
scanf("%d%d", &sate, &n);
for(int i = 0; i < n; ++i){
int x, y; scanf("%d%d", &x, &y);
pnt.push_back( pt(x, y) );
}
for(int i = 0; i < pnt.size(); ++i){
for(int j = i+1; j < pnt.size(); ++j){
edg.push_back( eg(i, j, CalDist(i,j)) );
}
}
sort(edg.begin(), edg.end());
double l = 0, r = inf, mid;
while(fabs(r-l) > 1e-5){
mid = (l+r)/2;
if(ok(mid) <= sate) r = mid; //衛星足夠,r往左移
else l = mid; // 衛星不夠,l往右移
}
printf("%.2f\n", l);
}
}
H - The Unique MST
題解: n個點,m條邊,現在可以容易地求出一棵MST,問是否存在另外一棵權值相同的MST。
如果一條邊存在於樹 但不存在於 ,我們認爲這兩棵樹不同。
確定MST是否唯一,暴力的做法是先跑出一棵MST,然後枚舉去掉這棵MST的每一條邊,再次跑MST,跑完再還原,看權值是否變化,如果不存在權值沒變的情況,我們可以判定MST唯一,複雜度爲 ,極限爲
此題數據範圍暴力即可。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 105;
const int INF = ~0u>>2;
struct eg{
int u, v, w;
bool operator < (const eg &a) const{
return w < a.w;
}
}tmp;
int n, m;
vector<eg>G;
vector<int>mst;
int seed[N];
int find(int x){ return seed[x] < 0? x : seed[x] = find(seed[x]); }
int join(int a, int b){
a = find(a), b = find(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
int kruskal(int no){
memset(seed, -1, sizeof(seed));
int res = 0, cnt = 0;
for(int i = 0; i < G.size(); ++i){
if(i == no) continue; //這條邊已經去掉
if(join(G[i].u, G[i].v)) res += G[i].w, ++cnt;
}
if(cnt != n-1) return INF;
return res;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
G.clear();
mst.clear();
for(int i = 0; i < m; ++i){
scanf("%d%d%d", &tmp.u, &tmp.v, &tmp.w);
G.push_back(tmp);
}
memset(seed, -1, sizeof(seed));
sort(G.begin(), G.end());
int MST = 0;
for(int i = 0; i < G.size(); ++i){
if(join(G[i].u, G[i].v)){
MST += G[i].w;
mst.push_back(i); // 記錄MST的邊
}
}
int ans = INF;
for(int i = 0; i < mst.size(); ++i){ //枚舉MST的邊
ans = min(ans, kruskal(mst[i]));
}
if(ans == MST) puts("Not Unique!");
else printf("%d\n", MST); // MST唯一
}
}
I - Truck History
題解: 卡題意及建圖。字符串都能建圖,就是這麼酷。
給出n個字符串,每個串7個字符。
任意兩個字符串 之間都能建邊,代價是 這7個位置裏 的位置的個數。
枚舉 ,建完圖跑MST,然後按照格式輸出就可以了。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int N = 2005;
struct eg{
int u, v, w;
eg(){}
eg(int a, int b, int c){ u = a, v = b, w = c; }
bool operator < (const eg &a) const{
return w < a.w;
}
}edg[N*N];
int ecnt;
char str[N][10];
int cal(int a, int b){
int res = 0;
for(int i = 0; i < 7; ++i) if(str[a][i] != str[b][i]) res += 1;
return res;
}
int seed[N];
int find(int x){ return seed[x] < 0? x : seed[x] = find(seed[x]); }
int join(int a, int b){
a = find(a), b = find(b);
if(a == b) return 0;
seed[b] = a;
return 1;
}
int main(){
int n;
while(scanf("%d", &n), n){
ecnt = 0;
memset(seed, -1, sizeof(seed));
for(int i = 0; i < n; ++i) scanf("%s", str[i]);
for(int i = 0; i < n; ++i){
for(int j = i+1; j < n; ++j){
edg[ecnt++] = eg(i, j, cal(i,j));
}
}
sort(edg, edg+ecnt);
int ans = 0;
for(int i = 0; i < ecnt; ++i){
if(join(edg[i].u, edg[i].v)) ans += edg[i].w;
}
printf("The highest possible quality is 1/%d.\n", ans);
}
}
J - Trail Maintenance
題解: n個點, 一開始沒有邊,在m天裏每天給出1條邊,問已經給出了的邊的MST值是多少,如果n個點無法連通就輸出-1。
容易想到暴力的做法,每天都跑一次MST,最後一天的複雜度是熟悉的 ,然而每一天加起來,總複雜度接近 ,並且是多組數據,暴力是無法通過的。
那麼如何優化呢?首先注意到在n-1天之前,答案必定是-1,因爲n個點第一次連通必然出現再n-1天或之後,然而這是個常數優化,複雜度瓶頸並沒有改變,但是可以給我們一些啓示。
假設在第k天時,n個點第一次連通,那麼此時按照我們暴力的做法,需要求一次MST。然而在第k+1天時,我們似乎沒有必要去關注所有的邊,複雜度很高的原因是因爲我們在處理k+1天時,完全沒有利用第k天留下的信息。
假如我們在第k天保存了MST信息,那麼在k+1天時,只需要用第k天的MST和新加的邊就可以求出第k+1天的MST,對於第k天時不在MST裏的邊,在k+1天也不可能出現在MST裏,因爲極端情況下可以不使用新加的邊,完全使用第k天的MST,這樣每次只需要對n條邊跑MST,複雜度是 ,解決。
#include<bits/stdc++.h>
using namespace std;
struct eg{
int u, v, w, id;
bool operator < (eg a) const{
return w < a.w;
}
}edg[6006], MST[300];
int seed[205];
int find(int x){ return seed[x] < 0? x : seed[x] = find(seed[x]); }
int join(int a, int b){
a = find(a), b = find(b);
if(a == b) return 0;
if(seed[a] > seed[b]) seed[b] += seed[a], seed[a] = b;
else seed[a] += seed[b], seed[b] = a;
return 1;
}
int n, m;
int getMST(int e){ // 跑出MST,並保存邊的信息
memset(seed, -1, sizeof(seed));
sort(edg, edg+e);
int mcnt = 0, tmp = 0;
for(int i = 0; i < e; ++i){
if(join(edg[i].u, edg[i].v)) MST[mcnt++] = edg[i], tmp += edg[i].w;
}
printf("%d\n", tmp);
return mcnt;
}
void kruskal(int x){
int ans = 0, mcnt = 0;
memset(seed, -1, sizeof(seed));
sort(MST, MST+x);
for(int i = 0; i < x; ++i){
if(join(MST[i].u, MST[i].v)) ans += MST[i].w, MST[mcnt++] = MST[i];
}
printf("%d\n", ans);
}
int main(){
int T, ca = 1;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
printf("Case %d:\n", ca++);
memset(seed, -1, sizeof(seed));
int cnt = n, i, flag = 0, mcnt = 0;
for(i = 0; i < m; ++i){
scanf("%d%d%d", &edg[i].u, &edg[i].v, &edg[i].w);
if(join(edg[i].u, edg[i].v)) cnt -= 1;
if(cnt != 1) printf("-1\n"); //n個點沒連通
else { mcnt = getMST(++i); break; } //第一次連通
}
for(; i < m; ++i){
scanf("%d%d%d", &MST[mcnt].u, &MST[mcnt].v, &MST[mcnt].w);
kruskal(mcnt+1); //用前一次的MST和新邊跑MST
}
}
}
K - Travel
題解: n個點,m條無向邊,q個詢問。
每次詢問給出一個費用限制 ,假設有兩個不同的點 ,如果只選擇花費不大於D的邊,可以從a走到b的話,就說 合法。對於每個d,要你算出有多少對合法的 ,ab和ba是不同的答案。
對於多詢問的問題,一般有在線和離線兩種解決方式,通常來說如果解決一次詢問的複雜度很低,如 ,甚至一些比較小的 ,那麼選擇在線處理,通常更容易編碼。
對於這題,很難做到 的在線回答,即使做到了, 也會超時,只能考慮離線處理。
離線的精髓就是一次性讀取所有詢問,再分別回答,並且按照某種關係保留信息,利用上一次回答得到的信息減少下一次回答的複雜度。
在這題中,讀取所有詢問後,將所有 遞增排序。
此時最前面的 就是最小的詢問,我們先採用暴力來回答這個詢問。
做法是Kruskal,只選擇費用不大於 的邊,同時在並查集中,我們需要維護額外的信息,對於一個並查集根 ,我們要維護 下面的元素有多少個。
根 有什麼含義呢?前面說了,我們只選擇了費用不大於 的邊,也就是說 下面的任意兩個元素都是可以通過不大於 的邊互相到達的,對於一個根 ,設它的大小爲 ,那麼這個根及其元素對答案的貢獻就是 ,即任選兩個元素的排列。
需要留意這種算貢獻的思想也是十分重要的,例如,逆序對也可以考慮成每個數對答案的貢獻。
現在處理完了最小的詢問,要處理下一個詢問,如何利用上次的信息呢?
假設現在有兩個根 和 ,在上一次詢問裏這兩個根沒有合併,說明這兩個根只靠通過不大於 的邊無法互相到達,現在的限制升高到 ,在Kruskal過程中,如果要將 和 合併,那麼貢獻是 內的任意兩點排列 內的任意兩點排列 一點在 ,另外一點在 的排列 ,可以看出前面兩項的累加就是前一次的答案,所以新產生的貢獻就是最後一項,通過排列可以算出是 ,那麼本次詢問的答案 ,只需要遍歷所有需要合併的並查集就可以算出答案,注意到在 的過程中正好需要合併並查集,其實代碼比較容易寫了。
結論就是我們發現本題採用離線回答時,當前詢問的答案只跟前一次的答案,以及並查集裏新合併的根的大小有關係。
每條邊只需要加入一次,複雜度瓶頸是快排 ,解決。
本題是去年區域賽網賽水題。
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int M = 100005;
const int N = 20005;
struct eg{
int u, v, w;
bool operator < (const eg &a) const{
return w < a.w;
}
}edg[M];
pair<int,int> qry[5005];
int seed[N];
//seed值爲負的表示是根,負數的大小表示元素個數
int find(int x){ return seed[x] < 0? x : seed[x] = find(seed[x]); }
int join(int a, int b, int &sum){
a = find(a), b = find(b);
if(a == b) return 0;
sum += 2*seed[a]*seed[b]; //加入新貢獻
if(seed[a] > seed[b]) seed[b] += seed[a], seed[a] = b;
else seed[a] += seed[b], seed[b] = a;
return 1;
}
int ans[5005];
int main(){
int T;
scanf("%d", &T);
while(T--){
int n, m, q;
scanf("%d%d%d", &n, &m, &q);
memset(seed, -1, sizeof(seed));
for(int i = 0; i < m; ++i) scanf("%d%d%d", &edg[i].u, &edg[i].v, &edg[i].w);
sort(edg, edg+m);
for(int i = 0; i < q; ++i) scanf("%d", &qry[i].first), qry[i].second = i; // 讀取全部詢問
sort(qry, qry+q); // 排序詢問
int ecnt = 0, sum = 0;
for(int i = 0; i < q; ++i){
while(ecnt < m && edg[ecnt].w <= qry[i].first){
//只加入當前需要的邊
join(edg[ecnt].u, edg[ecnt].v, sum);
ecnt += 1;
}
ans[qry[i].second] = sum; //分別回答
}
for(int i = 0; i < q; ++i) printf("%d\n", ans[i]);
}
}