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

B - Flip Digits

 

题意:给定两个长为n的01串s,t,每次操作对于s串,选择一个为1的位置,并同时翻转该位置和左边一位,问最少操作次数使得两串相同。

贪心

画几个能发现每次操作相当于把1往左移动一位。

从左向右遍历,当 s 不等于 t 时,找到该位右边最近的1,移动到该位右边,翻转这个1,使得 s 在该位等于 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
#include <bits/stdc++.h>
#define debug(x) cout << #x << ":\t" << (x) << endl;
using namespace std;
#define ll long long
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
int n;
char s[N], t[N];
queue<int>q;
int main() {
scanf("%d", &n);
scanf("%s%s", s, t);
for (int i = 0; i < n; i++) {
if (s[i] == '1')q.push(i);
}
ll ans = 0;
for (int i = 0; i < n; i++) {
if (s[i] != t[i]) {
while (!q.empty() && q.front() <= i)q.pop();
if (q.empty()) { puts("-1"); return 0; }
s[q.front()] = '0';
ans += q.front() - i;
q.pop();
}
}
printf("%lld\n", ans);
return 0;
}

 

A - Erasing Vertices

 

题意:给定一张有向图,每次操作选择一个点,删除它以及所有由该点可达的点(包括涉及的边),问期望几次删除所有点。

期望可加

独立计算每个点被删除的期望,由于操作次数一次就能删除,所以期望就是它被选择的概率。

对于一个操作序列,点 u 被手动删除当且仅当所有可以到达它的点都排在它的后面。

设可到达 u 的点数为 mm。则点 u 被手动删除的概率为 1m\frac{1}{m}.

本题要注意的点在于要知道所有操作序列出现的概率都是一样的,即使该序列不能够完整实现。所以应该把所有操作序列都视为长度为 nn 的排列,即操作序列的全集为全排列。

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 <bits/stdc++.h>
#define debug(x) cout << #x << ":\t" << (x) << endl;
using namespace std;
#define ll long long
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
int n;
char s[N];
vector<int>G[N];
int vis[110];
void dfs(int u) {
vis[u] = 1;
for (int v : G[u]) {
if (!vis[v]) {
dfs(v);
}
}
}
double ans;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= n; j++) {
if (s[j] == '1') {
G[j].push_back(i);
}
}
}
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
dfs(i);
int t = 0;
for (int j = 1; j <= n; j++)if (vis[j])t++;
ans += 1.0 / t;
}
printf("%.10lf\n", ans);
return 0;
}

 

C - Robots

 

题意:在一个数轴上,从 0 到 INF ,每个整点上都有一个机器人。有 n 种球,第 ii 种上有数字 AiA_i,第 ii 种有 BiB_i 个。先选一些球,任意修改球上的数字为新的正整数。修改结束后,再进行操作,当操作一个球时,如果初始在 AiA_i 处的那个机器人还在,则把它向左移动一格,如果新位置处有机器人,则那个机器人被摧毁。现在要求把所有球排列,按顺序操作每个球。要求 0 处的机器人不能被摧毁,问初始时最少要修改几个球。1A1<A2<<AN109,1Bi1091 \leq A_1 < A_2 < \ldots < A_N \leq 10^9,1 \leq B_i \leq 10^9

BiB_i 代表 AiA_i 号机器人必须移动的次数,除非它被摧毁了。

如果某个机器人会移动到 0 处,则它必须被摧毁,定义它为“不安全的”。否则不需要摧毁,定义它为“安全的”。

要用安全的机器人摧毁掉不安全的。

对于一个不安全的机器人,把一个 AiA_i 球变为 Ai+1A_i+1,则可以摧毁这个机器人,相当于花费代价 1 自毁。

考虑最终情况是怎样的。一定是第 AiA_i 号机器人走到 1 处,沿途摧毁所有机器人,而右边的所有不安全的机器人自毁。

也可能所有不安全的机器人都自毁。

