SCUACM 2016集训 7.8【13/16】

集训期间完成8道,补题5道。
比赛链接:http://vjudge.net/contest/121106#overview

A - Article Decryption(KMP + DP)

题意:

给定几种单词,问给定的文本串有几种翻译方式。

分析:

KMP预处理出每个单词在文本串中的匹配情况,然后$dp$一下即可。
最初KMP写错了一个地方,竟然完全没有发现,一直以为是细节出错,wa了4发!

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*************************************************************************
i > File Name: A.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: Fri 08 Jul 2016 03:31:58 PM CST
************************************************************************/
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define sa(n) scanf("%d", &(n))
const int maxn = 1e3 + 5, mod = 835672545;
int len, len1;
int next[maxn];
int dp[maxn];
char t[maxn];
char s[maxn][maxn];
bool match[maxn][maxn];
void KMP_pre(int id)
{
int j = next[0] = -1;
int i = 0;
len1 = strlen(s[id]);
while(i < len1){
while(j != -1 && s[id][i] != s[id][j]) j = next[j];
next[++i] = ++j;
}
}
void KMP(int id)
{
int i = 0, j = 0;
len = strlen(t);
len1 = strlen(s[id]);
while(i < len){
while(j != -1 && t[i]!= s[id][j]){
j = next[j];
}
j++;
if(j == len1){
match[id][i] = true;
j = next[j];
}
i++;
}
}
int main (void)
{
int T;sa(T);
while(T--){
int n;sa(n);
for(int i = 0; i < n; i++){
scanf("%s", s[i]);
}
scanf("%s", t);
len = strlen(t);
memset(match, false, sizeof(match));
for(int i = 0; i < n; i++){
KMP_pre(i);
KMP(i);
}
memset(dp, 0, sizeof(dp));
for(int i = 0; i < len; i++){
for(int j = 0; j < n; j++){
if(match[j][i]){
int l = strlen(s[j]);
if(i - l == -1) dp[i] = (dp[i] + 1) % mod;
else dp[i] = (dp[i - l] + dp[i]) % mod;
}
}
}
printf("%d\n", dp[len - 1] % mod);
}
return 0;
}

B - Special Fish(KM)

题意:

给定$n$条鱼的价值及他们之间的攻击和被攻击关系,一个条鱼仅可以攻击和被攻击一次,攻击产生的价值为两条鱼的异或,问产生的最大结果?

分析:

看了样例才知道题目问的是什么= =
裸KM

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*************************************************************************
> File Name: 3395.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/9/14 17:34:48
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
int nm, nn;
const int maxn = 1e2 + 5, INF = 0x3f3f3f3f;
int usex[maxn], usey[maxn], match[maxn], lx[maxn], ly[maxn], slack[maxn];
int z[maxn][maxn];
int a[maxn];
bool Find(int x)
{
usex[x] = 1;
for(int i = 0; i < nm; i++){
if(usey[i]) continue;
int s = lx[x] + ly[i] - z[x][i];
if(s == 0){
usey[i] = 1;
if(match[i] == -1|| Find(match[i] )){
match[i] = x;
return true;
}
}else if(slack[i] > s){
slack[i] = s;
}
}
return false;
}
int KM()
{
memset(ly, 0, sizeof(ly));
memset(match, -1, sizeof(match));
for(int i = 0; i <nn; i++){
lx[i] = - INF;
for(int j = 0; j < nm; j++){
if(lx[i] < z[i][j])
lx[i] = z[i][j];
}
}
for(int a = 0; a < nn; a++){
memset(slack, 0x3f, sizeof(slack));
for(;;){
memset(usex, 0,sizeof(usex));
memset(usey, 0, sizeof(usey));
if(Find(a)) break;
int d = INF;
for(int i = 0; i < nm; i++){
if(!usey[i] && d > slack[i]){
d = slack[i];
}
}
for(int i = 0; i < nn; i++){
if(usex[i]) lx[i] -= d;
}
for(int i = 0; i < nm; i++){
if(usey[i]) ly[i] += d;
else slack[i] -= d;
}
}
}
int sum = 0;
for(int i = 0; i < nm; i++){
if(match[i] > -1){
sum += z[match[i]][i];
}
}
return sum;
}
char t[maxn];
int main (void)
{
int n;
while(~scanf("%d", &n) && n){
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
memset(z, 0, sizeof(z));
for(int i = 0; i < n; i++){
scanf("%s", t);
for(int j = 0; j < n; j++){
if(t[j] == '1') z[i][j] = a[i] ^ a[j];
}
}
nn = n, nm = n;
printf("%d\n", KM());
}
return 0;
}

C - Bear and Bowling 4(斜率优化dp)

题意:

给定序列,求一个子序列,若子序列中有$k$个元素,值分别为$s_i,s_2,…s_{i + k}$,那么结果为$\sum_{j = i}^{i + k} (j - i + 1) * s_j$,问能够得到的序列的最大值。

分析:

