重新认识了Kruskal算法。
题目
T1:BZOJ 3293 / CQOI 2011 分金币 (coin)(乱搞???)
T2:BZOJ 1007 / HNOI 2008 水平可见直线 (line)(栈维护凸壳)
T3:BZOJ 1016 / JSOI 2008 最小生成树计数 (award)(Kruskal+计数)
T1
分析
下面,将「第
i
i
个人给第个人
k
k
个金币」定义为:
,则第
i
i
个人的金币数减,第
j
j
个人的金币数加;
否则第
i
i
个人的金币数加,第
j
j
个人的金币数减,也就是第
j
j
个人给第个人
−k
−
k
个金币。
并且第
i
i
个人初始的金币数量为,平均值为
x¯
x
¯
,
sum[l,r]
s
u
m
[
l
,
r
]
为
∑ri=la[i]
∑
i
=
l
r
a
[
i
]
的值。
首先考虑把第
1
1
个人的金币数量变成,那么可以得到,第
1
1
个人给第个人的金币加上第
1
1
个人给第个人的金币数量一定等于
x1−x¯
x
1
−
x
¯
。这样第
1
1
个人给了第个人和第
n
n
个人金币之后,第个人就不用再给第
1
1
个人金币了,也就是说第个人只需要给第
3
3
个人金币。照这样计算下去,第个人给了第
2
2
个人和第个人金币之后,对于所有的
1<i<n
1
<
i
<
n
,第
i
i
个人只需要给第个人金币,使得第
i
i
个人的金币数量变为。
而现在的关键就是求得第
2
2
个人被第个人分到金币后的金币数目(下面记为
w
w
)。
可以推出,第个人分给了第
2
2
个人个金币,也分给了第
n
n
个人个金币,代价为:
|w−(x1+x2−x¯)|+|w−x2|
|
w
−
(
x
1
+
x
2
−
x
¯
)
|
+
|
w
−
x
2
|
。
再继续考虑第
2
2
个人到第个人:
可以算出,第
2
2
个人分给第个人金币,使得第
2
2
个人的金币数量变为的代价为
|w−x¯|
|
w
−
x
¯
|
。这样,第
3
3
个人的金币数量变成了,所以分给第
4
4
个人金币的代价为…
所以,对于所有的
2<i<n
2
<
i
<
n
,第
i
i
个人分给第个人金币的代价为:
|w−((i−1)x¯−sum[3,i])|
|
w
−
(
(
i
−
1
)
x
¯
−
s
u
m
[
3
,
i
]
)
|
。
所以,设一个新的数组
b
b
,定义:
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
,最小化的值。
显然(也就是我不会严谨证明),
w
w
的值为数组的中位数。将
b
b
排序之后,就可以找到这个。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e5 + 5;
int n; ll a[N], ave, sum[N], b[N], ans;
ll cyx(int l, int r) {
return sum[r] - sum[l - 1];
}
int main() {
int i; n = read();
for (i = 1; i <= n; i++) ave += (a[i] = read()); ave /= n;
if (n == 1) return printf("0\n"), 0;
for (i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
b[1] = a[1] + a[2] - ave; b[2] = a[2]; for (i = 3; i <= n; i++)
b[i] = ave * (i - 2) - cyx(3, i - 1);
sort(b + 1, b + n + 1); if (!(n & 1)) {
for (i = 1; i <= (n >> 1); i++) ans += b[n >> 1] - b[i];
for (i = (n >> 1) + 1; i <= n; i++)
ans += b[i] - b[n >> 1];
}
else {
for (i = 1; i <= (n >> 1) + 1; i++) ans += b[(n >> 1) + 1] - b[i];
for (i = (n >> 1) + 2; i <= n; i++)
ans += b[i] - b[(n >> 1) + 1];
}
cout << ans << endl;
return 0;
}
T2
分析
首先,将所有的直线按照斜率(
A
A
) 从小到大排序,相同情况按照从小到大排序,这样,
A
A
相同的直线中只有最大的才可见。这样先去除掉一些一定被覆盖的直线,使每条直线的
A
A
都不同。
如果合法直线只有不到条,那么所有的合法直线都可见。
否则建立一个栈,把前
2
2
条直线加入栈中,这条直线在其他的直线被加入之前全部可见。
考虑加入一条新的直线。加入一条新的直线之后,在这之前栈中的直线(可见)有可能被覆盖,但由于已经按
A
A
排序,所以如果栈中的一条直线在加入了这条直线
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 = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 5e4 + 5; const double eps = 1e-9;
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 = 1.0 * (cx[p2].b - cx[p1].b) / (cx[p1].k - cx[p2].k),
x2 = 1.0 * (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 = 0; Tn = n = read();
for (i = 1; i <= n; i++) cx[i].k = read(), cx[i].b = read(), cx[i].id = i;
sort(cx + 1, cx + n + 1, comp); for (i = 1; i <= n; i++)
if (i == n || cx[i].k != cx[i + 1].k) pyz[++m] = cx[i];
n = m; for (i = 1; i <= n; i++) cx[i] = pyz[i];
if (n < 3) {
for (i = 1; i <= n; i++) del[cx[i].id] = 1;
for (i = 1; i <= Tn; i++) if (del[i]) printf("%d ", i);
printf("\n"); return 0;
}
stk[1] = 1; stk[top = 2] = 2; for (i = 3; i <= n; i++) {
while (top > 1 && slope(stk[top - 1], stk[top], i)) top--;
stk[++top] = i;
}
for (i = 1; i <= top; i++) del[cx[stk[i]].id] = 1;
for (i = 1; i <= Tn; i++) if (del[i]) printf("%d ", i);
cout << endl;
return 0;
}
T3 最小生成树计数
分析
先把边按权值排序。假设现在考虑到的是所有权值为
w
w
的边,并且所有权值小于的合法边已经在最小生成树内。这样考虑一个贪心,把所有的权值为
w
w
都加入最小生成树内,但这样会形成环。又由于要求边权和最小的生成树,所以要在所有边权为的边中,删掉尽可能少的边,使得当前的生成树没有环。这样等同于在当前的生成树内,删掉这些边后每对点之间的连通性不变。而一个
m
m
个点的连通图,去掉尽可能少的边使图仍然连通,答案一定是让这个连通图只剩下条边。
归纳一下,得出结论:在一张图的每一个最小生成树中,同一权值的边的出现次数和连通的点集相同。
因此,先将边按权值排序,然后做一遍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
的结果为,权值
2
2
的结果为,但最后结果是
2
2
而不是,原因很简单:方案
(1,2)
(
1
,
2
)
(1,3)
(
1
,
3
)
(2,3)
(
2
,
3
)
,虽然权值为
1
1
和的边都构不成环,但是合起来就构成环了。但如果保留连通集合,也就是考虑完权值
1
1
之后,将
(1,3)
(
1
,
3
)
(2,3)
(
2
,
3
)
标记为已连通,那么权值为
2
2
的边的选择方案种,就不是一个合法的方案。具体可以用并查集来实现。
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 = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 105, M = 1005, ZZQ = 31011;
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 0;
return fa[iy] = ix, 1;
}
void kruskal() {
int i; for (i = 1; i <= n; i++) fa[i] = i;
for (i = 1; i <= m; i++) if (zm(orz[i].u, orz[i].v)) sel[i] = 1;
}
void dfs(int cyx, int dep, int sta) {
if (dep == R[cyx] + 1) {
int i, cnt = 0; for (i = 1; i <= n; i++) fa[i] = F[i];
for (i = 0; i < R[cyx] - L[cyx] + 1; i++)
if ((sta >> i) & 1) cnt++; if (cnt != pyz[cyx]) return;
for (i = 0; i < R[cyx] - L[cyx] + 1; i++)
if (((sta >> i) & 1) && !zm(orz[L[cyx] + i].u, orz[L[cyx] + i].v))
return; ans[cyx]++;
if (ans[cyx] == 1) for (i = 1; i <= n; i++) lyt[i] = fa[i];
return;
}
dfs(cyx, dep + 1, sta);
dfs(cyx, dep + 1, sta | (1 << dep - L[cyx]));
}
int main() {
int i, j; n = read(); m = read();
for (i = 1; i <= m; i++) orz[i].u = read(), orz[i].v = read(),
orz[i].w = read(); sort(orz + 1, orz + m + 1, comp);
kruskal(); for (i = 1; 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 - 1;
i = j;
}
for (i = 1; i <= n; i++) fa[i] = i;
for (i = 1; i <= d; i++) if (pyz[i] && cnt[i] == 1)
zm(orz[L[i]].u, orz[L[i]].v);
for (i = 1; i <= n; i++) F[i] = fa[i], fa[i] = i;
for (i = 1; i <= d; i++) {
if (!pyz[i]) continue;
if (cnt[i] == 1) ans[i] = 1;
else {
dfs(i, L[i], 0);
for (j = 1; j <= n; j++) F[j] = lyt[j];
}
}
int wohaocaia = 1; for (i = 1; i <= d; i++) if (pyz[i])
wohaocaia = wohaocaia * ans[i] % ZZQ;
for (i = 1; i <= n; i++) fa[i] = i; for (i = 1; i <= m; i++)
zm(orz[i].u, orz[i].v); for (i = 2; i <= n; i++)
if (cx(1) != cx(i)) return printf("0\n"), 0;
cout << wohaocaia << endl;
return 0;
}