重新認識了Kruskal算法。
題目
T1:BZOJ 3293 / CQOI 2011 分金币 (coin)(亂搞???)
T2:BZOJ 1007 / HNOI 2008 水準可見直線 (line)(棧維護凸殼)
T3:BZOJ 1016 / JSOI 2008 最小生成樹計數 (award)(Kruskal+計數)
T1
分析
下面,将「第 i i 個人給第jj個人 k k 個金币」定義為:
k≥0k≥0,則第 i i 個人的金币數減kk,第 j j 個人的金币數加kk;
否則第 i i 個人的金币數加kk,第 j j 個人的金币數減kk,也就是第 j j 個人給第ii個人 −k − k 個金币。
并且第 i i 個人初始的金币數量為xixi,平均值為 x¯ x ¯ , sum[l,r] s u m [ l , r ] 為 ∑ri=la[i] ∑ i = l r a [ i ] 的值。
首先考慮把第 1 1 個人的金币數量變成x¯x¯,那麼可以得到,第 1 1 個人給第22個人的金币加上第 1 1 個人給第nn個人的金币數量一定等于 x1−x¯ x 1 − x ¯ 。這樣第 1 1 個人給了第22個人和第 n n 個人金币之後,第22個人就不用再給第 1 1 個人金币了,也就是說第22個人隻需要給第 3 3 個人金币。照這樣計算下去,第11個人給了第 2 2 個人和第nn個人金币之後,對于所有的 1<i<n 1 < i < n ,第 i i 個人隻需要給第i+1i+1個人金币,使得第 i i 個人的金币數量變為x¯x¯。
而現在的關鍵就是求得第 2 2 個人被第11個人分到金币後的金币數目(下面記為 w w )。
可以推出,第11個人分給了第 2 2 個人w−x2w−x2個金币,也分給了第 n n 個人x1+x2−w−x¯x1+x2−w−x¯個金币,代價為:
|w−(x1+x2−x¯)|+|w−x2| | w − ( x 1 + x 2 − x ¯ ) | + | w − x 2 | 。
再繼續考慮第 2 2 個人到第n−1n−1個人:
可以算出,第 2 2 個人分給第33個人金币,使得第 2 2 個人的金币數量變為x¯x¯的代價為 |w−x¯| | w − x ¯ | 。這樣,第 3 3 個人的金币數量變成了x3+w−x¯x3+w−x¯,是以分給第 4 4 個人金币的代價為|w−(2x¯−x3)||w−(2x¯−x3)|…
是以,對于所有的 2<i<n 2 < i < n ,第 i i 個人分給第i+1i+1個人金币的代價為:
|w−((i−1)x¯−sum[3,i])| | w − ( ( i − 1 ) x ¯ − s u m [ 3 , i ] ) | 。
是以,設一個新的數組 b b ,定義:
b1=x1+x2−x¯b1=x1+x2−x¯
b2=x2 b 2 = x 2
b3=x¯ b 3 = x ¯
∀3<i≤n,bi=(i−2)x¯−sum[3,i−1] ∀ 3 < i ≤ n , b i = ( i − 2 ) x ¯ − s u m [ 3 , i − 1 ]
這時候,就是要找到一個值 w w ,最小化∑ni=1|w−bi|∑i=1n|w−bi|的值。
顯然(也就是我不會嚴謹證明), w w 的值為數組bb的中位數。将 b b 排序之後,就可以找到這個ww。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = ; bool bo = ; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = ; else res = c - ;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << ) + (res << ) + (c - );
return bo ? ~res + : res;
}
typedef long long ll;
const int N = + ;
int n; ll a[N], ave, sum[N], b[N], ans;
ll cyx(int l, int r) {
return sum[r] - sum[l - ];
}
int main() {
int i; n = read();
for (i = ; i <= n; i++) ave += (a[i] = read()); ave /= n;
if (n == ) return printf("0\n"), ;
for (i = ; i <= n; i++) sum[i] = sum[i - ] + a[i];
b[] = a[] + a[] - ave; b[] = a[]; for (i = ; i <= n; i++)
b[i] = ave * (i - ) - cyx(, i - );
sort(b + , b + n + ); if (!(n & )) {
for (i = ; i <= (n >> ); i++) ans += b[n >> ] - b[i];
for (i = (n >> ) + ; i <= n; i++)
ans += b[i] - b[n >> ];
}
else {
for (i = ; i <= (n >> ) + ; i++) ans += b[(n >> ) + ] - b[i];
for (i = (n >> ) + ; i <= n; i++)
ans += b[i] - b[(n >> ) + ];
}
cout << ans << endl;
return ;
}
T2
分析
首先,将所有的直線按照斜率( A A ) 從小到大排序,相同情況按照BB從小到大排序,這樣, A A 相同的直線中隻有BB最大的才可見。這樣先去除掉一些一定被覆寫的直線,使每條直線的 A A 都不同。
如果合法直線隻有不到33條,那麼所有的合法直線都可見。
否則建立一個棧,把前 2 2 條直線加入棧中,這22條直線在其他的直線被加入之前全部可見。
考慮加入一條新的直線。加入一條新的直線之後,在這之前棧中的直線(可見)有可能被覆寫,但由于已經按 A A 排序,是以如果棧中的一條直線l1l1在加入了這條直線 l2 l 2 之後被覆寫,那麼棧中 l1 l 1 之後的直線(除 l2 l 2 外)必然被覆寫。是以在加入 l2 l 2 之前,應該:
(1)判斷棧頂直線是否會在加入 l2 l 2 之後被覆寫,如果有則轉(2),否則将 l2 l 2 加入棧中;
(2)将棧頂元素退棧,轉(1)。
操作完成後,棧中剩餘的直線就是答案。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = ; bool bo = ; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = ; else res = c - ;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << ) + (res << ) + (c - );
return bo ? ~res + : res;
}
const int N = + ; const double eps = ;
int n; struct cyx {
int id, k, b;
} cx[N], pyz[N]; int top, stk[N]; bool del[N];
bool comp(cyx a, cyx b) {
if (a.k != b.k) return a.k < b.k;
return a.b < b.b;
}
bool slope(int p1, int p2, int p3) {
double x1 = * (cx[p2].b - cx[p1].b) / (cx[p1].k - cx[p2].k),
x2 = * (cx[p3].b - cx[p2].b) / (cx[p2].k - cx[p3].k);
return x1 > x2 || abs(x1 - x2) <= eps;
}
int main() {
int i, Tn, m = ; Tn = n = read();
for (i = ; i <= n; i++) cx[i].k = read(), cx[i].b = read(), cx[i].id = i;
sort(cx + , cx + n + , comp); for (i = ; i <= n; i++)
if (i == n || cx[i].k != cx[i + ].k) pyz[++m] = cx[i];
n = m; for (i = ; i <= n; i++) cx[i] = pyz[i];
if (n < ) {
for (i = ; i <= n; i++) del[cx[i].id] = ;
for (i = ; i <= Tn; i++) if (del[i]) printf("%d ", i);
printf("\n"); return ;
}
stk[] = ; stk[top = ] = ; for (i = ; i <= n; i++) {
while (top > && slope(stk[top - ], stk[top], i)) top--;
stk[++top] = i;
}
for (i = ; i <= top; i++) del[cx[stk[i]].id] = ;
for (i = ; i <= Tn; i++) if (del[i]) printf("%d ", i);
cout << endl;
return ;
}
T3 最小生成樹計數
分析
先把邊按權值排序。假設現在考慮到的是所有權值為 w w 的邊,并且所有權值小于ww的合法邊已經在最小生成樹内。這樣考慮一個貪心,把所有的權值為 w w 都加入最小生成樹内,但這樣會形成環。又由于要求邊權和最小的生成樹,是以要在所有邊權為ww的邊中,删掉盡可能少的邊,使得目前的生成樹沒有環。這樣等同于在目前的生成樹内,删掉這些邊後每對點之間的連通性不變。而一個 m m 個點的連通圖,去掉盡可能少的邊使圖仍然連通,答案一定是讓這個連通圖隻剩下m−1m−1條邊。
歸納一下,得出結論:在一張圖的每一個最小生成樹中,同一權值的邊的出現次數和連通的點集相同。
是以,先将邊按權值排序,然後做一遍Kruskal,求出每種權值邊的出現次數。
然後按順序考慮每種權值:由于同種權值的邊數 ≤10 ≤ 10 ,是以可以 210 2 10 暴力枚舉每條邊選或不選,隻要沒有環并且選出邊的條數恰好等于預處理出的次數,這種選法就是合法的。這時候,這種權值的結果就是合法方案的個數。
最後把每種權值的合法方案個數相乘,得到最終結果。
實作上的細節:
1、考慮完一種權值之後,要保留這種權值的邊連通的集合。具體用個例子: n=4 n = 4 , m=5 m = 5 ,有邊 (1,2,1) ( 1 , 2 , 1 ) , (1,3,1) ( 1 , 3 , 1 ) , (2,3,2) ( 2 , 3 , 2 ) , (2,4,2) ( 2 , 4 , 2 ) , (3,4,2) ( 3 , 4 , 2 ) 。(三元組的第三個元素表示邊權)這時候如果不保留連通的集合,那麼權值 1 1 的結果為11,權值 2 2 的結果為33,但最後結果是 2 2 而不是33,原因很簡單:方案 (1,2) ( 1 , 2 ) (1,3) ( 1 , 3 ) (2,3) ( 2 , 3 ) ,雖然權值為 1 1 和22的邊都構不成環,但是合起來就構成環了。但如果保留連通集合,也就是考慮完權值 1 1 之後,将(1,2)(1,2) (1,3) ( 1 , 3 ) (2,3) ( 2 , 3 ) 标記為已連通,那麼權值為 2 2 的邊的選擇方案種,(2,3)(2,3)就不是一個合法的方案。具體可以用并查集來實作。
2、注意圖不聯通的情況,輸出 0 0 <script type="math/tex" id="MathJax-Element-266">0</script>。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = ; bool bo = ; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = ; else res = c - ;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << ) + (res << ) + (c - );
return bo ? ~res + : res;
}
const int N = , M = , ZZQ = ;
int n, fa[N], m, d, cnt[M], pyz[M], sel[M], ans[M], L[M], R[M], F[M], lyt[M];
struct cyx {
int u, v, w;
} orz[M];
bool comp(cyx a, cyx b) {
return a.w < b.w;
}
int cx(int x) {
if (fa[x] != x) fa[x] = cx(fa[x]);
return fa[x];
}
bool zm(int x, int y) {
int ix = cx(x), iy = cx(y);
if (ix == iy) return ;
return fa[iy] = ix, ;
}
void kruskal() {
int i; for (i = ; i <= n; i++) fa[i] = i;
for (i = ; i <= m; i++) if (zm(orz[i].u, orz[i].v)) sel[i] = ;
}
void dfs(int cyx, int dep, int sta) {
if (dep == R[cyx] + ) {
int i, cnt = ; for (i = ; i <= n; i++) fa[i] = F[i];
for (i = ; i < R[cyx] - L[cyx] + ; i++)
if ((sta >> i) & ) cnt++; if (cnt != pyz[cyx]) return;
for (i = ; i < R[cyx] - L[cyx] + ; i++)
if (((sta >> i) & ) && !zm(orz[L[cyx] + i].u, orz[L[cyx] + i].v))
return; ans[cyx]++;
if (ans[cyx] == ) for (i = ; i <= n; i++) lyt[i] = fa[i];
return;
}
dfs(cyx, dep + , sta);
dfs(cyx, dep + , sta | ( << dep - L[cyx]));
}
int main() {
int i, j; n = read(); m = read();
for (i = ; i <= m; i++) orz[i].u = read(), orz[i].v = read(),
orz[i].w = read(); sort(orz + , orz + m + , comp);
kruskal(); for (i = ; i <= m;) {
L[++d] = i; for (j = i; orz[i].w == orz[j].w && j <= m; j++)
cnt[d]++, pyz[d] += sel[j]; R[d] = j - ;
i = j;
}
for (i = ; i <= n; i++) fa[i] = i;
for (i = ; i <= d; i++) if (pyz[i] && cnt[i] == )
zm(orz[L[i]].u, orz[L[i]].v);
for (i = ; i <= n; i++) F[i] = fa[i], fa[i] = i;
for (i = ; i <= d; i++) {
if (!pyz[i]) continue;
if (cnt[i] == ) ans[i] = ;
else {
dfs(i, L[i], );
for (j = ; j <= n; j++) F[j] = lyt[j];
}
}
int wohaocaia = ; for (i = ; i <= d; i++) if (pyz[i])
wohaocaia = wohaocaia * ans[i] % ZZQ;
for (i = ; i <= n; i++) fa[i] = i; for (i = ; i <= m; i++)
zm(orz[i].u, orz[i].v); for (i = ; i <= n; i++)
if (cx() != cx(i)) return printf("0\n"), ;
cout << wohaocaia << endl;
return ;
}