(马后炮)解题报告.

Table of Contents

  1. Overview
  2. A. Benches
    1. Description
    2. Solution
  3. B. Vitamins
    1. Description
    2. Solution
  4. C. Array Product
    1. Description
    2. Solution
  5. D. Petya and Array
    1. Description
    2. Solution
  6. E. Vasya and Magic Matrix
    1. Description
    2. Solution
  7. F. Leaf Sets
    1. Description
    2. Solution

Overview

本来是register了, 但是因为上课所以并没有打. 比赛的时候口胡了两个题, 下场调了一会倒是过了, AB比较水就另说. 然后又口胡了一下E, 大概是个期望维护个前缀和就可以转移了的说. 大概也就能做到这样子. F还是有点仙的, 不过不难理解.

A. Benches

Description

长凳上已经坐了一些人, 又要来一些人, 问所有长凳上最多的人的最大可能和最小可能.

Solution

堆+贪心乱搞一通.

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
//maki
#include<bits/stdc++.h>

using namespace std;

const int maxn = 105;

int n, a[maxn], m;
priority_queue<int, vector<int>, greater<int> >q;

int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);

cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i], q.push(a[i]);
for(int i = 1; i <= m; ++i)
{
int u = q.top(); q.pop();
u++;
q.push(u);
}
int mx = 0;
while(!q.empty()) mx = max(mx, q.top()), q.pop();
cout << mx << " ";
sort(a + 1, a + 1 + n);
cout << a[n] + m << endl;
return 0;
}

B. Vitamins

Description

每种商品含若干种维他命, 问获取所有维他命的最小花费, 种类 $\le 3$.

Solution

要是种类很多可以DP吧? 不过3嘛, 嘿嘿嘿.

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
#include <bits/stdc++.h>

using namespace std;

const int maxn = 1005;

int n, co[maxn];
int a, b, c, ab, bc, ac, abc;

int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
string s;

a = b = c = 400005;
ac = bc = ab = abc = 400005;

cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> co[i] >> s;
if(s.size() == 1)
{
if(s[0] == 'A')
a = min(a, co[i]);
else if(s[0] == 'B')
b = min(b, co[i]);
else c = min(c, co[i]);
}
else if(s.size() == 2)
{
int has[3] = {0,0,0};
has[s[0]-'A'] = 1;
has[s[1]-'A'] = 1;
if(has[0] && has[1]) ab = min(ab, co[i]);
else if(has[1] && has[2]) bc = min(bc, co[i]);
else ac = min(ac, co[i]);
}
else
{
abc = min(abc, co[i]);
}
}
int ans = 400005;
ans = min(ans, min(a + b + c, abc));
ans = min(ans, min(ab + bc, ab + ac));
ans = min(ans, ac + bc);
ans = min(ans, min(ab + c, bc + a));
ans = min(ans, ac + b);
if(ans < 400005) cout << ans << endl;
else puts("-1");
return 0;
}

C. Array Product

Description

有两种操作, 第一种是把两个数乘起来放到一个位置, 另一个删掉, 第二种是删掉一个数. 删除后下标不变, 但不能使用. 最大化剩下的一个数的结果.

Solution

有正有负, 肯定就看负数能不能乘出一个整数嘛, 不能就和0合并一下然后删掉.

能的话, 就把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
//maki
#include <bits/stdc++.h>

using namespace std;

const int maxn = 2e+5 + 5;

vector<int> zrs;
int n, a[maxn], negabs = 0x3f3f3f3f, negpos, negcnt;
int vis[maxn];

int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);

cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> a[i];
if(a[i] < 0)
{
if(abs(a[i]) < negabs) negabs = -a[i], negpos = i;
negcnt ^= 1;
}
if(a[i] == 0) zrs.push_back(i);
}
if(!negcnt)
{
if(zrs.size())
{
for(int i = 1; i < zrs.size(); ++i)
printf("1 %d %d\n", zrs[i], zrs[0]), vis[zrs[i]] = 1;
if((int)zrs.size() == n) return 0;
printf("2 %d\n", zrs[0]); vis[zrs[0]] = 1;
}
for(int i = 1; i <= n; ++i)
{
if(!vis[i])
{
for(int j = i + 1; j <= n; ++j)
if(!vis[j]) printf("1 %d %d\n", j, i);
return 0;
}
}
}
else
{
if(zrs.size())
for(int i = 0; i < zrs.size(); ++i)
printf("1 %d %d\n", zrs[i], negpos), vis[zrs[i]] = 1;
if((int)zrs.size() == n - 1) return 0;
printf("2 %d\n", negpos); vis[negpos] = 1;

for(int i = 1; i <= n; ++i)
{
if(!vis[i])
{
for(int j = i + 1; j <= n; ++j)
if(!vis[j]) printf("1 %d %d\n", j, i);
return 0;
}
}
}
return 0;
}

D. Petya and Array

Description

给一个数组, 求所有和小于 $t$ 的子区间个数.

Solution

如果都是非负数的话, 双指针就好了, 但是有负数.

一个经典思路然而我并没想到, 好像还讲过, 分治, 每次只考虑跨越当前区间中点的答案, 这样在左边维护后缀和, 右边维护前缀和, 排序以后, 是可以双指针的.

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
//maki
#include <bits/stdc++.h>

using namespace std;

const int maxn = 2e+5 + 5;

typedef long long ll;

int a[maxn], n;
ll pre[maxn], suf[maxn];
ll ans, t;

