https://atcoder.jp/contests/abc173/tasks

E - Multiplication 4

 

题意:给定n个数,要选k个数,乘积最大。

很典型的贪心了。

只有在非负数没有选择空间时最终结果才可能是负数。在这种情况下直接选绝对值最小的负数即可。

其它情况下结果都是非负数。先选择k个绝对值最大的数,如果其中有奇数个负数,则要换掉一个负数变为非负数,或者换掉一个非负数变为负数。这两种情况都要模拟,最终选更大的情况。

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<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int N = 1e6 + 10;
int n, k;
int cp, cn;
typedef pair<ll, int>pii;
vector<pii>vc, vv;
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
ll x;
scanf("%lld", &x);
if (x < 0)vc.push_back(pii(-x, 1)), cn++;
else vc.push_back(pii(x, 0)), cp++;
}
if (k == n && (k & 1) != (cp & 1) || cp == 0 && (k & 1)) {
ll ans = 1ll;
sort(vc.begin(), vc.end());
for (int i = 0; i < k; i++)ans = ans * vc[i].first % mod;
ans = (mod - ans) % mod;
printf("%lld\n", ans);
}
else {
ll ans = 1ll;
int cnt = 0;
sort(vc.begin(), vc.end(), greater<pii>());
for (int i = 0; i < k; i++) {
vv.push_back(vc[i]);
cnt += vc[i].second;
}
if (cnt & 1) {
ll tmp1 = vv.back().first, tmp2;
int p1 = -1, p2 = -1, q = -1;
int t = vv.back().second;
for (int i = (int)vv.size() - 2; i >= 0; i--) {
if (vv[i].second != t) {
tmp2 = vv[i].first;
q = i;
break;
}
}
if (q == -1) {
for (int i = k; i < (int)vc.size(); i++) {
if (vc[i].second != t) {
vv.pop_back();
vv.push_back(vc[i]);
break;
}
}
}
else {
for (int i = k; i < (int)vc.size() && (p1 == -1 || p2 == -1); i++) {
if (vc[i].second != t) {
if (p1 == -1) {
p1 = i;
tmp2 *= vc[i].first;
}
}
else {
if (p2 == -1) {
p2 = i;
tmp1 *= vc[i].first;
}
}
}
if (p2 == -1 || p1 != -1 && tmp1 < tmp2) {
vv.pop_back();
vv.push_back(vc[p1]);
}
else {
vv.erase(vv.begin() + q);
vv.push_back(vc[p2]);
}
}
}
for (pii p : vv)ans = ans * p.first%mod;
printf("%lld\n", ans);
}
return 0;
}

 

F - Intervals on Tree

 

题意:给定一棵树,f(L,R)f(L,R) 为由下标在区间 [L,R][L,R] 中的节点导出的子图的联通块个数,求所有区间的值之和。

dp

dp[i]dp[i] 表示以 ii 为右端点的所有区间的值之和。

每次加入一个节点,该结点连出的边可能减少联通块个数,所以要看在上一点的基础上增加了多少,减少了多少。

由于与 i-1 比较,所以限制连边只由大节点连向小节点。

设 u 连向的最大的节点为 v,则当区间左端点大于 v 时,u 都单独作为一个联通块,所以共增加了 u-v 个联通块。

u 连出的第一条边并不会使减少联通块个数,从第 2 条边起,连向点 viv_i,则必定减少了一个联通块,且对于所有包含 viv_i,即左端点小于等于 viv_i,右端点为 u 的区间来说,都少了一个联通块,所以共少了 viv_i 个联通块。

由于通过 u 连接的两个联通块之前必定不会联通(否则有环),所以这是正确的。

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
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int N = 1e6 + 10;
int n;
vector<int>G[N];
ll dp[N], ans;
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
G[max(u, v)].push_back(min(u, v));
}
ans = dp[1] = 1ll;
for (int i = 2; i <= n; i++) {
sort(G[i].begin(), G[i].end(), greater<int>());
int mx = 0;
if (!G[i].empty())mx = G[i][0];
dp[i] = i - mx + dp[i - 1];
for (int j = 1; j < (int)G[i].size(); j++) {
int v = G[i][j];
dp[i] -= v;
}
ans += dp[i];
}
printf("%lld\n", ans);
return 0;
}