SCUACM 2016集训 7.22【16/20】

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

A - Island of Survival(概率dp)

题意:

给定岛上老虎和鹿的个数,人、老虎和鹿每天随机有两只相遇。若人或鹿和老虎相遇,人或鹿被吃。若老虎相遇,两只老虎就会同归于尽,其余情况什么也不发生。问人存活下来的概率。

分析:

仔细分析,问题可以转化为老虎全部死亡的概率,而这只与老虎有关,鹿和人并不会影响老虎的数量。
若老虎数量奇数,人总会被吃掉。
若偶数,计算老虎两两同归于尽的概率即可。

代码:

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
/*************************************************************************
> File Name: A.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/24 21:51:37
************************************************************************/
#include<iostream>
#include<vector>
#include<cstring>
#include<map>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
const int maxn = 1e3 + 5;
double dp[maxn];
int main (void)
{
int T;scanf("%d", &T);
for(int tt = 1; tt <= T; tt++){
int t, d;scanf("%d%d", &t, &d);
printf("Case %d: ", tt);
if(t & 1){
puts("0");
continue;
}
dp[t] = 1;
for(int i = t; i >= 2; i -= 2){
dp[i - 2] = dp[i] * (double)(i * (i - 1)) / (double)(i * (i + 1));
}
printf("%.10f\n", dp[0]);
}
return 0;
}

B - Where to Run(概率dp)

题意:

给定$n个点(1 \le n \le 15)构成的$无向图,从$0$出发,在一个点处可以去往相邻的点,或者在该点继续呆5分钟,每个选择概率相等。问遍历完整个图的期望时间。

分析:

$n$只有$15$,容易想到状压
而对于某一点期望$dp[u], dp[u] = 5 + dp[u] + \sum_{v与u相连且未访问过}{dp[v] + val[u][v]}$
整理一下再套个记忆化搜索外壳就好啦~
注意!要考虑不可达的状态!!!

代码:

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
/*************************************************************************
> File Name: B.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/24 22:17:10
************************************************************************/
//要考虑不可达的状态
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
#define sa(n) scanf("%d", &(n))
using namespace std;
const int maxn = 15 + 1, maxm = 5e2 + 5;
double dp[1 << maxn][maxn];//当期那在j,访问情况i
bool vis[1 << maxn][maxn];//当期那在j,访问情况i
struct edge{int to;int val;int next;}edges[maxm];
int head[maxn];
int tot;
int t;
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w)
{
edges[tot].to = v;
edges[tot].val = w;
edges[tot].next = head[u];
head[u] = tot++;
}
bool dfs(int state, int u)
{
if(vis[state][u]) return state == t || dp[state][u] > 0;
vis[state][u] = true;
int cnt = 0;
int nstate;
double ans = 0;
for(int i = head[u]; i != -1; i = edges[i].next){
int v = edges[i].to;
if((state >> v) & 1) continue;
nstate = state | (1 << v);
if(!dfs(nstate, v)) continue;
cnt++;
ans += dp[nstate][v] + edges[i].val;
}
if(!cnt) return false;
ans = (double) (ans + 5)/(double)cnt;
dp[state][u] = ans;
return true;
}
int main (void)
{
int T;sa(T);
for(int kas = 1; kas <= T; kas++){
int n, m;sa(n);sa(m);
int x, y, w;
init();
for(int i = 0; i < m; i++){
sa(x);sa(y);sa(w);
addedge(x, y, w);
addedge(y, x, w);
}
memset(vis, false, sizeof(vis));
memset(dp, 0, sizeof(dp));
t = (1 << n) - 1;
for(int i = 0; i < n; i++){
vis[t][i] = true;
}
dfs(1, 0);
printf("Case %d: %.10f\n", kas, dp[1][0]);
}
return 0;
}

C - Batting Practice(概率+公式推导)

题意:

已知一枚子弹命中概率,求连续$n$次不命中或连续$m$次命中时发射子弹数的期望。

分析:

较为复杂的公式推导,晚些把公式$markdown$上来了,占个坑。
!!!注意精度问题!!!

