1.13训练赛
A.P7998 猜数
对于一个区间\([L, R]\)中的询问\([l, r]\)交互库必然会让你能确定的区间为
\([L, r - 1]\),\([l + 1, R]\)中的较长者。所以我们应当选取到端点距离相等的区间询问。
在此基础上dp,有
其中\(w(j, i) = \frac{1}{i - 2(i - 1 - j)} = \frac{1}{2j + 2 - i}\)
满足四边形不等式,所以这是一个决策单调性dp,可以用双端队列+二分\(nlogn\)实现。
如果你不会,可以尝试\(n^2\) dp后分段打表。
inline double f(int l, int r) {
ll w = l * 2 + 2 - r;
return w > 0 ? 1.0 / w + dp[l] : 1e9;
}
inline int bs(int x, int y) { // 返回x相对于y的边界
int l = x, r = x + 1 << 1;
while (l < r - 1) {
m = l + r >> 1;
f(x, m) < f(y, m) ? l = m : r = m;
}
// 在l及其之前从x转移更优,之后从y转移更优
return l;
}
signed main() {
ios::sync_with_stdio(0);
cin >> n, l = 1, r = n;
// q中存的是决策点,p中存的是决策点相对于下一个决策点的边界
fu(i, 2, n) {
// 队头的决策点范围已经遍历完
if (h < t && p[h] < i)
++h;
// 通过队头确定dp[i]
dp[i] = f(q[h], i), x[i] = q[h];
// 新加入的i可以覆盖掉已有决策点
while (h < t && p[t - 1] >= bs(q[t], i))
--t;
// 加入i
p[t] = bs(q[t], i), q[++t] = i;
}
while (l < r) {
m = r - l + 1;
cout << "? " << r - x[m] << ' ' << l + x[m] << '\n';
cin >> a >> b;
if (b ^ 1)
b ? r = a - 1 : l = a + 1;
else
l = r = a;
}
cout << "! " << l;
}
B.CF1437C Chef Monocarp
\(dp[i][j]\)代表前i个位置放了前j道菜,那么
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> t;
while (t--) {
cin >> n;
memset(dp, 60, sizeof dp), dp[0][0] = 0;
fu(i, 1, n) cin >> x[i], dp[i][0] = 0;
sort(x + 1, x + n + 1);
fu(i, 1, 300) fu(j, 1, min(i, n)) dp[i][j] =
min(dp[i - 1][j - 1] + abs(x[j] - i), dp[i - 1][j]);
k = dp[n][n];
fu(i, n + 1, 300) mn(k, dp[i][n]);
cout << k << '\n';
}
}
也可以用网络流\二分图匹配,第\(i\)个点连\(i \sim n + (i - 1 >> 1)\)
inline void add(ll u, ll v, ll C, ll W) {
ne[cnt] = he[u], to[cnt] = v, c[cnt] = C, w[cnt] = W, he[u] = cnt++;
ne[cnt] = he[v], to[cnt] = u, c[cnt] = 0, w[cnt] = -W, he[v] = cnt++;
}
inline bool dij(ll t) {
__gnu_pbds::priority_queue<pll, greater<pll>> q;
memset(d + 1, 14, t << 3), memset(vis + 1, 0, t << 3);
q.push({0, 0}), m = t + 1;
while (sz(q)) {
auto [W, u] = q.top();
q.pop();
if (d[u] < W)
continue;
--m, vis[u] = 1;
if (!m)
break;
for (ll i = he[u]; ~i; i = ne[i])
if (c[i] && !vis[to[i]]) {
ll v = to[i], tw = d[u] + w[i] + h[u] - h[v];
if (tw < d[v])
d[v] = tw, pv[v] = u, pe[v] = i, q.push({d[v], v});
}
}
return d[t] < inf;
}
inline void mcmf(ll t) {
F = C = 0;
while (dij(t)) {
fu(i, 1, t) if (h[i] < inf) h[i] += d[i];
ll f = inf;
for (ll u = t; u; u = pv[u])
mn(f, c[pe[u]]);
F += f, C += h[t] * f;
for (ll u = t; u; u = pv[u])
c[pe[u]] -= f, c[pe[u] ^ 1] += f;
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> t;
while (t--) {
cin >> n, cnt = 0;
memset(he, -1, sizeof he);
fu(i, 1, n) cin >> x[i];
sort(x + 1, x + n + 1);
ll t = 2 * n + (n - 1 >> 1) + 1;
fu(i, n + 1, t - 1) add(i, t, 1, 0);
fu(i, 1, n) {
add(0, i, 1, 0);
fu(j, i, n + (i - 1 >> 1)) add(i, n + j, 1, abs(j - x[i]));
}
mcmf(t);
cout << C << '\n';
}
}
C.CF1438C Engineer Artem
利用奇偶性构造,也可以2-sat。
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> t;
while (t--) {
cout << '\n';
cin >> n >> m;
int x[n + 1][m + 1], F = 0, f = 0;
fu(i, 1, n) {
F ^= 1, f = F;
fu(j, 1, m) {
cin >> x[i][j], x[i][j] += x[i][j] & 1 ^ (f ^= 1);
}
}
fu(i, 1, n) {
fu(j, 1, m) cout << x[i][j] << ' ';
cout << '\n';
}
}
}
D.CF1467B Hills And Valleys
简单的写法是枚举当前点修改对左右点的影响,也可以贪心(容易漏情况)。
inline bool ck(int l, int m, int r) {
if (l < 1 || r > n)
return 0;
return ll(x[m] - x[l]) * (x[m] - x[r]) > 0;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> t;
while (t--) {
cin >> n, ans = 0;
fu(i, 1, n) cin >> x[i];
fu(i, 1, n) y[i] = ck(i - 1, i, i + 1), ans += y[i];
m = min(ans, 1);
fu(i, 2, n - 1) if (y[i]) {
if (y[i - 1] && y[i + 1]) {
m = 3;
break;
}
if (y[i - 1] && (!ck(i - 3, i - 2, i) ||
!ck(i - 2, i - 1, i + 1) ||
!ck(i - 1, i + 1, i + 2)))
m = 2;
}
cout << ans - m << '\n';
}
}
E.CF1621E New School
题意转化一下就是给定长度为\(m\)的数组\(a,b\),每次在原\(a\)数组中最多改变一个值,
问对\(a,b\)排序后是否满足\(\forall i \in [1, m], a_i <= b_i\),注意到修改
后被修改数以外的数的位置最多\(±1\),预处理一下就可以了。
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> t;
while (t--) {
cin >> n >> m, r[m + 1] = 1;
fu(i, 1, n) cin >> x[i];
sort(x + 1, x + n + 1), copy(x + 1 + n - m, x + n + 1, x + 1);
x[m + 1] = 0;
vector<ll> v[m + 1];
fu(i, 1, m) {
cin >> y[i], z[i] = 0;
v[i].resize(y[i] + 1);
fu(j, 1, y[i]) cin >> v[i][j], z[i] += v[i][j];
fu(j, 1, y[i]) v[i][j] = (z[i] - v[i][j] + y[i] - 2) / (y[i] - 1);
w[i] = z[i] = (z[i] + y[i] - 1) / y[i];
}
sort(w + 1, w + m + 1);
fu(i, 1, m) {
l[i] = l[i - 1] & w[i] <= x[i];
L[i] = w[i] <= x[i - 1] ? L[i - 1] + 1 : 0;
}
fd(i, m, 1) {
r[i] = r[i + 1] & w[i] <= x[i];
R[i] = w[i] <= x[i + 1] ? R[i + 1] + 1 : 0;
}
fu(i, 1, m) {
ll P = lower_bound(w + 1, w + m + 1, z[i]) - w;
string a;
fu(j, 1, y[i]) {
ll p = lower_bound(w + 1, w + m + 1, v[i][j]) - w;
if (p > P)
cout << char('0' + (r[p] && v[i][j] <= x[p - 1]
&& p - 1 - L[p - 1] <= P && l[P - 1]));
else
cout << char('0' + (r[P + 1] && p + R[p] >= P
&& v[i][j] <= x[p] && l[p - 1]));
}
}
cout << '\n';
}
}
F.P7943 CONsecutive and CONcat
把贡献分为字符串内部的贡献以及与其它字符串拼接的贡献。对于某一字符
的拼接贡献,可以表示成后缀+相同字母串+前缀,即
inline ll A(ll n, ll m) {
return fac[n] * ifac[n - m] % M;
}
inline ll cal(ll c, ll s, ll l, ll p) {
ll r = suf[c][s] * pre[c][p] - same[c][s][p];
if (l < n && !s && !p)
--r; // 如果中间长度不够n,那么不能同时取左右端点
r = r % M * A(all[c], l) % M * fac[max(0ll, n - 1 - l)] % M;
return r * max(0ll, s + p + l * m - k + 1) % M;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m >> k, fac[0] = ifac[0] = 1;
fu(i, 1, n) fac[i] = fac[i - 1] * i % M;
fu(i, 2, n) iv[i] = (M - M / i) * iv[M % i] % M;
fu(i, 1, n) ifac[i] = ifac[i - 1] * iv[i] % M;
fu(c, 'a', 'z')++ suf[c][0], ++pre[c][0]; // 左右端点
fu(i, 1, n) {
cin >> s, f = s[0], b = s[m - 1];
// 前后边界
l = s.find_first_not_of(f);
r = s.find_last_not_of(b);
if (~l) {
t = 0, c = 1; // 字符串内部贡献
fu(i, l + 1, r) {
s[i] ^ s[i - 1] ? c = 1 : ++c;
t += c >= k;
}
ans += t * fac[n] % M, r = m - r - 1;
fu(c, 'a', 'z') { // 比如后缀字母是a,那么对于b-z就是suf[0]
++pre[c][c ^ f ? 0 : l], ++suf[c][c ^ b ? 0 : r];
++same[c][c ^ b ? 0 : r][c ^ f ? 0 : l];
}
} else { // 字符串由相同字母构成
++all[f];
fu(c, 'a', 'z') if (c ^ f) {
++pre[c][0], ++suf[c][0], ++same[c][0][0];
}
}
}
ans %= M;
fu(c, 'a', 'z') fu(i, 0, m - 1) fu(j, 0, all[c]) fu(k, 0, m - 1)
ans += cal(c, i, j, k);
cout << ans % M;
}
G.P1224 向量内积
注意到k = 2 or 3,先考虑k = 2,以下运算都在模k意义下进行,两个向量的点积只能为0或1,
对应是或不是k的倍数,每次让第i个向量点乘前面i - 1个向量的和,如果结果为i - 1,
说明有满足要求的有0,2,4...个,否则至少有一个,暴力把它找出来就行。考虑至少有一对满足
要求的向量时出错的概率,加入一个与这两个向量的点分别为0,1的向量,随机排列下出错的概率
为1/3,所以打乱序列做几次就行,k = 3时将点积平方就可以分成0,1。此时计算公式为\(\sum_i
\sum_j a_i a_j b_i b_j\)
inline int ck(const valarray<valarray<int>> &v, int p) {
fu(i, 0, p - 1) {
a = (v[x[i]] * v[x[p]]).sum();
k ^ 2 ? a = a * a % 3 : 0;
if (~a & 1)
return cout << min(++x[p], ++x[i]) << ' ' << max(x[p], x[i]), 0;
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m >> k;
valarray<valarray<int>> v(valarray<int>(m), n);
fu(i, 0, n - 1) fu(j, 0, m - 1) cin >> v[i][j], v[i][j] %= k;
iota(x, x + n, 0);
fu(_, 1, 3) {
shuffle(x, x + n, default_random_engine());
if (k ^ 3) {
auto t = v[x[0]];
fu(i, 1, n - 1) {
if ((v[x[i]] * t).sum() & 1 ^ i & 1)
return ck(v, i);
t += v[x[i]];
}
} else {
valarray<valarray<int>> t(m);
fu(i, 0, m - 1) t[i] = v[x[0]][i] * v[x[0]];
fu(i, 1, n - 1) {
a = 0;
fu(j, 0, m - 1) a += (v[x[i]] * t[j]).sum() * v[x[i]][j];
if (a % 3 ^ i % 3)
return ck(v, i);
fu(j, 0, m - 1) t[j] += v[x[i]][j] * v[x[i]];
}
}
}
cout << "-1 -1";
}
H.P1231 教辅的组成
比较裸的网络流。
inline bool bfs() {
memset(d + 1, 0, t << 2);
queue<int> q({0});
while (sz(q)) {
int u = q.front();
q.pop();
for (auto [v, c] : g[u])
if (!d[v] && c)
d[v] = d[u] + 1, q.emplace(v);
}
return d[t];
}
int dfs(int u = 0, int c = 1e9) {
if (u == t)
return c;
int F = 0, f;
for (auto &i = cur[u]; c && i != g[u].end(); ++i) {
auto &[v, w] = *i;
if (d[v] == d[u] + 1 && w) {
f = dfs(v, min(c, w));
w -= f, c -= f, F += f, g[v][u] += f;
}
}
return F;
}
inline void dinic() {
while (bfs()) {
fu(i, 0, t) cur[i] = g[i].begin();
F += dfs();
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n1 >> n2 >> n3, n = n1 + n2, n0 = n + n2;
cin >> m, t = n0 + n3 + 1;
fu(i, 1, n1) g[0][i] = 1, g[i][0] = 0;
fu(i, 1, n2) g[i + n1][i + n] = 1, g[i + n][i + n1] = 0;
fu(i, 1, n3) g[i + n0][t] = 1, g[t][i + n0] = 0;
fu(i, 1, m) cin >> x >> y, g[y][x + n1] = 1, g[x + n1][y] = 0;
cin >> m;
fu(i, 1, m) cin >> x >> y, g[x + n][y + n0] = 1, g[y + n0][x + n] = 0;
dinic(), cout << F;
}