void DaC(int l, int r)
{
if(l == r)
{
if(a[l] < t) ans++;
return;
}
//cout << l << " " << r << endl;
int mid = (l + r) >> 1;
vector<ll> suf, pre; ll sum = 0;
for(int i = mid; i >= l; --i)
sum += a[i], suf.push_back(sum);
sum = 0;
for(int i = mid + 1; i <= r; ++i)
sum += a[i], pre.push_back(sum);
int llen = suf.size(), rlen = pre.size();
sort(suf.begin(), suf.end());
sort(pre.begin(), pre.end());
int j = rlen;
//cout << "--" << endl;
for(int i = 1; i <= llen; ++i)
{
while(j && suf[i - 1] + pre[j - 1] >= t) --j;
//cout << i << " " << j << endl;
ans += j;
}
DaC(l, mid); DaC(mid + 1, r);
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);

cin >> n >> t;
for(int i = 1; i <= n; ++i) cin >> a[i];

DaC(1, n);
cout << ans << endl;
return 0;
}

E. Vasya and Magic Matrix

Description

给一个矩阵, 每个点有值, 每次操作只能从当前的跳到更小值的格子去, 得分是欧几里得距离的平方. 求得分期望.

Solution

先列式子嘛.

然后是转移.

先把式子打开.

这样增量的时候, 把该减的减掉该加的加上就可以增量了, 维护一些前缀和, 前缀平方和, 前缀期望和之类的.

记得相等的值要同时做.

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
//maki
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn = 1005;
const ll p = 998244353;

struct node
{
ll x, y;
ll v;
bool operator < (const node &rhs) const
{
return v < rhs.v;
}
};
int n, m;
ll sumx, sumy, sumxs, sumys, sumf;
ll r, c, f[maxn][maxn], mtr[maxn][maxn];
vector<node> vec;

inline ll quick_power(ll base, ll index)
{
ll ret = 1;
while(index)
{
if(index & 1) ret = (ret * base) % p;
index >>= 1;
base = base * base % p;
}
return ret;
}
inline void add(ll &res, ll num)
{
res += num;
while(res >= p) res -= p;
while(res < 0) res += p;
}
inline ll prod(ll a, ll b)
{
return a * b % p;
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);

cin >> n >> m;
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
{
cin >> mtr[i][j];
}
cin >> r >> c;
r--; c--;

for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
vec.push_back((node){i, j, mtr[i][j]});

sort(vec.begin(), vec.end());

int i = 0;
while(i < (int)vec.size())
{
int j = i;
while(j < (int)vec.size() && vec[j].v == vec[i].v) ++j;

ll inv = -1;
if(i) inv = quick_power(i, p - 2);
for(int k = i; k < j; ++k)
{
ll xx = vec[k].x, yy = vec[k].y;
if(inv == -1)
{
f[xx][yy] = 0;
continue;
}
add(f[xx][yy], prod(sumf, inv));
add(f[xx][yy], prod(xx, xx));
add(f[xx][yy], prod(yy, yy));
add(f[xx][yy], prod(sumxs, inv));
add(f[xx][yy], prod(sumys, inv));
add(f[xx][yy], prod(prod(-xx * 2ll, sumx), inv));
add(f[xx][yy], prod(prod(-yy * 2ll, sumy), inv));

}
for(int k = i; k < j; ++k)
{
ll xx = vec[k].x, yy = vec[k].y;
add(sumf, f[xx][yy]);
add(sumxs, prod(xx, xx));
add(sumys, prod(yy, yy));
add(sumx, xx);
add(sumy, yy);
}
i = j;
}

cout << f[r][c] << endl;
return 0;
}

F. Leaf Sets

Description

把一颗树的叶子节点划分成若干集合, 使得每个集合内最远点对的距离小于等于 $k$. 求最小集合数.

Solution

本来想二分答案, 比如按深度排个序然后顺次验证什么的, 但是不太可做.

有一个性质, 就是在当前节点 $v$, 记她到某个孩子的子树内最深节点的距离为 $d$.

这样就有了若干个 $d$, 当然是要合并了, 这样才能减少集合, 考虑合并的条件, 当且仅当$d_a + d_b \le k$, 合并后的集合, 应该有: $d_u = max(d_a, d_b)$.

再考虑不能合并, 如果有 $d_a + d_b > k$, 且$d_a \le d_b$. 那么$b$接下来一定没有用了, 因为能和她合并的就能和 $a$ 合并. 而她又不能再合并, 所以弹掉, 答案+1.

最后做到根节点会剩一些一定合并的集合, 记得答案+1.

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
//maki
#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e+6 + 5;

struct edge
{
int to, nxt;
}e[maxn << 1];
int ptr, lnk[maxn], dep[maxn], ans, dgr[maxn], rt, n, k;
set<int> st;

inline void add(int bgn, int end)
{
e[++ptr] = (edge){end, lnk[bgn]};
lnk[bgn] = ptr;
}
int dfs(int x, int fa)
{
if(dgr[x] == 1) return 0;
vector<int> s;
for(int p = lnk[x]; p; p = e[p].nxt)
{
int y = e[p].to;
if(y == fa) continue;
int tmp = dfs(y, x);
s.push_back(tmp + 1);
}
//cout << "dealing" << x << endl;
sort(s.begin(), s.end());
for(int i = int(s.size()) - 1; i >= 1; --i)
{
if(s[i] + s[i-1] <= k) break;
ans++;
s.pop_back();
}

return s.back();
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);

cin >> n >> k;
for(int i = 1; i < n; ++i)
{
int u, v;
cin >> u >> v;
add(u, v); add(v, u);
dgr[u]++; dgr[v]++;
}
for(int i = 1; i <= n; ++i) if(dgr[i] != 1) {rt = i; break;}
dfs(rt, 0);

cout << ans + 1 << endl;//remaining elements in vector(rt);
return 0;
}