代码:

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
/*************************************************************************
> File Name: C.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/24 22:20:39
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
double quick_pow(double x, int a)
{
double ans = 1.0;
for(; a; a >>= 1, x = x * 1.0 * x){
if(a & 1) ans = ans * 1.0 * x;
}
return ans;
}
int main (void)
{
int T;scanf("%d", &T);
for(int kas = 1; kas <= T; kas++){
double p;scanf("%lf", &p);
int k1, k2;scanf("%d%d", &k1, &k2);
double ans;
if(p == 1){
ans = k2;
}else if(p == 0){
ans = k1;
}else{
double q = 1 - p;
double xx = (1.0 - pow(q, k1 - 1)) / p;
double yy = (1.0 - pow(p, k2 - 1)) / q;
double a0 = (xx * yy * q + yy) /(1.0 - xx * yy * p * q) ;
double b0 = xx * (p * a0 + 1.0);
ans = a0 * p + q * b0 + 1.0;
}
printf("Case %d: %.10f\n", kas, ans);
}
}

D - Race to 1 Again(概率dp)

题意:

给定一个数,每次可以选择数的一个因子,用该除上这个因子得到一个新的数,问该数变为1的期望操作次数为多少?

分析:

设$x$有因子$cnt$个,那么$dp[x]=1+dp[x]/cnt+dp[1]/cnt+…$
化简后为$dp[x]=(cnt+tot)/(cnt-1)$
其中$tot$为因数的$dp$之和。

代码:

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 5;
double dp[maxn];
bool vis[maxn];
double gao(int x)
{
if(vis[x]) return dp[x];
vis[x] = true;
int cnt = 2;
dp[x] = dp[1];
for(int i = 2; i * i <= x; i++){
if(x % i) continue;
dp[x] += gao(i);
cnt++;
if(i * i != x){
cnt++;
dp[x] += gao(x / i);
}
}
dp[x] = (dp[x] + cnt) / (double)(cnt - 1);
return dp[x];
}
int main (void)
{
int T;scanf("%d", &T);
memset(vis, false, sizeof(vis));
dp[1] = 0;vis[1] = true;
for(int tt = 1; tt <= T; tt++){
int n;scanf("%d", &n);
printf("Case %d: %.10f\n",tt, gao(n));
}
return 0;
}

E - Power of Matrix(二分+矩阵快速幂)

题意:

给定$n \times n$矩阵A,求$A+A^2+A^3+…+A^k$

分析:

QAQ清楚的记得线代老师讲过这个问题,构造出(都是$2 \times 2$ 的矩阵,不知为何换行显示不出来QAQ)
$B= \begin{bmatrix} A & E \ 0 & E \ \end{bmatrix}$
$B^k = \begin{bmatrix} A^k & E + A + A ^ 2 + A^3+…+A^K \ 0 & E \ \end{bmatrix}$
还有一个方法:倍增法
设 $f[k] = A + A^1 + … + A^k$
$若k为奇数$,
$f[k] = (A + A^1 + … + A^(k/2)) + (A + A^1 + … + A^(k/2)) \times A^(k/2) + A^k$
$ = f(k/2)\times (E+A^{k/2})+A^k$
$若k为偶数$,
$f[k] = (A + A^1 + … + A^(k/2)) + (A + A^1 + … + A^(k/2)) \times A^(k/2) $
$= f(k/2) \times (E+A^{k/2})$
递归下去即可~

代码:

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: E.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/25 18:14:53
************************************************************************/
#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 = 40 + 5, mod = 10;
const int N = 40 + 5;
int n, k;
struct Matrix
{
int row,cal;
int m[N][N];
};
Matrix init(Matrix a, int t)
{
for(int i = 0; i < a.row; i++)
for(int j = 0; j < a.cal; j++)
a.m[i][j] = t;
return a;
}
Matrix mul(Matrix a,Matrix b)
{
Matrix ans;
ans.row = a.row, ans.cal = b.cal;
ans = init(ans, 0);
for(int i = 0; i < a.row; i++)
for(int j = 0; j < b.cal; j++)
for(int k = 0; k < a.cal; k++)
ans.m[i][j] = (ans.m[i][j] + a.m[i][k] * b.m[k][j])%mod;
return ans;
}
Matrix add(Matrix a,Matrix b)
{
Matrix ans;
ans.row = a.row, ans.cal = a.cal;
ans = init(ans, 0);
for(int i = 0;i < a.row; i++){
for(int j = 0;j < a.cal; j++){
ans.m[i][j] = (a.m[i][j] + b.m[i][j]) % mod;
}
}
return ans;
}
Matrix quick_pow(Matrix A, int k)
{
Matrix I;
I.cal = I.row = n;
I = init(I, 0);
for(int i = 0; i < n; i++){
I.m[i][i] = 1;
}
while(k){
if(k & 1) I = mul(I, A);
A = mul(A, A);
k >>= 1;
}
return I;
}
Matrix E;
Matrix gao(Matrix x, int k)
{
if(k == 1) return x;
Matrix tmp;
tmp.cal = tmp.row = n;
tmp = init(tmp, 0);
tmp = gao(x, k / 2);
//tmp = mul(add(E, quick_pow(x, k / 2)), gao(x, k / 2));
tmp = add(tmp, mul(quick_pow(x, k / 2), tmp));
if(k & 1) tmp = add(tmp, quick_pow(x, k));
return tmp;
}
int main (void)
{
while(~scanf("%d%d", &n, &k) && n){
E.cal = E.row = n;
E = init(E, 0);
for(int i = 0; i < n; i++){
E.m[i][i] = 1;
}
Matrix a;
a.row = a.cal = n;
a = init(a, 0);
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
scanf("%d", &a.m[i][j]);
a.m[i][j] %= mod;
}
}
if(!k){
a = E;
}else a = gao(a, k);
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
printf("%d%c", a.m[i][j], j == (n - 1)?'\n':' ');
}
}
puts("");
}
return 0;
}