容易想到状态$dp[i][j]:= 第i+1个元素到第j个元素之间的序列得到的值$
设$pre[i]:=前i个元素的和,val[i] := \sum_{j = 1} ^{i} j \times s[j]$,
那么$dp[i][j] = val[j] - val[i] - i \times (pre[j] - pre[i]);$
若$dp[i][j] > dp[k][j], i > k$
即$ val[j] - val[i] - i \times (pre[j] - pre[i]) > val[j] - val[k] - k \times (pre[j] - pre[k])$
$$grad(i,k)={(val[i] - i \times pre[i]) - (val[k] - k \times pre[k]) \over{i - k}} < -pre[j]$$
这样就转化成一个很斜率的式子了,
即当$i > k且grad(i,k)<-pre[j]$时,$i优于k$
令$i>j>k,grad(i, j )< grad(j, k)$,此时$j$均非最优,故可以舍弃。
最后由于$pre[j]$不单调,所以不必维护$head$,直接每次在单调队列上进行二分即可。
最后注意一下$long long$

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*************************************************************************
> File Name: I.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
using namespace std;
typedef pair<int, int>p;
typedef long long ll;
#define sa(n) scanf("%d", &(n))
#define sal(n) scanf("%lld", &(n))
const int maxn = 2e5 + 5, mod = 1e9 + 7, oo = 0x3f3f3f3f;
ll a[maxn], val[maxn], sum[maxn];
int q[maxn];
inline ll up(int a, int b)
{
ll x1 = val[a] - a * sum[a];
ll x2 = val[b] - b * sum[b];
return x2 - x1;
}
inline ll down(int a, int b){return b - a;}
bool judge(int a, int i)
{
ll x1 = val[q[a]] - q[a] * sum[q[a]];
ll x2 = val[q[a + 1]] - q[a + 1] * sum[q[a + 1 ]];
return x2 - x1 > -sum[i] * down(q[a], q[a + 1]);
}
int main (void)
{
int n;sa(n);
ll ans = 0;
for(int i = 1; i <= n; i++){
sal(a[i]);
sum[i] = sum[i - 1] + a[i];
val[i] = val[i - 1] + i * a[i];
ans = max(ans, a[i]);
}
int rear = 0;
int l, r;
q[rear++] = 0;
for(int i = 1; i <= n; i++){
l = -1, r = rear - 1;
while(l < r - 1){
int mid = l + r >> 1;
if(judge(mid, i)) r = mid;
else l = mid;
}
ans = max(ans, val[i] - val[q[r]] - q[r] * (sum[i] - sum[q[r]]));
while(rear > 1 && up(q[rear - 1], i) * down(q[rear - 2], q[rear - 1]) < up(q[rear - 2], q[rear - 1]) * down(q[rear - 1], i)) rear--;
q[rear++] = i;
}
printf("%lld\n", ans);
return 0;
}

D - Treasure Hunt(线段交)

题意:

$100 \times 100$的正方形围墙内有$n(n \le 30)$端点在围墙边上的墙。给定终点,问从正方形外部走到终点要经过最少多少堵墙,穿过墙时只能走中点?

分析:

