# A. Garland
简单分讨。
考虑一段 0 的两端奇偶性不同,那么不管怎么放代价只会加 1.
所以只考虑连段奇偶性相同的。
如果数字数量足够填满当前段,那么没有代价,否则代价是 2(自己手模一下)。
所以把两端奇偶性相同的空段按长度从小到大排序,能填满就填满即可。
还有一些奇奇怪怪的小细节,自己写代码的时候慢慢调吧。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
const int N = 110; | |
int n, ans; | |
int a[N], vis[N]; | |
int pos, pre[N]; | |
struct node{ | |
int len, typ, t; | |
inline bool operator < (const node &b) const{ | |
return t != b.t ? t > b.t : len > b.len; | |
} | |
}; | |
priority_queue <node> q; | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
ios :: sync_with_stdio(false); | |
cin >> n; | |
for(int i = 1; i <= n; ++i) cin >> a[i], vis[a[i]] = 1; | |
if(n == 1) return cout << "0\n", 0; | |
for(int i = 1; i <= n; ++i) | |
if(a[i]) pre[i] = pos, pos = i; | |
a[n + 1] = a[pos] + 2, pre[n + 1] = pos; | |
int c0 = 0, c1 = 0; | |
for(int i = 1; i <= n; ++i) | |
if(!vis[i]){ | |
if(i & 1) c1++; | |
else c0++; | |
} | |
for(int i = 2; i <= n + 1; ++i) | |
if(a[i] && a[i - 1] && (a[i] & 1) != (a[i - 1] & 1)) ans++; | |
for(int i = 1; i <= n + 1; ++i){ | |
int t = (pre[i] > 0) ? (a[pre[i]] & 1) != (a[i] & 1) : 0; | |
if(a[i] && pre[i] < i - 1){ | |
if(!t) q.push((node){i - pre[i] - 1, a[i] & 1, (pre[i] == 0 || i > n)}); | |
else ans++; | |
} | |
} | |
while(!q.empty()){ | |
int len = q.top().len, typ = q.top().typ, t = q.top().t; q.pop(); | |
if(!typ){ | |
if(c0 >= len) c0 -= len; | |
else ans += 2 - t; | |
}else{ | |
if(c1 >= len) c1 -= len; | |
else ans += 2 - t; | |
} | |
} | |
cout << ans << '\n'; | |
return 0; | |
} |
# B. Numbers on Tree
这道题似乎没有那么复杂。
大致思路是无脑 一遍求出每个点之间的相对大小关系,然后依次赋值即可。
首先如果出现 大于等于 的子树大小,就无解。
然后 dfs 回溯过程暴力合并序列,最后把 移动到 的位置即可。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 2010; | |
int n, rt; | |
int fa[N], c[N], a[N]; | |
vector <int> G[N], seq[N]; | |
int siz[N]; | |
inline void dfs(int x){ | |
siz[x] = 1; | |
for(auto y : G[x]){ | |
dfs(y), siz[x] += siz[y]; | |
for(auto v : seq[y]) seq[x].pb(v); | |
} | |
if(siz[x] <= c[x]) puts("NO"), exit(0); | |
seq[x].pb(x); | |
for(int i = siz[x] - 1; i > c[x]; --i) swap(seq[x][i], seq[x][i - 1]); | |
} | |
int ans[N]; | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
n = read(); | |
for(int i = 1; i <= n; ++i){ | |
G[fa[i] = read()].pb(i), c[i] = read(); | |
if(!fa[i]) rt = i; | |
} | |
dfs(rt); | |
for(int i = 0; i < n; ++i) ans[seq[rt][i]] = i + 1; | |
puts("YES"); | |
for(int i = 1; i <= n; ++i) write(ans[i]), putchar(' '); | |
return 0; | |
} |
# C1. Madhouse (Easy version)
较简单,你只有 3 次询问机会,不管怎么试都能试出来吧……
那么正确的询问方法是先问 再问 。
然后你的第一次询问比第二次询问子串数多 个。
开个 记一下依次推出每一个字符即可。
总询问字符串数为 。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
const int N = 110; | |
int n; | |
string s, str[N], ans; | |
multiset <string> s1[N], s2; | |
map <string, int> mp; | |
signed main(){ | |
cin >> n; | |
if(n == 1){ | |
cout << "? 1 1\n"; | |
cin >> s[0]; | |
cout << "! " << s[0] << endl; | |
fflush(stdout); | |
return 0; | |
} | |
cout << "? 1 " << n << '\n'; | |
for(int i = 1; i <= n * (n + 1) / 2; ++i){ | |
cin >> s; sort(s.begin(), s.end()); | |
s1[s.length()].insert(s); | |
} | |
cout << "? 2 " << n << '\n'; | |
for(int i = 1; i <= (n - 1) * n / 2; ++i){ | |
cin >> s; sort(s.begin(), s.end()); | |
s2.insert(s), mp[s]++; | |
} | |
for(int i = 1; i <= n; ++i){ | |
for(auto s : s1[i]){ | |
if(!mp[s]){ | |
for(int j = 0; j <= i; ++j) | |
if(str[i - 1][j] != s[j]) {ans += s[j]; break;} | |
str[i] = s; break; | |
}else mp[s]--; | |
} | |
} | |
cout << "! " << ans << '\n'; | |
fflush(stdout); | |
return 0; | |
} |
# C2. Madhouse (Hard version)
现在你总询问子串数不能超过 了。怎么办呢?
假设你的字符串为 ,现在你询问 。
然后你考虑 中的每一位 在不同长度子串中出现的次数。
经过一番观察不难发现,在所有不同长度的子串中出现次数全部相同的字符个数最多两个。(出现一次的是奇数长度时最中间的字符)
比如与 在所有长度子串出现次数完全相同的是 (总之就是轴对称)。
所以我们先询问 和 得到 的答案。
然后询问 ,再根据上面说的对称性求出 的答案。
总子串个数 。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
const int N = 150; | |
int n, a[N], b[N], c[N]; | |
string s; | |
char ans[N]; | |
inline void qry(int l, int r, int b[N]){ | |
cout << "? " << l << " " << r << '\n'; | |
for(int i = l; i <= r; ++i) | |
for(int j = i; j <= r; ++j){ | |
cin >> s; | |
for(int k = 0; k < (int)s.length(); ++k) b[s.length()] += s[k]; | |
} | |
} | |
signed main(){ | |
ios :: sync_with_stdio(false); | |
cin >> n; | |
if(n <= 3){ | |
for(int i = 1; i <= n; ++i) | |
qry(i, i, a), ans[i] = a[1], a[1] = 0; | |
cout << "! " << (ans + 1) << '\n'; | |
return 0; | |
} | |
qry(1, (n + 1) / 2, a); | |
qry(2, (n + 1) / 2, b); | |
qry(1, n, c); | |
for(int i = 1; i <= (n + 1) / 2; ++i) | |
ans[i] = (a[i] -= b[i]) - a[i - 1]; | |
for(int i = n / 2, j = ans[n / 2 + 1]; i >= 1; --i){ | |
ans[n + 1 - i] = c[i] - c[i - 1] - j - ans[i]; | |
j = c[i] - c[i - 1]; | |
} | |
cout << "! " << (ans + 1) << '\n'; | |
return 0; | |
} |
# D. LCC
首先一个比较显然的结论是肯定是相邻两个点相撞,不解释了。
一共有三种碰撞情况:都向左,相向,都向右。
不难想到去枚举哪两个点相撞,假设是 和 相撞。
设 表示 向 方向走, 向 方向走是被禁止的。
记 表示考虑了前 个点,当前点是向左 / 右走,且与 的碰撞是最早的概率。
是往左走的概率, 反之。
转移方程:
每次修改 后都要把后缀再做一遍,复杂度是 的,过不了。
如何优化呢?考虑线段树。
线段树上存 表示 对应区间 的 向 走, 向 走且当前枚举的 和 最先相撞的概率。
更新的话从当前点暴力往上跳暴力修改。
答案就是根节点 4 个值加起来。
和 碰撞的概率是前 个不碰撞减去前 个不碰撞。
具体见代码吧。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define ls rt << 1 | |
#define rs rt << 1 | 1 | |
#define mid ((l + r) >> 1) | |
using namespace std; | |
const int N = 1e5 + 10; | |
const int mod = 998244353; | |
int n; | |
int a[N], b[N], p[N], q[N]; | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline int sub(int x) {return x < 0 ? x + mod : x;} | |
inline int mul(int x, int y) {return 1ll * x * y % mod;} | |
inline int qpow(int a, int b){ | |
int res = 1; | |
while(b){ | |
if(b & 1) res = mul(res, a); | |
a = mul(a, a), b >>= 1; | |
} | |
return res; | |
} | |
inline int Inv(int x) {return qpow(x, mod - 2);} | |
struct node{ | |
int x, d1, d2, l, v; | |
inline bool operator < (const node &b) const{ | |
return 1ll * l * b.v < 1ll * b.l * v; | |
} | |
}c[N << 2]; | |
int tot; | |
int val[N << 2][2][2], Mid[N << 2], id[N << 2]; //val [rt][j][k] rt 对应区间 [l, r] 的 l 向 j,r 向 k 走的概率 | |
bool lim[N][2][2]; //lim [i][j][k]: i 向 j 且 i + 1 向 k 走是被禁止的 | |
inline void pushup(int rt){ | |
for(int i = 0; i < 2; ++i) | |
for(int j = 0; j < 2; ++j){ | |
val[rt][i][j] = 0; | |
for(int x = 0; x < 2; ++x) | |
for(int y = 0; y < 2; ++y) | |
if(!lim[Mid[rt]][x][y]) | |
val[rt][i][j] = add(val[rt][i][j] + mul(val[ls][i][x], val[rs][y][j])); | |
} | |
} | |
inline void build(int l, int r, int rt){ | |
if(l == r) return val[rt][0][0] = p[l], val[rt][1][1] = q[l], void(); | |
build(l, mid, ls), build(mid + 1, r, rs); | |
Mid[rt] = mid, id[mid] = rt; | |
pushup(rt); | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
ios :: sync_with_stdio(false); | |
cin >> n; | |
int inv = Inv(100); | |
for(int i = 1; i <= n; ++i){ | |
cin >> a[i] >> b[i] >> q[i]; | |
q[i] = mul(q[i], inv), p[i] = sub(1 - q[i]); | |
} | |
build(1, n, 1); | |
for(int i = 2; i <= n; ++i){ | |
c[++tot] = (node){i - 1, 1, 0, a[i] - a[i - 1], b[i] + b[i - 1]}; | |
if(b[i - 1] > b[i]) c[++tot] = (node){i - 1, 1, 1, a[i] - a[i - 1], b[i - 1] - b[i]}; | |
if(b[i - 1] < b[i]) c[++tot] = (node){i - 1, 0, 0, a[i] - a[i - 1], b[i] - b[i - 1]}; | |
} | |
sort(c + 1, c + 1 + tot); | |
int ans = 0, lst = 1; | |
for(int i = 1; i <= tot; ++i){ | |
lim[c[i].x][c[i].d1][c[i].d2] = 1; | |
for(int x = id[c[i].x]; x; x >>= 1) pushup(x); | |
int res = (1ll * val[1][0][0] + val[1][0][1] + val[1][1][0] + val[1][1][1]) % mod; | |
ans = add(ans + mul(mul(c[i].l, Inv(c[i].v)), sub(lst - res))); | |
lst = res; | |
} | |
cout << ans << '\n'; | |
return 0; | |
} |
# E. Fedya the Potter Strikes Back
首先这个条件就是 ,我们只需要维护 集合来计算答案增量即可。
考虑每次新加进来一个字符 对 的影响。
新加一个 ,即 .
原本的 依旧是 ,但是权值要取 .
删除原本的 .
假设我们能找到所有依旧是 的位置,和被删除的 的位置。
我们只需要开一个线段树来找区间最小值,然后再开一个 来存每个权值出现过多少次用于更新答案。
现在的问题是找到被删除的 的位置。
我们只需要在 的失配树上跑即可,具体可以先枚举 的 是什么,然后暴力跳。
具体看代码。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define ls rt << 1 | |
#define rs rt << 1 | 1 | |
#define mid ((l + r) >> 1) | |
#define int long long | |
#define fi first | |
#define se second | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 6e5 + 10; | |
const int inf = 1e18; | |
const int mod = 1e18; | |
typedef pair<int, int> P; | |
int n, sum, s[N], w[N]; | |
__int128 ans; | |
char c[4]; | |
map <int, int> mp; | |
int nxt[N], col[N], fa[N][27]; | |
int mn[N << 2]; | |
inline void upd(int x, int v, int l = 1, int r = n, int rt = 1){ | |
if(l == r) return mn[rt] = v, void();; | |
if(x <= mid) upd(x, v, l, mid, ls); | |
else upd(x, v, mid + 1, r, rs); | |
mn[rt] = min(mn[ls], mn[rs]); | |
} | |
inline int qry(int L, int R, int l = 1, int r = n, int rt = 1){ | |
if(L > r || R < l) return inf; | |
if(L <= l && r <= R) return mn[rt]; | |
return min(qry(L, R, l, mid, ls), qry(L, R, mid + 1, r, rs)); | |
} | |
inline void add(int x, int v){ | |
mp[x] += v, sum += x * v; | |
} | |
int stk[N]; | |
inline int calc(int w){ | |
int cnt = 0, top = 0; | |
for(auto it = mp.upper_bound(w); it != mp.end(); ++it) | |
stk[++top] = it->first, cnt += it->second, sum -= it->first * it->second; | |
while(top) mp.erase(stk[top--]); | |
return cnt; | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
n = read(), scanf("%s", c), w[1] = read(); | |
s[1] = c[0] - 'a', ans = ans + w[1], upd(1, w[1]); | |
write(ans), puts(""); | |
for(int i = 2; i <= n; ++i){ | |
scanf("%s", c), w[i] = read(); | |
s[i] = (c[0] - 'a' + ans % 26) % 26, w[i] ^= (ans % (1 << 30)); | |
if(s[1] == s[i]) add(w[i], 1); // 新加 border | |
upd(i, w[i]), ans = ans + qry(1, i); // 往线段树里插入 w [i] | |
int pos = nxt[i - 1]; // 更新 nxt | |
while(pos && s[pos + 1] != s[i]) pos = nxt[pos]; | |
nxt[i] = (s[pos + 1] == s[i]) ? pos + 1 : pos, col[i - 1] = s[i]; | |
for(int j = 0; j < 26; ++j) fa[i][j] = fa[nxt[i]][j]; // 更新失配树 | |
fa[i][col[nxt[i]]] = nxt[i]; | |
for(int c = 0; c < 26; ++c){ // 枚举被删除的 border 后一位是什么 | |
if(c == s[i]) continue; | |
for(int j = fa[i - 1][c]; j; j = fa[j][c]) add(qry(i - j, i - 1), -1); // 修改 | |
} | |
add(w[i], calc(w[i])); | |
write(ans = ans + sum), puts(""); | |
} | |
return 0; | |
} |
# F. Harry The Potter
感觉这个比上面两道题稍微简单一点。
我们假设对 进行二操作,那么在 之间连一条边。
不难证明,最后形成的一定是森林,设连通块个数为 ,答案就是 ,问题变成了如何最大化 。
我们再把这个问题分成两部分。
判断一个点集 是可以由二操作连成一棵树的。
跑一个枚举子集 的东西来求值。
第二个部分是简单的, 即可。
下面只讨论第一个部分。
观察到二操作是对 减 ,对 减 。这个 有点恶心,我们先不考虑它。即对 都减去 。
那么一个点集能否由二操作连起来的条件即为:把这个点集 分成两部分 ,使得这两部分的和 。
现在把 的条件考虑进去。
条件就变为了:.( 是点集大小)
所以折半搜一下所有的和,然后双指针跑一跑就行。
还有一个小问题。
设有两个数分别为 ,它们分成两部分后差的绝对值最小为 .
1 是小于 2 的,符合条件。
但是我们真的可以一步把它们两个都变成 0 吗?显然不行。
所以遇到这种所有数都在同一部分还符合条件的情况,要特判掉。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define int long long | |
using namespace std; | |
const int N = (1 << 20) + 10; | |
int n, m; | |
int a[25], lg[N], sum[N]; | |
int f[N]; | |
int b[25], v1[N], v2[N]; | |
int s1[N], s2[N]; | |
inline void dfs(int l, int r, int *v){ | |
v[1] = 0; | |
if(l > r) return; | |
for(int i = l, m = 1; i <= r; ++i, m <<= 1){ | |
for(int j = 1; j <= m; ++j) s1[j] = v[j] + b[i], s2[j] = v[j] - b[i]; | |
for(int p = 1, q = 1, k = 1; k <= (m << 1); ++k){ | |
if(p > m) v[k] = s2[q++]; | |
else if(q > m) v[k] = s1[p++]; | |
else if(s1[p] < s2[q]) v[k] = s1[p++]; | |
else v[k] = s2[q++]; | |
} | |
} | |
} | |
inline bool chk(int s){ | |
int cnt = 0, sum = 0; | |
for(int i = 1; i <= n; ++i) | |
if((s >> (i - 1)) & 1) b[++cnt] = a[i], sum += a[i]; | |
if((sum - cnt + 1) & 1) return 0; | |
dfs(1, cnt / 2, v1), dfs(cnt / 2 + 1, cnt, v2); | |
int L = 1 << (cnt / 2), R = 1 << (cnt - cnt / 2), tmp = 1 + (abs(sum) < cnt) * 2; | |
for(int i = R, j = 1; i >= 1; --i){ | |
while(j <= L && v1[j] + v2[i] <= -cnt) j++; | |
for(int k = j; k <= L && tmp && v1[k] + v2[i] < cnt; ++k) tmp--; | |
} | |
return !tmp; | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
ios :: sync_with_stdio(false); | |
cin >> n; | |
for(int i = 1; i <= n; ++i){ | |
cin >> a[i]; | |
if(a[i]) a[++m] = a[i]; | |
} | |
n = m; int all = (1 << n) - 1; | |
for(int s = 1; s <= all; ++s) if(!f[s] && chk(s)){ | |
int res = all ^ s; f[s] = 1; | |
for(int t = res; t; t = (t - 1) & res) f[s | t] = max(f[s | t], f[t] + 1); | |
} | |
cout << n - f[all] << '\n'; | |
return 0; | |
} |