F - Recurrences(矩阵快速幂)

分析:

$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
/*************************************************************************
> File Name: F.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/25 15:06:54
************************************************************************/
#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 = 15 + 5;
int mod;
ll a[maxn], f[maxn];
int d;
int n;
const int N = 16;
struct Matrix
{
int row,cal;
ll m[N][N];
};
Matrix init(Matrix a, int t)
{
for(int i = 0; i < a.row; i++)
for(int j = 0; j < a.cal; j++)
a.m[i][j] = t;
return a;
}
Matrix mul(Matrix a,Matrix b)
{
Matrix ans;
ans.row = a.row, ans.cal = b.cal;
ans = init(ans,0);
for(int i = 0; i < a.row; i++)
for(int j = 0; j < b.cal; j++)
for(int k = 0; k < a.cal; k++)
ans.m[i][j] = (ans.m[i][j] + a.m[i][k] * b.m[k][j])%mod;
return ans;
}
ll quick_pow(Matrix A)
{
if(n <= d) return f[n];
Matrix I;
n -= d;
I.row = d, I.cal = 1;
I = init(I, 0);
for(int i = 0; i < d; i++){
I.m[i][0] = f[d - i];
}
while(n){
if(n & 1) I = mul(A, I);
A = mul(A, A);
n >>= 1;
}
return I.m[0][0];
}
int main (void)
{
while(scanf("%d%d%d", &d, &n, &mod) &&(n + d + mod)){
for(int i = 1; i <= d; i++) {
scanf("%lld", &a[i]);
a[i] %= mod;
}
for(int i = 1; i <= d; i++){
scanf("%lld", &f[i]);
f[i] %= mod;
}
Matrix A;
A.row = d, A.cal = d;
A = init(A, 0);
for(int i = 0; i < d; i++){
A.m[0][i] = a[i + 1];
}
for(int i = 1; i < d; i++){
A.m[i][i - 1] = 1;
}
printf("%lld\n", quick_pow(A));
}
return 0;
}

H - Yet another Number Sequence(矩阵快速幂)

分析:

裸矩阵快速幂