$n$只有30,显然枚举入口。仔细分析我们可以发现只能走墙中点这个信息并没有什么用,因为只要是经过这堵墙,必然要穿过去,实际走的即是直线。
那么我们在枚举入口的时候只需枚举端点,然后求连接起点与终点的线段与其他所有线段的交点个数最小值即可~
顺便复习一下线段交 http://blog.csdn.net/yukizzz/article/details/50816072

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/*************************************************************************
> File Name: 1066.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/9/16 11:03:53
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 30 + 5;
double eps = 1e-8;
double add(double a, double b)
{
if(abs(a + b) < eps * (abs(a) + abs(b))) return 0;
else return a + b;
}
struct P{
double x, y;
P() {}
P(double x, double y) : x(x), y(y){}
P operator + (P p){
return P(add(x, p.x), add(y, p.y));
}
P operator -(P p){
return P(add(x, -p.x), add(y, -p.y));
}
double dot(P p){
return add(x * p.x, y *p.y);
}
double det(P p){
return add(x * p.y, - y * p.x);
}
P operator *(double d){
return P(x * d, y * d);
}
}p[maxn << 1];
P des;
bool onseg(P p1, P p2, P q)
{
return (p1 - q).det(p2 - q) == 0 && (p1 - q).dot(p2 - q) <= 0;
}
P intersection(P p1, P p2, P q1, P q2)
{
return p1 + (p2 - p1) * ((q2 - q1).det(q1 - p1) / (q2 - q1).det(p2 - p1));
}
int main (void)
{
int n;scanf("%d", &n);
for(int i = 0; i < 2 * n; i++){
scanf("%lf%lf", &p[i].x, &p[i].y);
}
int ans, res = n + 1;
double x, y;scanf("%lf%lf", &des.x, &des.y);
for(int i = 0; i < 2 * n; ++i){
ans = 1;
for(int j = 0; j < 2 * n; j += 2){
if(j == i || j == i - 1) continue;
P tmp = intersection(p[i], des, p[j], p[j + 1]);
ans += onseg(p[j], p[j + 1], tmp) && onseg(p[i], des, tmp);
}
res = min(res, ans);
}
printf("Number of doors = %d\n", res);
return 0;
}

E - Network(LCA + 并查集)

题意:

给定无向图,给定若干询问,每个询问加入一条边,问加入该边后图中桥的数目。

分析:

首先想是缩点,缩点之后形成的是棵树,树边均为桥,每加一条新边,就把他们所在集合并起来。这样就在树上形成了一个环,环中所有边都不再是桥。找环中的边的过程实际就是找最近公共祖先的过程,向上爬的过程中遇到的所有边都不再是桥。
集合并起来很自然的想到用并查集搞,而缩点过程我们也可以在tarjan求桥的同时求用并查集搞。对于每个询问在两个点向上爬的时候判断该点是否与父节点在同一集合中,若不是,说明连边为桥,删去,并将该点与其父节点并起来。
QAQ在缩点的时候我们用深度最低的点来代表他所处在的强连通分量中,利用这点就可以在求LCA时集合合并的同时让结点向上一步一步的跳。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*************************************************************************
> File Name: 3694.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/9/14 13:37:27
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 5, maxm = 2e5 + 5;
int head[maxn], dfn[maxn], low[maxn];
struct EDGE{
int to, next;
bool vis;
};
EDGE edge[maxm * 2];
int tot, dfnum;
int btot;
int pa[maxn], fa[maxn];
int dep[maxn];
void init(int n)
{
tot = 0;
btot = 0;
dfnum = 1;
memset(head, -1, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
for(int i = 1; i <= n; i++) pa[i] = i;
}
int _find(int x)
{
if(x == pa[x]) return x;
return pa[x] = _find(pa[x]);
}
void unit(int x, int y)
{
int rx = _find(x), ry = _find(y);
if(rx == ry) return;
pa[rx] = ry;
}
void addedge(int u, int v)
{
edge[tot].vis = false;
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u, int depth)
{
dfn[u] = low[u] = dfnum++;
dep[u] = depth;
for(int i = head[u]; i != -1; i = edge[i].next){
if(edge[i].vis) continue;
edge[i].vis = edge[i ^ 1].vis = true;
int v = edge[i].to;
if(!dfn[v]){
fa[v] = u;
dfs(v, depth + 1);
low[u] = min(low[u], low[v]);
}else low[u] = min(low[u], dfn[v]);
if(low[v] <= dfn[u]) unit(v, u);
else btot++;
}
}
int LCA(int u, int v)
{
int cnt = 0;
while(dep[u] > dep[v]){
if(_find(u) != _find(fa[u])) cnt++;
unit(u, fa[u]);
u = fa[u];
}
while(dep[v] > dep[u]){
if(_find(v) != _find(fa[v])) cnt++;
unit(v, fa[v]);
v = fa[v];
}
while(u != v){
if(_find(u) != _find(fa[u])) cnt++;
unit(u, fa[u]);
if(_find(v) != _find(fa[v])) cnt++;
unit(v, fa[v]);
u = fa[u];
v = fa[v];
}
return cnt;
}
int main (void)
{
int n, m;
int a, b;
int kas = 1;
while(~scanf("%d%d", &n, &m) && (n + m)){
init(n);
for(int i = 0; i < m; i++){
scanf("%d%d", &a, &b);
addedge(a, b);
addedge(b, a);
}
fa[1] = 1;
dfs(1, 1);
int q;scanf("%d", &q);
printf("Case %d:\n", kas++);
for(int i = 0; i < q; i++){
scanf("%d%d", &a, &b);
if(_find(a) != _find(b)) btot -= LCA(a, b);
printf("%d\n", btot);
}
puts("");
}
return 0;
}

I- Intellectual Inquiry【dp,构造】

题意:

给定字符串,要求用字母表前$k$个字母组成长度为$n$的字符串,并添加在已知字符串后面,使得不同子序列个数最多,求个数。

分析:

设$dp[i]:=前i个元素组成的子序列个数$,则有$dp[i] = dp[i - 1] * 2 - 重复序列个数$.
如何求得重复序列呢?首先对于每个$i$,我们都保证$dp$值中不存在重复序列,那么对于第$i$个元素,只需考虑$s[i]$对于序列的影响即可。而这重复部分即为$s[i]$上一个位置$last[s[i] - ‘a’ ]$之前的所有元素与$s[i]$形成的序列,即重复序列个数=$dp[last[s[i]-‘a’]-1]$。
为使最后得到的$dp$值最大,我们要尽量减小$dp[last[s[i]-‘a’]-1]$,而显然$dp$值是单调上升的,构造时每次选取last值最小的即可~~每次得到的$dp$都是最大的,这样累加起来最后得到的而$dp[n+m]$也一定是最大的。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 5, mod = 1e9 + 7;
char s[maxn];
int dp[maxn];
int last[26 + 5];
int main (void)
{
int n, k;scanf("%d%d", &n, &k);
scanf("%s", s + 1);
int m = strlen(s + 1);
memset(last, -1, sizeof(last));
dp[0] = 1;
int tmp, res;
for(int i = 1; i <= n + m; i++){
if(i <= m) tmp = s[i] - 'a';
else{
res = n + m + 1, tmp = 0;
for(int j = 0; j < k; j++){
if(res > last[j]){
res = last[j];
tmp = j;
if(res == -1) break;
}
}
}
if(last[tmp] > 0) (dp[i] = dp[i - 1] * 2 % mod - dp[last[tmp] - 1] + mod) %= mod;
else dp[i] = dp[i - 1] * 2 % mod;
last[tmp] = i;
}
printf("%d\n", dp[m + n]);
return 0;
}

J - Dividing Kingdom II(带权并查集,二分图)

题意:

给定$m$条边,若干询问,每次选择编号在$[l,r]$之间的边,将图分成两部分,问如何分才能使得端点在同一部分的最长边最小。

分析:

为使在端点在同一部分的最长边长度最小,我们可以将所有边从大到小排个序,然后每次都贪心的将两条边的端点放在两部分,这样便形成了一个二分图,不停加边直到不能形成二分图,即一条边的端点在同一部分,当前边就是最大值。使用带权并查集判断二分图。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int, int>p;
const int maxn = 1e3 + 5, maxm = 1e6 + 5, mod = 1e9 + 7, oo = 0x3f3f3f3f;
int pa[maxn];
int kind[maxn];
struct Node{int x, y, val, id;}edge[maxm], nedge[maxm];
int MAP[maxn];
int n, m;
bool cmp(Node a, Node b)
{
return a.val > b.val;
}
int _find(int a)
{
int fa = pa[a];
if(pa[a] == a) return a;
pa[a] = _find(pa[a]);
kind[a] = (kind[a] + kind[fa]) % 2;
return pa[a];
}
void init()
{
for(int i = 1; i <= n; i++){
pa[i] = i;
kind[i] = 0;
}
}
int gao(int l, int r)
{
init();
for(int i = 1; i <= m; i++){
if(edge[i].id > r || edge[i].id < l) continue;
int x = edge[i].x, y = edge[i].y;
int rx = _find(x);
int ry = _find(y);
//cout<<rx<<' '<<ry<<endl;
if(rx != ry){
pa[rx] = ry;
// y根 -> y, y->x,x -> x根,即y根->x根
kind[rx] = (kind[y] + 1 - kind[x] + 2) % 2;
}
if(rx == ry && kind[y] == kind[x] ){
return edge[i].val;
}
}
return -1;
}
int main (void)
{
int q;
scanf("%d%d%d", &n, &m, &q);
int x, y, w;
for(int i = 1; i <= m; i++){
scanf("%d%d%d", &x, &y, &w);
edge[i].x = x , edge[i].y = y; edge[i].val = w;
edge[i].id = i;
}
sort(edge + 1, edge + m + 1, cmp);
for(int i = 1; i <= m; i++){
MAP[edge[i].id] = i;
}
int l, r;
for(int i = 0; i < q; i++){
scanf("%d%d", &l, &r);
printf("%d\n", gao(l, r));
}
return 0;
}

K - Can you answer these queries?(线段树)

题意:

给定序列,将给定区间所有数开方,若干询问,询问区间和。

分析:

这题的关键是即便最大的数开方7次也变成了1,之后无论怎么开方都为1,这样在区间更新的时候判断下区间是否全为1即可。再套个线段树求区间和。
分析题目数据范围及性质!
注意这题一大坑点:$l,r$大小未知!

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;
#define sal(n) scanf("%I64d", &(n))
#define sa(n) scanf("%d", &(n))
typedef long long ll;
const int maxn = 1e5 + 15, maxm = 1e6 + 5, oo = 0x3f3f3f3f;
struct Node{int l;int r; ll a;}Tree[maxn << 2];
ll m[maxn];
inline void push_up(int i)
{
Tree[i].a = Tree[i << 1].a + Tree[(i << 1)|1].a;
}
void build(int i, int l, int r)
{
Tree[i].l = l;
Tree[i].r = r;
Tree[i].a = 0;
if(l == r){
Tree[i].a = m[l];
return;
}
int mid = l + r >> 1;
build(i << 1, l, mid);
build((i << 1) | 1, mid + 1, r);
push_up(i);
}
ll query(int i, int l, int r)
{
if(Tree[i].l >= l && Tree[i].r <= r){
return Tree[i].a;
}
int mid = Tree[i].l + Tree[i].r >> 1;
if(r <= mid) query(i<<1, l, r);
else if(l > mid) query((i << 1)|1, l, r);
else{
int mid = Tree[i].l + Tree[i].r >> 1;
return query(i << 1, l, mid) + query((i << 1)|1, mid + 1, r);
}
}
void update(int l, int r, int i)
{
if(Tree[i].r - Tree[i].l + 1 == Tree[i].a) return;
if(Tree[i].l == Tree[i].r){
Tree[i].a = (ll)sqrt((double)Tree[i].a);
return;
}
int mid = Tree[i].l + Tree[i].r >> 1;
if(r <= mid) update(l, r, i<<1);
else if(l > mid) update(l, r, (i << 1)|1);
else {
update(l, mid, i << 1);
update(mid + 1, r, (i << 1)|1);
}
push_up(i);
}
int main (void)
{
int n, q;
int tt = 1;
while(~scanf("%d", &n)){
for(int i = 0; i < n; i++){
sal(m[i]);
}
build(1, 0, n - 1);
cout<<"Case #"<<tt<<":"<<endl;
int l, r, x;
int a, b;
scanf("%d", &q);
for(int i = 0; i < q; i++){
sa(x);sa(l);sa(r);
if(l > r) swap(l, r);
if(x == 0){
update(l - 1, r - 1, 1);
}else{
cout<<query(1, l - 1, r - 1)<<endl;
}
}
cout<<endl;
tt++;
}
return 0;
}

L - Assign the task(DFS+线段树)

题意:

给你一棵有$N$个节点的树,有$M$次操作。
$T\ x\ y$ 表示把$x$点权值修改为$y$,其所有子节点都会变成$y$。$C\ x$ 查询$x$的权值。

####分析:
求出DFS序,找到每个节点第一次被标记和最后一次被标记的区间,建立线段树,成段更新+单节点查询即可。
这题暴力好像也能过,每次查询一个结点,就访问向上访问他的祖先,找到最后一次修改的结点即为答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#include<cstring>
using namespace std;
#define sal(n) scanf("%I64d", &(n))
#define sa(n) scanf("%d", &(n))
typedef long long ll;
const int maxn = 1e5 + 5, maxm = 1e6 + 5, oo = 0x3f3f3f3f;
struct edge{int to, next;}edge[maxn];
int head[maxn];
int tot;
int num;
int s[maxn], e[maxn];
bool vis[maxn];
void init()
{
num = 0;
tot = 0;
memset(head, -1, sizeof(head));
memset(vis, false, sizeof(vis));
}
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u)
{
s[u] = num++;
for(int i = head[u]; i != -1; i = edge[i].next){
dfs(edge[i].to);
}
e[u] = num++;
}
struct Tree{
int l, r;
int lazy, val;
}tree[maxn* 4 + 5];
void push_down(int i)
{
if(tree[i].lazy){
tree[i].lazy = 0;
tree[i << 1].val = tree[i].val;
tree[i << 1].lazy = 1;
tree[(i << 1) | 1].val = tree[i].val;
tree[(i << 1) | 1].lazy = 1;
}
}
void build(int i, int l, int r)
{
tree[i].l = l;
tree[i].r = r;
tree[i].val = -1;
tree[i].lazy = 0;
if(l == r) return;
int mid = l + r >> 1;
build(i << 1, l, mid);
build((i << 1) | 1, mid + 1, r);
}
void update(int i, int l, int r, int val)
{
if(tree[i].l == l && tree[i].r == r){
tree[i].val = val;
tree[i].lazy = 1;
return;
}
push_down(i);
int mid = tree[i].l + tree[i].r >> 1;
if(r <= mid) update(i << 1, l, r, val);
else if(l > mid) update((i << 1) | 1, l, r, val);
else{
update(i << 1, l, mid, val);
update((i << 1) | 1, mid + 1, r, val);
}
}
int query(int i, int k)
{
if(tree[i].l == k && tree[i].r == k){
return tree[i].val;
}
push_down(i);
int mid = tree[i].l + tree[i].r >> 1;
if(k <= mid) return query(i << 1, k);
else return query((i <<1)|1, k);
}
int main(void)
{
int T;sa(T);
for(int tt = 1; tt <= T; tt++){
int n;sa(n);
init();
int u, v;
int root;
for(int i = 0; i < n - 1; i++){
scanf("%d%d",&u, &v);
addedge(v, u);
vis[u] =true;
}
for(int i = 1; i <= n; i++){
if(!vis[i]){
dfs(i);
break;
}
}
build(1, 0, num);
int m;sa(m);
char t[10];
printf("Case #%d:\n", tt);
for(int i = 0; i < m; i++){
scanf("%s", t);
if(t[0] == 'C'){
sa(u);
printf("%d\n", query(1, s[u]));
}else{
sa(u);sa(v);
update(1, s[u], e[u], v);
}
}
}
return 0;
}

M - Vases and Flowers(线段树)

题意:

有$n$个花瓶,标号$0到n-1$,每次输入$k,A,B$,有两种操作:

  1. $k=1,$ 从标号为$A$的花瓶开始顺序放$B$朵花,若花瓶非空,则跳过。问初始放花的花瓶和最后一个放花的花瓶的序号。可以不放完。
  2. $k=2$,标号为$A到B$的花瓶清空。问清空了多少个原本非空的花瓶。

分析:

线段树记录区间空花瓶的个数。
最初想法是:每次都更新到tree[i].cnt == tree[i].r - tree[i].l + 1即该区间内花瓶皆空,$ls,rs$必为这些区间的端点,比较一下即可。但是对于特殊情况,每次都要更新到叶节点,就变成了暴力。。。。
换个思路,我们可以先计算出$A$之前所有空花瓶的个数$cnt$,那么第$cnt+1$个空花瓶即为$ls$,同理第$cnt + B$即为$rs$,找第$cnt$个花瓶就利用线段树查询时递归的性质。
操作二直接区间更新,用一个全局变量记录区间内空花瓶个数。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*************************************************************************
> File Name: P.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/9/5 19:35:17
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
const int maxn = 5e4 + 5;
struct Tree{
int l, r;
int flag;
int cnt;
}tree[maxn << 2];
int ls, rs, cnt;
void push_up(int i)
{
tree[i].cnt = tree[i << 1].cnt + tree[i << 1| 1].cnt;
}
void push_down(int i)
{
if(tree[i].flag != -1){
if(tree[i].flag == 0){
tree[i << 1].cnt = tree[i << 1 | 1].cnt = 0;
tree[i << 1].flag = tree[i << 1 | 1].flag = 0;
}else{
tree[i << 1].cnt = tree[i << 1].flag = tree[i << 1].r - tree[i << 1].l + 1;;
tree[i << 1 | 1].cnt = tree[i << 1 | 1].flag = tree[i << 1 |1].r - tree[i << 1 | 1].l + 1;
}
tree[i].flag = -1;
}
}
void build(int i, int L, int R)
{
tree[i] = (Tree){L, R, -1, R - L + 1};
if(L == R) return;
int mid = L + R >> 1;
build(i << 1, L, mid);
build(i << 1|1, mid + 1, R);
}
int query(int i, int L, int R)
{
if(tree[i].l >= L && tree[i].r <= R) return tree[i].cnt;
push_down(i);
int mid = tree[i].l + tree[i].r >> 1;
if(mid >= R) return query(i << 1, L, R);
else if(mid < L) return query(i << 1|1, L, R);
else return query(i << 1, L, mid) + query(i << 1 | 1, mid + 1, R);
}
int ans;
void update(int i, int L, int R, int k)
{
if(tree[i].l >= L && tree[i].r <= R){
if(k) ans += tree[i].r - tree[i].l + 1 - tree[i].cnt;
tree[i].cnt = (R - L + 1) * k;
tree[i].flag = tree[i].cnt;
return;
}
push_down(i);
int mid = tree[i].l + tree[i].r >> 1;
if(mid >= R) update(i << 1, L, R, k);
else if(mid < L) update(i << 1|1, L, R, k);
else{
update(i << 1, L, mid, k);
update(i << 1 | 1, mid + 1, R, k);
}
push_up(i);
}
int gao(int i, int cnt)
{
if(tree[i].l == tree[i].r) return tree[i].l;
push_down(i);
if(cnt > tree[i << 1].cnt) return gao(i << 1 | 1, cnt - tree[i << 1].cnt);
else return gao(i << 1, cnt);
}
int main (void)
{
int T;scanf("%d", &T);
while(T--){
int n, m;scanf("%d%d", &n, &m);
build(1, 0, n - 1);
int k, A, B;
for(int i = 0; i < m; i++){
scanf("%d%d%d", &k, &A, &B);
if(k == 1){
int cnt1 = query(1, A, n - 1);
if(cnt1 == 0){
puts("Can not put any one.");
continue;
}
int ls = gao(1, tree[1].cnt - cnt1 + 1);
int rs = gao(1, tree[1].cnt - cnt1 + min(cnt1, B));
update(1, ls, rs, 0);
printf("%d %d\n", ls, rs);
}else{
ans = 0;
update(1, A, B, 1);
printf("%d\n", ans);
}
}
puts("");
}
return 0;
}

N - 覆盖的面积(扫描线+线段树)

题意:

给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积.

分析:

矩阵面积交问题,扫描线+ 线段树。
关键思想就是将矩阵上下边拆成两条线段,给每条线段一个标记,标记为上边还是下边,排序+离散化之后,从下往上扫描。题目中说的是被覆盖两次或以上的矩形,那么我们就对每个区间记录区间内被覆盖的长度,以及覆盖两次或以上的线段的长度,每扫到一个边就更新一次,最后将区间内覆盖两次或以上的边长乘上距下一条线段的高度差加到答案中,扫到最后一条边即可。
具体看代码理解。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int, int>p;
#define sa(n) scanf("%d", &(n))
const int maxn = 1e3 + 5, maxm = 1e6 + 5, mod = 1e9 + 7, oo = 0x3f3f3f3f;
double x[maxn];
struct Seg{
double x, y, h;
int k;
bool operator < (const Seg &b)const{
if(h == b.h) return k > b.k;
return h < b.h;
}
}seg[maxn<<1 + 5];
struct Tree{
int l, r;
double len, twice;
int cnt;
}tree[2 * maxn * 4 + 5];
int tot, tott;
int Lower_bound(double xx)
{
int l = 0, r = tott - 1;
while(l <= r){
int mid = l + r >> 1;
if(x[mid] == xx) return mid;
else if(x[mid] > xx) r = mid - 1;
else l = mid + 1;
}
return -1;
}
void build(int l, int r, int i)
{
tree[i].l = l;
tree[i].r = r;
tree[i].len = 0;
tree[i].twice = 0;
tree[i].cnt = 0;
if(l == r) return;
int mid = l + r >> 1;
build(l, mid, i << 1);
build(mid + 1, r, (i << 1)|1);
}
void push_up(int i)
{
if(tree[i].cnt >= 2) {
tree[i].twice =x[tree[i].r + 1] - x[tree[i].l];
tree[i].len = x[tree[i].r + 1] - x[tree[i].l];
}else if(tree[i].cnt == 1){
tree[i].len = x[tree[i].r + 1] - x[tree[i].l];
if(tree[i].l == tree[i].r) tree[i].twice = 0;
else tree[i].twice = tree[i << 1].len + tree[(i << 1) | 1].len;
}else{
if(tree[i].l == tree[i].r) tree[i].twice = 0, tree[i].len = 0;
else {
tree[i].twice = tree[i << 1].twice + tree[(i << 1)|1].twice;
tree[i].len = tree[i << 1].len + tree[(i << 1) | 1].len;
}
}
}
void update(int l, int r, int i, int k)
{
if(l <= tree[i].l && r >= tree[i].r){
tree[i].cnt += k;
push_up(i);
return;
}
int mid = tree[i].l + tree[i].r >> 1;
if(r <= mid) update(l, r, i << 1,k);
else if(l > mid) update(l, r, (i << 1)|1,k);
else{
update(l, mid, i <<1, k);
update(mid + 1, r, (i<<1)|1, k);
}
push_up(i);
}
int main (void)
{
int T;sa(T);
while(T--){
int n;sa(n);
tot = 0, tott = 0;
double x1, x2, y1, y2;
for(int i = 0; i < n; i++){
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
seg[tot++] = (Seg){x1, x2, y1, 1};
seg[tot++] = (Seg){x1, x2, y2, -1};
x[tott++] = x1;
x[tott++] = x2;
}
sort(x, x + tott);
sort(seg, seg + tot);
tott = unique(x, x + tott) - x;
double ans = 0;
build(0, tott - 1, 1);
for(int i = 0; i < tot - 1; i++){
int l = Lower_bound(seg[i].x);
int r = Lower_bound(seg[i].y) - 1;
if(l <= r) update(l, r, 1, seg[i].k);
ans += tree[1].twice * (seg[i + 1].h - seg[i].h);
}
printf("%.2f\n", ans);
}
return 0;
}

坐标那里一定要注意,以区间$[0,5]$为例,他的左右结点分别为$[0,2],[3,5]$,如果我们直接用左右端点来表示线段的话,线段$[2,3]$即无法表示,所以在线段树中我们采用$[l, r -1]$来表示端点为$[l,r]$的线段。

  • 矩阵面积并问题,即可直接用我们线段树中的$len$进行求解。
  • 矩阵周长并,即传说中的轮廓线,求法也类似,可以横着扫一遍再竖着扫一遍,也可以给线段树每个节点增加一个变量$num$,记录该区间内有多少条线段,$ls和rs$来表示当前节点左右端点是否被覆盖(用于去重),那么该竖直方向的长度即为$num 2 高度差$,水平方向直接求扫描线经过一条线段的长度差即可。线段树辅助——扫描线法计算矩形周长并(轮廓线)
    其中$push_up()$的操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void push_up(int i)
    {
    if(tree[i].cnt){
    tree[i].len = (X[tree[i].r + 1] - X[tree[i].l];
    tree[i].ls = tree[i].rs = 1;
    tree[i].num = 1;
    }else if(tree[i].l == tree[i].r){
    tree[i].len = 0;
    tree[i].num = tree[i].rs = tree[i].ls = 0;
    }else{
    tree[i].len = tree[i << 1].len + tree[i << 1 | 1].len;
    tree[i].ls = tree[i << 1].ls;
    tree[i].rs = tree[i << 1 | 1].rs;
    tree[i].num = tree[i << 1].num + tree[i << 1 | 1].num;
    tree[i].num -= (tree[i << 1].rs && tree[i << 1 | 1].ls); //减去重叠覆盖的部分
    }
    }

O - Airport

& P - Radar (DLX重复覆盖问题)

题意:

给定$n$个城市的坐标,要在城市中建$k$个飞机场,使城市距离最近的飞机场的最长距离最小,求这个最小距离。

分析:

最小化最大值,显然二分最大距离。然后我们将距离在范围内的两个城市建边,看能否选出$k$个城市,使得覆盖了所有城市。
将点之间的关系转化成01矩阵的覆盖问题,重复覆盖,建好边套个$DLX$即可。
看了鸟神博客,这里可以直接将所有距离保存在一个数组中,排序并去重,二分下标即可。这样快了很多很多!
P题和这题一个套路,更裸一些。
跳舞链好强大,可惜只会用模板,这个讲的还挺清晰

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*************************************************************************
> File Name: O.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: Fri 08 Jul 2016 03:31:58 PM CST
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
#define sal(n) scanf("%I64d", &(n))
#define sa(n) scanf("%d", &(n))
typedef long long ll;
const int maxn = 3600 + 5, maxc = 60 + 5, maxr = 60 + 5, inf = 0x3f3f3f3f;
struct Node{ll x; ll y;}city[maxn];
int L[maxn], R[maxn], D[maxn], U[maxn], C[maxn];
int S[maxc], H[maxr], size;
int n, m, k;
ll d[maxr][maxc], t[maxn];
void Link(int r, int c)
{
S[c]++; C[size] = c;
U[size] = U[c]; D[U[c]] = size;
D[size] = c; U[c] = size;
U[c] = size;
if(H[r] == -1) H[r] = L[size] = R[size] = size;
else {
L[size] = L[H[r]]; R[L[H[r]]] = size;
R[size] = H[r]; L[H[r]] = size;
}
size++;
}
void remove(int c)
{
for(int i = D[c]; i != c; i = D[i])
L[R[i]] = L[i], R[L[i]] = R[i];
}
void resume(int c)
{
for (int i = U[c]; i != c; i = U[i])
L[R[i]] = R[L[i]] = i;
}
int h()
{///用精确覆盖去估算剪枝
int ret = 0;
bool vis[maxc];
memset (vis, false, sizeof(vis));
for(int i = R[0]; i; i = R[i]){
if(vis[i]) continue;
ret++;
vis[i] = true;
for (int j = D[i]; j != i; j = D[j])
for (int k = R[j]; k != j; k = R[k])
vis[C[k]] = true;
}
return ret;
}
int ans;
bool Dance(int a) //具体问题具体分析
{
if(a + h() > k) return 0;
if(!R[0]) return a <= k;
int c = R[0];
for (int i = R[0]; i; i = R[i])
if(S[i] < S[c]) c = i;
for(int i = D[c]; i != c; i = D[i]){
remove(i);
for(int j = R[i]; j != i; j = R[j])
remove(j);
if(Dance(a + 1)) return 1;
for (int j = L[i]; j != i; j = L[j])
resume(j);
resume(i);
}
return 0;
}
void initL(int x)///col is 1~x,row start from 1
{
for (int i = 0; i <= x; ++i){
S[i] = 0;
D[i] = U[i] = i;
L[i+1] = i; R[i] = i+1;
}///对列表头初始化
R[x] = 0;
size = x + 1;///真正的元素从m+1开始
memset (H, -1, sizeof(H));
///mark每个位置的名字
}
ll dist(int i, int j)
{
ll d = abs(city[i].x - city[j].x) + abs(city[i].y - city[j].y);
return d;
}
bool judge(ll mid)
{
initL(n);
ans = 0x3f3f3f3f;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(d[i][j] <= mid){
Link(i, j);
}
}
}
return Dance(0);
}
int main (void)
{
int T;sa(T);
for(int tt = 1; tt <= T; tt++){
sa(n);sa(k);
ll maxd = 0;
int cnt = 0;
for(int i = 1; i <= n; i++){
scanf("%I64d%I64d", &city[i].x, &city[i].y);
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
d[i][j] = dist(i, j);
t[cnt++] = d[i][j];
}
}
sort(t, t + cnt);
int ncnt = unique(t, t + cnt) - t;
ll l = -1, r = ncnt;
while(r - l > 1){
ll mid = (l + r) / 2;
if(judge(t[mid])) r = mid;
else l = mid;
}
printf("Case #%d: %I64d\n", tt, t[r]);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/******************************
> File Name: P.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: Fri 08 Jul 2016 03:31:58 PM CST
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
#define sal(n) scanf("%I64d", &(n))
#define sa(n) scanf("%d", &(n))
typedef long long ll;
const double eps = 1e-8;
const int maxn = 3600 + 5, maxc = 60 + 5, maxr = 60 + 5, inf = 0x3f3f3f3f;
struct Node{ll x; ll y;}city[maxn], r[maxn];
int L[maxn], R[maxn], D[maxn], U[maxn], C[maxn];
int S[maxc], H[maxr], size;
int n, m, k;
ll d[maxr][maxc], t[maxn];
void Link(int r, int c)
{
S[c]++; C[size] = c;
U[size] = U[c]; D[U[c]] = size;
D[size] = c; U[c] = size;
U[c] = size;
if(H[r] == -1) H[r] = L[size] = R[size] = size;
else {
L[size] = L[H[r]]; R[L[H[r]]] = size;
R[size] = H[r]; L[H[r]] = size;
}
size++;
}
void remove(int c)
{
for(int i = D[c]; i != c; i = D[i])
L[R[i]] = L[i], R[L[i]] = R[i];
}
void resume(int c)
{
for (int i = U[c]; i != c; i = U[i])
L[R[i]] = R[L[i]] = i;
}
int h()
{///用精确覆盖去估算剪枝
int ret = 0;
bool vis[maxc];
memset (vis, false, sizeof(vis));
for(int i = R[0]; i; i = R[i]){
if(vis[i]) continue;
ret++;
vis[i] = true;
for (int j = D[i]; j != i; j = D[j])
for (int k = R[j]; k != j; k = R[k])
vis[C[k]] = true;
}
return ret;
}
int ans;
bool Dance(int a) //具体问题具体分析
{
if(a + h() > k) return 0;
if(!R[0]) return a <= k;
int c = R[0];
for (int i = R[0]; i; i = R[i])
if(S[i] < S[c]) c = i;
for(int i = D[c]; i != c; i = D[i]){
remove(i);
for(int j = R[i]; j != i; j = R[j])
remove(j);
if(Dance(a + 1)) return 1;
for (int j = L[i]; j != i; j = L[j])
resume(j);
resume(i);
}
return 0;
}
void initL(int x)///col is 1~x,row start from 1
{
for (int i = 0; i <= x; ++i){
S[i] = 0;
D[i] = U[i] = i;
L[i+1] = i; R[i] = i+1;
}///对列表头初始化
R[x] = 0;
size = x + 1;///真正的元素从m+1开始
memset (H, -1, sizeof(H));
///mark每个位置的名字
}
double dist(int i, int j)
{
double d = (city[i].x - r[j].x) * (city[i].x - r[j].x) + (city[i].y - r[j].y) * (city[i].y - r[j].y);
return sqrt(d);
}
bool judge(double mid)
{
initL(n);
ans = 0x3f3f3f3f;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
if(dist(j, i) < eps + mid){
Link(i, j);
}
}
}
Dance(0);
// cout<<ans<<' '<<k<<endl;
return ans <= k;
}
int main (void)
{
int T;sa(T);
while(T--){
sa(n);sa(m);sa(k);
for(int i = 1; i <= n; i++){
scanf("%lf%lf", &city[i].x, &city[i].y);
}
for(int i = 1; i <= m; i++){
scanf("%lf%lf", &r[i].x, &r[i].y);
}
double l = 0, r = 1999;
while(r - l > 1e-8){
double mid = (l + r) / 2.0;
if(judge(mid)) r = mid;
else l = mid;
}
printf("%.6f\n", r);
}
return 0;
}
文章目录
  1. 1. A - Article Decryption(KMP + DP)
    1. 1.1. 题意:
    2. 1.2. 分析:
    3. 1.3. 代码:
  2. 2. B - Special Fish(KM)
    1. 2.1. 题意:
    2. 2.2. 分析:
    3. 2.3. 代码:
  3. 3. C - Bear and Bowling 4(斜率优化dp)
    1. 3.1. 题意:
    2. 3.2. 分析:
    3. 3.3. 代码:
  4. 4. D - Treasure Hunt(线段交)
    1. 4.1. 题意:
    2. 4.2. 分析:
    3. 4.3. 代码:
  5. 5. E - Network(LCA + 并查集)
    1. 5.1. 题意:
    2. 5.2. 分析:
    3. 5.3. 代码:
  6. 6. I- Intellectual Inquiry【dp,构造】
    1. 6.1. 题意:
    2. 6.2. 分析:
    3. 6.3. 代码:
  7. 7. J - Dividing Kingdom II(带权并查集,二分图)
    1. 7.1. 题意:
    2. 7.2. 分析:
    3. 7.3. 代码:
  8. 8. K - Can you answer these queries?(线段树)
    1. 8.1. 题意:
    2. 8.2. 分析:
    3. 8.3. 代码:
  9. 9. L - Assign the task(DFS+线段树)
    1. 9.1. 题意:
    2. 9.2. 代码:
  10. 10. M - Vases and Flowers(线段树)
    1. 10.1. 题意:
    2. 10.2. 分析:
    3. 10.3. 代码:
  11. 11. N - 覆盖的面积(扫描线+线段树)
    1. 11.1. 题意:
    2. 11.2. 分析:
    3. 11.3. 代码:
  12. 12. O - Airport
  13. 13. & P - Radar (DLX重复覆盖问题)
    1. 13.1. 题意:
    2. 13.2. 分析:
    3. 13.3. 代码: