思维题真恶心。
题目
T1:BZOJ 1201 / HNOI 2005 数三角形 (triangle)(树状数组)
T2:BZOJ 3624 / APIO 2008 免费道路 (roads)(思路+并查集)
T3:BZOJ 2663 / BeiJing WC 2012 灵魂宝石 (soulgems)(二分图匹配)
T1
分析
下面定义
(i,j)
(
i
,
j
)
表示第
i
i
行的第个顶点(共
n+1
n
+
1
行,第
i
i
行有个顶点)
考虑一个朴素的做法:
一、利用递推,预处理出
5
5
个值:
(1):
(i,j)
(
i
,
j
)
通过未被删除的边,向左延伸的最长距离,如样例中
lef[3][3]=0
l
e
f
[
3
]
[
3
]
=
0
,
lef[4][4]=3
l
e
f
[
4
]
[
4
]
=
3
。
(2)
leup[i][j]
l
e
u
p
[
i
]
[
j
]
:
(i,j)
(
i
,
j
)
通过未被删除的边,向左上延伸的最长距离。
(3)
riup[i][j]
r
i
u
p
[
i
]
[
j
]
:
(i,j)
(
i
,
j
)
通过未被删除的边,向右上延伸的最长距离。
(4)
ledw[i][j]
l
e
d
w
[
i
]
[
j
]
:
(i,j)
(
i
,
j
)
通过未被删除的边,向左下延伸的最长距离。
(5)
ridw[i][j]
r
i
d
w
[
i
]
[
j
]
:
(i,j)
(
i
,
j
)
通过未被删除的边,向右下延伸的最长距离。
统计正立三角形时,先枚举右下顶点
(i,j)
(
i
,
j
)
。容易得出,
右下顶点为
(i,j)
(
i
,
j
)
的正立三角形个数不超过
min(lef[i][j],leup[i][j])
min
(
l
e
f
[
i
]
[
j
]
,
l
e
u
p
[
i
]
[
j
]
)
。
记
k=j−min(lef[i][j],leup[i][j])
k
=
j
−
min
(
l
e
f
[
i
]
[
j
]
,
l
e
u
p
[
i
]
[
j
]
)
,那么第
i
i
行的第个顶点都有可能成为正立三角形的左下角。
而对于任何的
h∈[k,j−1]
h
∈
[
k
,
j
−
1
]
,
(i,h)
(
i
,
h
)
向右上延伸后,能与
(i,j)
(
i
,
j
)
构成正立三角形的充分必要条件是:
统计倒立三角形也一样,先枚举右上顶点 (i,j) ( i , j ) ,
右上顶点为 (i,j) ( i , j ) 的正立三角形个数不超过 min(lef[i][j],ledw[i][j]) min ( l e f [ i ] [ j ] , l e d w [ i ] [ j ] ) 。
记 k=j−min(lef[i][j],ledw[i][j]) k = j − min ( l e f [ i ] [ j ] , l e d w [ i ] [ j ] ) ,那么第 i i 行的第个顶点都有可能成为倒立三角形的左上角。
对于任何的 h∈[k,j−1] h ∈ [ k , j − 1 ] , (i,h) ( i , h ) 向右下延伸后,能与 (i,j) ( i , j ) 构成倒立三角形的充分必要条件是:
复杂度是 O(n3) O ( n 3 ) 的。考虑将上面给出的两个不等式移项:
这样就转换成了求一个区间内,有多少个数大于一个定值。将询问离线排序之后,可以使用树状数组解决。复杂度 O(n2logn) O ( n 2 log n ) 。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
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 = 1024, M = 4096;
int n, led[N][N], rid[N][N], lef[N][N], leu[N][N], riu[N][N], T[M], tot; ll ans;
void change(int x, int v) {
for (; x <= 4000; x += x & -x) T[x] += v;
}
int ask(int x) {
int res = 0; for (; x; x -= x & -x) res += T[x]; return res;
}
struct cyx {
int id, ri;
cyx() {}
cyx(int _id, int _ri) : id(_id), ri(_ri) {}
} otz[M];
inline bool comp(const cyx &a, const cyx &b) {
return a.ri > b.ri;
}
int main() {
int i, j, x, y, z; n = read();
for (i = 1; i <= n; i++) for (j = 1; j <= i; j++) {
x = read(); y = read(); z = read();
lef[i + 1][j + 1] = z; led[i][j] = riu[i + 1][j] = x;
rid[i][j] = leu[i + 1][j + 1] = y;
}
n++; for (i = 1; i <= n; i++) for (j = 1; j <= i; j++)
lef[i][j] = lef[i][j] ? lef[i][j - 1] + 1 : 0;
for (j = 1; j <= n; j++) for (i = j; i <= n; i++) {
leu[i][j] = leu[i][j] ? leu[i - 1][j - 1] + 1 : 0;
riu[i][j] = riu[i][j] ? riu[i - 1][j] + 1 : 0;
}
for (i = n; i; i--) for (j = 1; j <= i; j++) {
led[i][j] = led[i][j] ? led[i + 1][j] + 1 : 0;
rid[i][j] = rid[i][j] ? rid[i + 1][j + 1] + 1 : 0;
}
for (i = 1; i <= n; i++) for (j = 1; j <= i; j++)
riu[i][j] += j, rid[i][j] += j;
for (i = 1; i <= n; i++) {
for (j = 0; j <= 4000; j++) T[j] = 0; tot = 0;
for (j = 1; j <= i; j++) otz[++tot] = cyx(j, riu[i][j]);
sort(otz + 1, otz + tot + 1, comp); int orz = 1;
for (j = i; j; j--) {
while (orz <= tot && otz[orz].ri >= j)
change(otz[orz].id, 1), orz++;
ans += ask(j - 1) - ask(j - min(lef[i][j], leu[i][j]) - 1);
}
for (j = 0; j <= 4000; j++) T[j] = 0; tot = 0;
for (j = 1; j <= i; j++) otz[++tot] = cyx(j, rid[i][j]);
sort(otz + 1, otz + tot + 1, comp); orz = 1;
for (j = i; j; j--) {
while (orz <= tot && otz[orz].ri >= j)
change(otz[orz].id, 1), orz++;
ans += ask(j - 1) - ask(j - min(lef[i][j], led[i][j]) - 1);
}
}
cout << ans << endl;
return 0;
}
T2
分析
这题真的很难想。方法:
(1)先把所有的水泥路加上,然后求出必须加上的鹅卵石路。
(2)把必须加上的鹅卵石路加上,然后加其他的鹅卵石路,直到满
k
k
个为止。最后加上剩余的水泥路。注意要一直保持树结构(会形成环的边不能加)。
注意判无解:
1、鹅卵石路的数量
2、必须加的鹅卵石路的数量
>k
>
k
3、所有边加入都无法连通
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 = 2e4 + 5, M = 1e5 + 5;
int n, m, K, X[M], Y[M], V[M], fa[N], T, aX[M], aY[M], aV[M];
bool mst[M];
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 fa[iy] = ix, 1; return 0;
}
int main() {
int i, cnt = 0, w = 0; n = read(); m = read(); K = read();
for (i = 1; i <= m; i++) X[i] = read(), Y[i] = read(), V[i] = read();
for (i = 1; i <= n; i++) fa[i] = i;
for (i = 1; i <= m; i++) if (V[i]) if (zm(X[i], Y[i])) cnt++;
for (i = 1; i <= m; i++)
if (!V[i]) {
if (zm(X[i], Y[i])) aX[++T] = X[i],
aY[T] = Y[i], aV[T] = V[i], cnt++, mst[i] = 1; w++;
}
if (T > K || w < K) return puts("no solution"), 0;
for (i = 2; i <= n; i++) if (cx(i) != cx(1))
return puts("no solution"), 0;
for (i = 1; i <= n; i++) fa[i] = i;
for (i = 1; i <= T; i++) zm(aX[i], aY[i]);
for (i = 1; i <= m; i++) if (!V[i] && !mst[i]) {
if (zm(X[i], Y[i])) aX[++T] = X[i], aY[T] = Y[i],
aV[T] = V[i], mst[i] = 1; if (T == K) break;
}
if (T < K) return puts("no solution"), 0;
for (i = 1; i <= m; i++) if (V[i] && zm(X[i], Y[i]))
aX[++T] = X[i], aY[T] = Y[i], aV[T] = V[i];
for (i = 1; i <= T; i++) printf("%d %d %d\n", aX[i], aY[i], aV[i]);
return 0;
}
T3
分析
显然是二分图匹配。求
Rmin
R
min
比较简单:
先二分答案。转化成判定性问题:
一个二分图,一边是魔法少女,一边是灵魂宝石。如果一个魔法少女和一个灵魂宝石之间的距离
≤R
≤
R
,就连一条边。求最大匹配数和
K
K
的大小关系。
这样是裸的二分图匹配。
求就是:
同样一个二分图。如果一个魔法少女和一个灵魂宝石之间的距离
<R
<
R
<script type="math/tex" id="MathJax-Element-807">
(并且不能有还能匹配的边)和
K
K
的大小关系。
显然(也就是我不会严谨证明),求出这个二分图的补图的最大匹配,再用减去最大匹配,就是要求出的最小匹配。
特殊情况:
N=K
N
=
K
时,
Rmax=+INF
R
max
=
+
INF
。
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 = 3e5 + 5; const double eps = 1e-9;
int n, K, X[N], Y[N], x[N], y[N], ecnt, nxt[N], adj[N], go[N],
vis[N], times, my[N];
void add_edge(int u, int v) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
int orzcyx(int i, int j, double R) {
int dis = (X[i] - x[j]) * (X[i] - x[j]) + (Y[i] - y[j]) * (Y[i] - y[j]);
R = R * R; if (fabs(dis - R) <= eps) return 0;
if (dis < R) return 1; return 2;
}
bool dfs(int u) {
for (int e = adj[u], v; e; e = nxt[e])
if (vis[v = go[e]] < times) {
vis[v] = times;
if (!my[v] || dfs(my[v])) return my[v] = u, 1;
}
return 0;
}
int maxCyx() {
int i; for (i = 1; i <= n; i++) my[i] = vis[i] = 0; times = 0;
int ans = 0; for (i = 1; i <= n; i++) {
times++; if (dfs(i)) ans++;
}
return ans;
}
int maxPyz(double R) {
int i, j; ecnt = 0; for (i = 1; i <= n; i++) adj[i] = 0;
for (i = 1; i <= n; i++) for (j = 1; j <= n; j++)
if (orzcyx(i, j, R) != 2) add_edge(i, j); return maxCyx();
}
int maxLpf(double R) {
int i, j; ecnt = 0; for (i = 1; i <= n; i++) adj[i] = 0;
for (i = 1; i <= n; i++) for (j = 1; j <= n; j++)
if (orzcyx(i, j, R) != 1) add_edge(i, j); return n - maxCyx();
}
int main() {
int i; n = read(); K = read();
for (i = 1; i <= n; i++) X[i] = read(), Y[i] = read();
for (i = 1; i <= n; i++) x[i] = read(), y[i] = read();
double l = 0, r = 1e5; while (r - l > 1e-7) {
double mid = (l + r) / 2;
if (maxPyz(mid) < K) l = mid;
else r = mid;
}
printf("%.2lf ", (l + r) / 2); if (n == K) return puts("+INF"), 0;
l = 0; r = 1e5; while (r - l > 1e-7) {
double mid = (l + r) / 2;
if (maxLpf(mid) > K) r = mid;
else l = mid;
}
printf("%.2lf\n", (l + r) / 2);
return 0;
}