代码:

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
/*************************************************************************
> File Name: H.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/25 21:21:04
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
int mod;
int f[5] = {0, 10, 100, 1000, 10000};
const int N = 2;
int a, b, m;
ll n;
struct Matrix
{
int row,cal;
int m[N][N];
};
Matrix init(Matrix a, int t)
{
for(int i = 0; i < a.row; i++)
for(int j = 0; j < a.cal; j++)
a.m[i][j] = t;
return a;
}
Matrix mul(Matrix a,Matrix b)
{
Matrix ans;
ans.row = a.row, ans.cal = b.cal;
ans = init(ans, 0);
for(int i = 0; i < a.row; i++)
for(int j = 0; j < b.cal; j++)
for(int k = 0; k < a.cal; k++)
ans.m[i][j] = (ans.m[i][j] + a.m[i][k] * b.m[k][j])%mod;
return ans;
}
Matrix add(Matrix a,Matrix b)
{
Matrix ans;
ans.row = a.row, ans.cal = a.cal;
ans = init(ans, 0);
for(int i = 0;i < a.row; i++){
for(int j = 0;j < a.cal; j++){
ans.m[i][j] = (a.m[i][j] + b.m[i][j]) % mod;
}
}
return ans;
}
Matrix quick_pow(Matrix A, ll k)
{
Matrix I;
I.cal = 1;I.row = 2;
I = init(I, 0);
I.m[0][0] = b;
I.m[1][0] = a;
while(k){
if(k & 1) I = mul(A, I);
A = mul(A, A);
k >>= 1;
}
return I;
}
int main (void)
{
int T;scanf("%d", &T);
while(T--){
scanf("%d%d%lld%d", &a, &b, &n, &m);
mod = f[m];
if(n == 0){
printf("%d\n", a % mod);
continue;
}else if(n == 1){
printf("%d\n", b % mod);
continue;
}
Matrix A;
A.row = A.cal = 2;
A = init(A, 0);
A.m[0][0] = 1;A.m[0][1] = 1;A.m[1][0] = 1;
A = quick_pow(A, n - 1);
printf("%d\n", A.m[0][0] % mod);
}
return 0;
}

I - 棋盘游戏(二分图匹配)

分析:

二分图匹配经典建图方式,每删除一条边,跑一遍二分图匹配,本以为会T,结果数据水水的 = =

代码:

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
/*************************************************************************
> File Name: I.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/26 9:39:23
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<int, int>p;
#define sa(n) scanf("%d", &(n))
const int maxn = 1e2 + 5, oo = 0x3f3f3f3f;
int match[maxn];
int n, m, k;
bool used[maxn];
bool mat[maxn][maxn];
bool dfs(int x)//x -> i
{
for (int i = 0; i < n; i++){
if (!mat[x][i]) continue;
if (used[i]) continue;
used[i] = true;
if (match[i] == -1|| dfs(match[i])){
match[i] = x;
return true;
}
}
return false;
}
int xiong()
{
int ans = 0;
memset(match, -1, sizeof(match));
for (int i = 0; i < n; i++){
memset(used, false, sizeof(used));
ans += dfs(i);
}
return ans;
}
vector<p>v;
int main (void)
{
int kas = 1;
while(~scanf("%d%d%d", &n, &m, &k)){
int x, y;
v.clear();
memset(mat, false, sizeof(mat));
for(int i = 0; i < k; i++){
scanf("%d%d", &x, &y);
mat[x - 1][y - 1] = true;
v.push_back(p(x - 1, y - 1));
}
int ans = xiong();
int cnt = 0;
for(int i = 0; i < v.size(); i++){
x = v[i].first, y = v[i].second;
mat[x][y] = false;
if(xiong() < ans) cnt++;
mat[x][y] = true;
}
printf("Board %d have %d important blanks for %d chessmen.\n", kas++, cnt, ans);
}
return 0;
}

J - Strategic Game(最少顶点覆盖问题)

分析:

最少顶点覆盖问题,原图为二分图,直接利用最少点覆盖等于二分图最大匹配除以2

代码:

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long ll;
const int maxn = 5500 + 5, oo = 0x3f3f3f3f;
int match[maxn];
bool used[maxn];
int n;
int head[maxn];
int tot;
struct EDGE{
int to, next;
}edge[maxn * maxn];
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
bool dfs(int x)//x -> i
{
for(int i = head[x]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(used[v]) continue;
used[v] = true;
if(match[v] == -1 || dfs(match[v])){
match[v] = x;
return true;
}
}
return false;
}
int xiong()
{
int ans = 0;
memset(match, -1, sizeof(match));
for (int i = 0; i < n; i++){
memset(used, false, sizeof(used));
ans += dfs(i);
}
return ans;
}
int main (void)
{
int m;
while(~scanf("%d", &n)){
init();
for(int i = 0; i < n; i++){
int x, y;
scanf("%d", &x);
getchar();getchar();scanf("%d", &m);getchar();
for(int j = 0; j < m; j++){
scanf("%d", &y);
addedge(y, x + n);
addedge(x, y + n);
}
}
printf("%d\n", xiong() / 2);
}
return 0;
}

N - I Hate It(线段树)

分析:

线段树单点更新,区间查询

代码:

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
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=1<<21;
const int INF=0x3fffffff;
int Max=0;
int mid;
struct Node
{
int l,r;//左孩子,右孩子
int value;
}N[maxn];
int p[200005];//记录标号
void build(int i,int left ,int right)//区间[left,right],节点标号i
{
N[i].l=left;
N[i].r=right;
N[i].value=0;
if(left==right) {
p[left]=i;
return;
}
mid=(left+right)>>1;
build(i<<1,left,mid);
build(1+(i<<1),mid+1,right);
}
void update(int i)//从下往上更新单节点
{
if(i==1) return ;
int pi=i/2;
int left=pi<<1;
int right=(pi<<1)+1;
N[pi].value=max(N[left].value,N[right].value);
update(i/2);
}
void query(int i,int left,int right)//从上往下查询节点
{
if(N[i].l==left&&N[i].r==right) {
Max=max(N[i].value,Max);
return;
}
i<<=1;
if(N[i].r>=left&&N[i].r>=right) query(i,left,right);
else if(N[i].r>=left&&N[i].r<=right) query(i,left,N[i].r);
i++;
if(N[i].l<=right&&N[i].l<=left) query(i,left,right);
else if(N[i].l<=right&&N[i].l>=left) query(i,N[i].l,right);
}
int main (void)
{
int n,m;
int A,B;
char c[2];
while(scanf("%d%d",&n,&m)==2) {
build(1,1,n);
for(int i=1;i<=n;i++) {
scanf("%d",& N[p[i]].value);
update(p[i]);
}
for(int i=1;i<=m;i++) {
scanf("%s%d%d",c,&A,&B);
if(c[0]=='Q') {
Max=0;
query(1,A,B);
printf("%d\n",Max);
}
else {
N[p[A]].value=B;
update(p[A]);
}
}
}
return 0;
}

O - 敌兵布阵(zkw线段树)

分析:

单点更新,区间查询,人生第一道zkw线段树,有趣!

代码:

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
#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 = 1e5 + 5;
int M;
int T[maxn << 2];
void build(int n)
{
for(M = 1; M <= n + 1; M <<= 1);
for(int i = M + 1; i <= M + n; i++) scanf("%d", &T[i]);
for(int i = M - 1; i; i--){
T[i] = T[i << 1] + T[i << 1 | 1];
}
}
void add(int a, int x)
{
T[a += M] += x;
for(a >>= 1;a;a >>= 1){
T[a] = T[a << 1] + T[a << 1|1];
}
}
ll query(int l, int r)
{
l = l + M - 1, r = r + M + 1;
ll ans = 0;
for(; l^r^1; l >>= 1, r >>= 1){
if(~l & 1) ans += T[l ^ 1];
if(r & 1) ans += T[r ^ 1];
}
return ans;
}
char s[10];
int main(void)
{
int kas; scanf("%d", &kas);
for(int tt = 1; tt <= kas; tt++){
int n;scanf("%d", &n);
build(n);
printf("Case %d:\n", tt);
while(scanf("%s", s)){
if(s[0] == 'E') break;
int x, y;
scanf("%d%d", &x, &y);
if(s[0] == 'A') add(x, y);
else if(s[0] == 'S') add(x, -y);
else printf("%lld\n", query(x, y));
}
}
return 0;
}

P - 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;
}

Q - Jungle Roads(最小生成树)

分析:

裸最小生成树。
注意

除了cin>>之外,其他函数都不会忽略第一个有效字符之前的字符,也就是会读取之前输入残留的换行符\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
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
const int maxn = 100 + 5, maxm = 900 + 6;
struct EDGE{
int from, to, val;
bool operator < (const EDGE& a) const{
return val < a.val;
}
}edge[maxm];
int pa[maxn];
int n;
int tot;
void init(int n)
{
tot = 0;
for(int i = 0; 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 a, int b)
{
int ra = _find(a), rb = _find(b);
if(ra == rb) return;
pa[rb] = ra;
}
int kruskal(int n)
{
sort(edge, edge + tot);
int ans = 0;
for(int i = 0; i < tot; i++){
int u = edge[i].from;
int v = edge[i].to;
int ru = _find(u), rv = _find(v);
if(ru == rv) continue;
unit(ru, rv);
ans += edge[i].val;
}
return ans;
}
int main (void)
{
int m, x;
char c, b;
while(~scanf("%d", &n) && n){
init(n);
for(int i = 0; i < n - 1; i++){
cin>>b>>m;
for(int j = 0; j < m; j++){
cin>>c>>x;
edge[tot].from = i;
edge[tot].to = c - 'A';
edge[tot++].val = x;
}
}
printf("%d\n", kruskal(n));
}
return 0;
}

R - Building a Space Station(最小生成树)

题意:

给定$n(n \le 100)$个圆的半径和圆心,若两个圆重叠或覆盖,则视为相连通。现在两个圆的圆周上建一条边,使得该两个圆联通。若使所有圆之间相互连通,问建边的最小长度和是多少。

分析:

$n只有100$,先$n^2$求出任两个圆之间的距离,并判断是否重叠或覆盖,若连通则将他们并在一起。最终形成若干连通块,而只要在连通块之间建边,求使连通块相连的最小花费即可。这样就转化为最小生成树问题。
建边必然是按照连通块之间的最小直线距离来建,最后再一次$n^2$枚举每个点,求出连通块之间的最小距离。最后求个最小生成树即可。

代码:

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 1e2 + 5;
double dist1[maxn][maxn], dist2[maxn][maxn];
struct POINT{
double x, y, z, r;
}p[maxn];
struct EDGE{
int from, to, next;
double val;
bool operator < (const EDGE& a) const{
return val < a.val;
}
}edge[maxn * maxn];
int pa[maxn], head[maxn];
int n;
int tot;
vector<int>v;
double eps = 1e-8;
void init(int n)
{
tot = 0;
memset(head, -1, sizeof(head));
for(int i = 0; i <= n; i++) pa[i] = i;
}
void addedge(int from, int to, double val)
{
edge[tot].from = from;
edge[tot].to = to;
edge[tot].val = val;
edge[tot].next = head[from];
head[from] = tot++;
}
int _find(int x)
{
if(x == pa[x]) return x;
return pa[x] = _find(pa[x]);
}
void unit(int a, int b)
{
int ra = _find(a), rb = _find(b);
if(ra == rb) return;
pa[rb] = ra;
}
double kruskal(int n)
{
sort(edge, edge + tot);
for(int i = 0; i <= n; i++) pa[i] = i;
double ans = 0;
for(int i = 0; i < tot; i++){
int u = edge[i].from;
int v = edge[i].to;
int ru = _find(u), rv = _find(v);
if(ru == rv) continue;
unit(ru, rv);
ans += edge[i].val;
}
return ans;
}
double dist(POINT& a, POINT& b)
{
double t = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z);
return sqrt(t);
}
int main (void)
{
int n;
while(~scanf("%d", &n) && n){
init(n);
for(int i = 0; i < n; i++){
scanf("%lf%lf%lf%lf", &p[i].x, &p[i].y, &p[i].z, &p[i].r);
}
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
dist1[i][j] = dist(p[i], p[j]);
dist2[i][j] = 400.0;
if(dist1[i][j] + eps < p[i].r + p[j].r) unit(i, j);
}
}
for(int i = 0; i < n; i++){
int ri = _find(i);
for(int j = i + 1; j < n; j++){
int rj = _find(j);
if(ri == rj) continue;
dist2[ri][rj] = dist2[rj][ri] = min(dist2[ri][rj], dist1[i][j] - p[i].r -p[j].r);
}
}
v.clear();
for(int i = 0; i < n; i++){
if(pa[i] != i) continue;
v.push_back(i);
}
int sz = v.size();
for(int i = 0; i < sz; i++){
for(int j = i + 1; j < sz; j++){
addedge(i, j, dist2[v[i]][v[j]]);
}
}
printf("%.3f\n", kruskal(sz));
}
return 0;
}

S - Borg Maze(最小生成树)

题意:

给定图,给定起点,每走到一个外星人点,可以拆分出两个及以上的其他人继续同时走其他未走过的点,问所有点都被访问过后,所有人走的最小距离和。

分析:

先$bfs$求出任意两点之间的最小距离,因为人拆分的个数不受限制,那么我们每到一个点(这里的点指外星人点,下同),就假设拆分出足够多的人到其他所有点,即在任意两点之间建边,问题就转化为求将这些点连接起来的所有边的和的最小值,即为最小生成树问题。
一个大坑就是,$m$和$n$后面可能会有很多个空格,不能用$getchar()$,用$gets()$可以ac,可是用$getline()$就RE到哭,不懂为什么?路过的大神求赐教!

代码:

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: 1135.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/9/13 9:41:30
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
typedef pair<int, int>p;
const int maxn = 50 + 5, maxm = 1e2 + 5;
int dx[4] ={-1, 1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int n, m;
int tot;
int head[maxm];
int mp[maxn][maxn];
int d[maxn][maxn][maxn][maxn];
bool vis[maxn][maxn];
int pa[maxm];
vector<p>v;
void init()
{
v.clear();
tot = 0;
memset(head, -1, sizeof(head));
memset(d, 0, sizeof(d));
memset(vis, false, sizeof(vis));
}
struct EDGE{
int from, to, val;
bool operator < (const EDGE& a)const{
return val < a.val;
}
}edge[maxn * maxn];
void addedge(int from, int to, int val)
{
edge[tot].from = from;
edge[tot].to = to;
edge[tot].val = val;
head[from] = tot++;
}
int _find(int x)
{
if(pa[x] == x) return x;
return pa[x] = _find(pa[x]);
}
void unite(int u, int v)
{
int ru = _find(u), rv = _find(v);
if(ru == rv) return;
pa[ru] = rv;
}
void prebfs(int si, int sj)
{
queue<p>q;
memset(vis, false, sizeof(vis));
vis[si][sj] = true;
d[si][sj][si][sj] = 0;
q.push(p(si, sj));
while(!q.empty()){
p t = q.front();q.pop();
int x = t.first, y = t.second;
int xx, yy;
for(int i = 0; i < 4; i++){
xx = x + dx[i], yy = y + dy[i];
if(xx < 0 || xx >= n || yy < 0 || yy >= m) continue;
if(mp[xx][yy] == 0 || vis[xx][yy]) continue;
d[xx][yy][si][sj] = d[x][y][si][sj] + 1;
// cout<<xx<<" "<<yy<<" "<<si<<" "<<sj<<' '<<d[xx][yy][si][sj]<<endl;
vis[xx][yy] = true;
q.push(p(xx, yy));
}
}
}
int kruskal()
{
sort(edge, edge + tot);
int ans = 0;
int u, v;
for(int i = 0; i < tot; ++i){
u = edge[i].from;
v = edge[i].to;
if(_find(v) == _find(u)) continue;
unite(u, v);
ans += edge[i].val;
}
return ans;
}
//char s[maxn];
char s[maxn];
//string tmp;
int main (void)
{
int T;scanf("%d", &T);
while(T--){
init();
scanf("%d%d", &m, &n);
gets(s);
// getline(cin, s);
for(int i = 0; i < n; i++){
// getline(cin, s);
gets(s);
for(int j = 0; j < m; ++j){
if(s[j] == '#') mp[i][j] = 0;
else if(s[j] == 'A') mp[i][j] = 2,v.push_back(p(i, j));
else if(s[j] == ' ') mp[i][j] = 1;
else v.push_back(p(i, j)), mp[i][j] = 2;
}
//printf("%s", s);
//cout<<s<<endl;
}
int sz = v.size();
for(int i = 0; i < sz; ++i){
pa[i] = i;
prebfs(v[i].first, v[i].second);
for(int j = i + 1; j < sz; j++){
// cout<<"add"<<v[i].first<<" "<<v[i].second<<" "<<v[j].first<<" "<<v[j].second<<" "<< d[v[j].first][v[j].second][v[i].first][v[i].second]<<endl;
addedge(i, j , d[v[j].first][v[j].second][v[i].first][v[i].second]);
}
}
printf("%d\n", kruskal());
}
return 0;
}

T - Constructing Roads(最小生成树)

分析:

裸最小生成树

代码:

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
/*************************************************************************
> File Name: T.cpp
> Author: jiangyuzhu
> Mail: 834138558@qq.com
> Created Time: 2016/7/25 14:36:35
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
const int maxn = 1e2 + 5, maxm = 1e4 + 5;
int pa[maxn];
struct edge{int from; int to; int val;int next;};
edge edges[maxm];
int head[maxn];
int tot, n;
void init()
{
memset(head, -1, sizeof(head));
tot = 0;
for(int i = 1; i <= n; i++){
pa[i] = i;
}
}
void addedge(int u, int v, int a)
{
edges[tot].to = v;
edges[tot].from = u;
edges[tot++].val = a;
}
bool cmp(const edge &e1,const edge &e2)
{
return e1.val < e2.val;
}
int _find(int a)
{
if(a == pa[a]) return a;
else return pa[a] = _find(pa[a]);
}
void unite(int a, int b)
{
int ra = _find(a), rb = _find(b);
if(ra == rb) return;
pa[rb] = ra;
return;
}
bool same(int a, int b)
{
return _find(a) == _find(b);
}
int kruskal(int cnt)
{
sort(edges, edges + tot, cmp);
int res = 0;
for(int i = 0; i < tot; i++){
if(!same(edges[i].from,edges[i].to)){
unite(edges[i].from, edges[i].to);
res += edges[i].val;
if(++cnt == n - 1) return res;
}
}
return res;
}
int main (void)
{
while(~scanf("%d", &n)){
init();
int x, y;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
scanf("%d", &x);
if(i == j) continue;
addedge(i, j, x);
}
}
int cnt = 0;
int Q;scanf("%d", &Q);
while(Q--){
scanf("%d%d", &x, &y);x--;y--;
if(!same(x, y)){
unite(x, y);
cnt++;
}
}
printf("%d\n", kruskal(cnt));
}
}
文章目录
  1. 1. A - Island of Survival(概率dp)
    1. 1.1. 题意:
    2. 1.2. 分析:
    3. 1.3. 代码:
  2. 2. B - Where to Run(概率dp)
    1. 2.1. 题意:
    2. 2.2. 分析:
    3. 2.3. 代码:
  3. 3. C - Batting Practice(概率+公式推导)
    1. 3.1. 题意:
    2. 3.2. 分析:
    3. 3.3. 代码:
  4. 4. D - Race to 1 Again(概率dp)
    1. 4.1. 题意:
    2. 4.2. 分析:
    3. 4.3. 代码:
  5. 5. E - Power of Matrix(二分+矩阵快速幂)
    1. 5.1. 题意:
    2. 5.2. 分析:
    3. 5.3. 代码:
  6. 6. F - Recurrences(矩阵快速幂)
    1. 6.1. 分析:
    2. 6.2. 代码:
  7. 7. H - Yet another Number Sequence(矩阵快速幂)
    1. 7.1. 分析:
    2. 7.2. 代码:
  8. 8. I - 棋盘游戏(二分图匹配)
    1. 8.1. 分析:
    2. 8.2. 代码:
  9. 9. J - Strategic Game(最少顶点覆盖问题)
    1. 9.1. 分析:
    2. 9.2. 代码:
  10. 10. N - I Hate It(线段树)
    1. 10.1. 分析:
    2. 10.2. 代码:
  11. 11. O - 敌兵布阵(zkw线段树)
    1. 11.1. 分析:
    2. 11.2. 代码:
  12. 12. P - Vases and Flowers(线段树)
    1. 12.1. 题意:
    2. 12.2. 分析:
    3. 12.3. 代码:
  13. 13. Q - Jungle Roads(最小生成树)
    1. 13.1. 分析:
    2. 13.2. 代码:
  14. 14. R - Building a Space Station(最小生成树)
    1. 14.1. 题意:
    2. 14.2. 分析:
    3. 14.3. 代码:
  15. 15. S - Borg Maze(最小生成树)
    1. 15.1. 题意:
    2. 15.2. 分析:
    3. 15.3. 代码:
  16. 16. T - Constructing Roads(最小生成树)
    1. 16.1. 分析:
    2. 16.2. 代码: