2021牛客OI赛前集训营-普及组题解(第四场)
Problem A. 多国语言
做法
设 ,
这两个数组可以扫描一遍 数组得到。
记 ,
如果 ,输出 ">-<";
否则如果 ,输出 "^v^";
否则只存在唯一的 ,满足
,如果此时
,则输出
,否则输出 "^v^"。
时间复杂度 。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100100;
int cnt[maxn], cntb[maxn], a[maxn];
int main(void) {
// freopen("sample.in", "r", stdin);
int T; scanf("%d", &T);
assert(1 <= T && T <= 10);
while (T--) {
int n, m; scanf("%d%d", &n, &m);
assert(1 <= n && n <= 100000);
assert(1 <= m && m <= 100000);
for (int i=1; i<=m; i++) cnt[i] = cntb[i] = 0;
for (int i=1; i<=n; i++) {
scanf("%d", &a[i]);
assert(1 <= a[i] && a[i] <= m);
cnt[a[i]]++;
}
for (int i=1; i<=n; i++) {
int b; scanf("%d", &b);
assert(b == 0 || b == 1);
if (b == 1) cntb[a[i]]++;
}
int num = 0, idx = 0;
for (int i=1; i<=m; i++) if (cntb[i]) {
num++;
if (cntb[i] == cnt[i]) idx = i;
}
if (num == 1 && idx) {
printf("%d\n", idx);
} else if (num) {
printf("^v^\n");
} else {
printf(">-<\n");
}
}
return 0;
}
Problem B. double u
做法
将 中所有 'w' 替换为 "uu",所有 'm' 替换为 "nn",假设替换后长度为
,若
,则贪心的选择最靠前的一个 "uu"/"nn" 子串替换为 'w'/'m',这样可以使得
减少1,一直替换直到
。由于题意保证存在答案,则这样贪心一定能得到答案。
时间复杂度 。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200200;
char s[maxn], t[maxn], u[maxn];
int main(void) {
// freopen("sample.in", "r", stdin);
int T; scanf("%d", &T);
assert(1 <= T && T <= 10);
while (T--) {
int n; scanf("%d", &n);
assert(1 <= n && n <= 100000);
scanf("%s", t);
int m = strlen(t);
assert(1 <= m && m <= 100000);
int l = 0;
for (int i=0; i<m; i++) {
if (t[i] == 'w') u[l++] = 'u', u[l++] = 'u';
else if (t[i] == 'm') u[l++] = 'n', u[l++] = 'n';
else u[l++] = t[i];
}
assert(l >= n);
int d = l - n;
for (int i=0, j=0; i<n; i++) {
if (d) {
if (u[j] == 'u' && u[j+1] == 'u') {
s[i] = 'w';
j += 2;
d--;
} else if (u[j] == 'n' && u[j+1] == 'n') {
s[i] = 'm';
j += 2;
d--;
} else s[i] = u[j++];
} else s[i] = u[j++];
}
assert(d == 0);
s[n] = 0;
printf("%s\n", s);
}
return 0;
}
Problem C. 活动
做法
可以观察到,放一个物品必然使得篮子内重量加倍,因此最多只能放 件物品。考虑动态规划,
篮子内放
件物品,当前重量为
的方案数,则
不妨假设 同阶,那么直接求这个dp的复杂度为
。
考虑到这个转移最多是一个连续的区间 的和减去一个数,维护前缀和即可将转移复杂度优化到
。
时间复杂度 。
代码
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int maxn = 100100;
int f[21][maxn], sum[21][maxn];
int main(void) {
// freopen("sample.in", "r", stdin);
int n, x, y; scanf("%d%d%d", &n, &x, &y);
assert(1 <= n && n <= 100000);
assert(1 <= x && x <= 100000);
assert(1 <= y && y <= n);
f[0][0] = 1;
for (int i=0; i<=x; i++) sum[0][i] = 1;
for (int i=1; i<=20; i++) {
for (int j=1; j<=x; j++) {
f[i][j] = sum[i-1][j/2];
if (j > n) f[i][j] = (f[i][j] - sum[i-1][j-n-1] + mod) % mod;
if (j-j/2 <= y && y <= j) f[i][j] = (f[i][j] - f[i-1][j-y] + mod) % mod;
sum[i][j] = (sum[i][j-1] + f[i][j]) % mod;
}
}
int ans = 0;
for (int i=0; i<=20; i++) ans = (ans + f[i][x]) % mod;
printf("%d\n", ans);
return 0;
}
Problem D. 迷宫
做法
将网络中每个格子到它的下一个格子连一条有向边,得到一个具有特殊性质的图。考虑在图上进行动态规划,具体来说,令 左/右脚在结点
时得到的最大答案,则
。(
是
指向的下一个结点。)
但这个图是可能存在环的,这意味着动态规划具有后效性。可以发现环上的结点数必然是偶数个。这是因为一个环内指向上的结点必然要和指向下的结点一样多,指向左的结点要和指向右的结点一样多。那么在环上,如果一开始以左脚踏入结点 ,则永远以左脚踏入结点
,右脚同理。由于每次走完后,结点的权值会变成自己的相反数,因此走两圈相当于没走。
由此可以得到一个暴力,即从每个点开始一直走,直到某个点需要访问三次则停下。假设 同阶,时间复杂度:
。
上述做法复杂度的瓶颈在于求环上每个点的答案。考虑到从环上的结点 出发,环的长度为
,走第一圈时相当于走了一个从
出发的长度
的区间,走第二圈时相当于走了一个到
结束的长度
的区间(
是
在环上的上一个结点)。这两部分的求解是完全相同的,可以把区间和拆成两个前缀和的差,然后求前缀和的一个环上的滑窗max,可以用单调队列做到
。因此最终时间复杂度为
。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const ll B = (ll)1e15;
const ll C = (ll)2e15 + 21;
const int maxn = 1010;
char d[maxn];
int val[maxn * maxn], nex[maxn * maxn];
int vis[maxn * maxn], st[maxn * maxn], top, in_st[maxn * maxn], is_r[maxn * maxn];
vector <int> e[maxn * maxn];
ll f[maxn * maxn][2], g[maxn * maxn];
void Max(ll & x, ll y) { if (y > x) x = y; }
void ring_max(vector <int> & w) {
static ll sum[maxn*maxn*2];
static int que[maxn*maxn*2];
int n = w.size();
sum[0] = 0;
for (int i=1; i<2*n; i++) sum[i] = sum[i-1] + w[(i-1<n)?(i-1):(i-1-n)];
// g[l] = max(sum[r] - sum[l]), l+1 <= r <= l+n
int head = 0, tail = 0;
for (int i=0; i<2*n; i++) {
while (tail < head && sum[que[head-1]] < sum[i]) head--;
que[head++] = i;
if (i-que[tail] >= n) tail++;
if (i >= n) g[i-n] = sum[que[tail]] - sum[i-n];
}
}
void fnd_ring(int u) {
if (in_st[u]) {
vector <int> a;
for (int i=in_st[u]; i<=top; i++) {
is_r[st[i]] = 1;
a.push_back(st[i]);
}
// calc f[a[i]][0 / 1]
int m = a.size();
vector <int> w(m);
for (int u=0; u<2; u++) {
for (int i=0; i<m; i++) {
if (i % 2 == u) w[i] = val[a[i]];
else w[i] = -val[a[i]];
}
ring_max(w);
for (int i=0; i<m; i++) {
if (i % 2 == 0) Max(f[a[i]][u], g[i]);
else Max(f[a[i]][!u], g[i]);
}
reverse(w.begin(), w.end());
ring_max(w);
for (int i=0; i<m; i++) {
if (i % 2 == 0) Max(f[a[(m-i)%m]][u], g[i]);
else Max(f[a[(m-i)%m]][!u], g[i]);
}
}
for (int i=0; i<m; i++) Max(f[a[i]][0], 0);
for (int i=0; i<m; i++) Max(f[a[i]][1], 0);
return ;
}
if (vis[u] || nex[u] == -1) return ;
vis[u] = 1;
st[++top] = u;
in_st[u] = top;
fnd_ring(nex[u]);
top--;
in_st[u] = 0;
}
void dp(int u) {
for (auto v : e[u]) {
if (is_r[v]) continue;
f[v][0] = val[v] + max(0ll, f[u][1]);
f[v][1] = -val[v] + max(0ll, f[u][0]);
dp(v);
}
}
int main(void) {
// freopen("sample.in", "r", stdin);
int n, m; scanf("%d%d", &n, &m);
assert(1 <= n && n <= 1000);
assert(1 <= m && m <= 1000);
for (int i=0; i<n; i++) {
scanf("%s", d);
assert(strlen(d) == m);
for (int j=0; j<m; j++) {
assert(d[j] == '^' || d[j] == 'v' || d[j] == '<' || d[j] == '>');
int ni, nj;
if (d[j] == '^') ni = i-1, nj = j;
else if (d[j] == 'v') ni = i+1, nj = j;
else if (d[j] == '<') ni = i, nj = j-1;
else ni = i, nj = j+1;
if (0 <= ni && ni < n && 0 <= nj && nj < m) {
nex[i*m+j] = ni*m+nj;
e[ni*m+nj].push_back(i*m+j);
} else nex[i*m+j] = -1;
}
}
for (int i=0; i<n*m; i++) {
scanf("%d", &val[i]);
assert(-(int)1e9 <= val[i] && val[i] <= (int)1e9);
}
for (int i=0; i<n*m; i++) f[i][0] = f[i][1] = -B;
for (int i=0; i<n*m; i++) if (!vis[i]) fnd_ring(i);
for (int i=0; i<n*m; i++) {
if (is_r[i]) dp(i);
if (nex[i] == -1) {
f[i][0] = val[i];
f[i][1] = -val[i];
dp(i);
}
}
ull ans = 0, c = 1;
for (int i=0; i<n*m; i++) {
ans += (ull)(f[i][0] + B) * c;
c *= C;
}
printf("%llu\n", ans);
return 0;
}
腾讯成长空间 5958人发布