枚举 AiA_i,答案取最小。

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>
#define debug(x) cout << #x << ":\t" << (x) << endl;
using namespace std;
#define ll long long
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
int n;
int a[N], b[N], dp1;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)scanf("%d", &b[i]);
int L = INF, cnt = 0, dp1 = INF;
for (int i = n; i >= 1; i--) {
if (b[i] < a[i]) {
L = min(L, a[i] - b[i]);
if (L == 1)dp1 = min(dp1, cnt);
}
else {
if (L <= a[i]) {
dp1 = min(dp1, b[i] - a[i] + 1 + max(0, cnt - (b[i] - a[i] + 1)));
}
else {
dp1 = min(dp1, b[i] - a[i] + 1 + max(0, cnt - (b[i] - a[i] + 1)));
cnt++;
}
}
}
printf("%d\n", min(dp1, cnt));
return 0;
}

 

D - Convex Sequence

 

题意:给定 n,mn,m,要求构造长度为 nn,每个元素为非负数的数列,且对 2iN12 \leq i \leq N-1,有 2AiAi1+Ai+12 A_i \leq A_{i-1} + A_{i+1}。问有几种数列。

差分思想+完全背包

转化为 AiAi1Ai+1AiA_i-A_{i-1}\le A_{i+1}-A_i。也就是说,差分数组递增。

要构造递增的数组,可以通过不断地向后缀+1,或者前缀-1,或者整个数组+1.

后缀+1在原数组上反映为 ai,ai+1,ai+1,,ana_i,a_{i+1},a_{i+1},\cdots,a_n 分别加上 1,2,3,1,2,3,\cdots

前缀-1反映为 ai,ai1,ai2,,a1a_i,a_{i-1},a_{i-2},\cdots,a_1 分别加上 1,2,3,1,2,3,\cdots

这三种操作各自都可以进行任意次,且操作顺序无关。

所以可以把 “整个数组+1” , “长度为 1 的后缀+1”,“长度为 2 的后缀+1”,······,“长度为 1 的前缀-1”,“长度为 2 的前缀-1”,······。分别视为物品,每种物品可以拿任意个,背包大小为 mm,求所有物品总质量为 mm 的方案数。

完全背包计算方案数,就和普通的完全背包一样,不会有重复。

但是如果只是这样做,会使得前缀可能与后缀重叠,导致差分数组不递增,所以必须设定一个分割点,限制前缀后缀不能超过该点。且该点必定是整个数组的最小值。

再枚举分割点,每次计算完全背包,由于物品的质量都是 O(n2)O(n^2) 的,所以物品数为 O(m)O(\sqrt{m})。单次完全背包复杂度 O(mm)O(m\sqrt{m})

再转移分割点,由于分割点处不能被碰到,所以转移时去掉 “长度为 n-i 的后缀+1” 这种物品,再加上 “长度为 i 的前缀-1” 这种物品。

这时就可能产生重复,例如可能在分割点 i,j 时都不取物品或都只取 “长度为 1 的前缀-1”。所以必须限定至少要取 “长度为 i-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
#include <bits/stdc++.h>
#define debug(x) cout << #x << ":\t" << (x) << endl;
using namespace std;
#define ll long long
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
int n, m;
ll dp[N];
ll val(int x) { return 1ll*(1 + x)*x / 2; }
void ins(ll x) {
for (ll i = x; i <= m; i++) {
dp[i] = (dp[i] + dp[i - x]) % mod;
}
}
void del(ll x) {
for (ll i = m; i >= x; i--) {
dp[i] = (dp[i] - dp[i - x] + mod) % mod;
}
}
ll qry(ll x) {
if (x<0 || x>m)return 0;
return dp[x];
}
ll ans;
int main() {
scanf("%d%d", &n, &m);
dp[0] = 1;
ins(n);
for (int i = 1; i < n; i++) {
ins(val(i));
}
for (int i = 1; i <= n; i++) {
ans = (ans + qry(m - val(i - 1))) % mod;
ins(val(i));
del(val(n - i));
}
printf("%lld\n", ans);
return 0;
}