1011模拟赛
为什么10.11的比赛现在才补呢,主要是由于之前效率不高+摸鱼严重,导致之前膜你赛的补题差一堆。。。
希望以后能适当摸鱼(
T1:ARC124B XOR Matching 2
题意就是给了你两个长度为 \(n\) 的序列 \(a,b\),然后找到所有满足以下条件的 \(x\):
存在一个 \(1\sim n\) 的排列 \(p\) 使得 \(a_{p_i}\oplus b_{p_i}=x\)。
这题图方便可以用 map
,但 \(O(n^2\log n)\) 的复杂度,以 map
的常数不太彳亍,容易 TLE,所以我用了离散化。
#include <cstdio>
#include <algorithm>
#include <cstring>
int a[2005], b[2005], c[4000005], d[2005], s[2005], used[2005], len, cnt, n;
int ans[2005], tot;
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);
if (n == 1) return printf("%d %d\n", 1, a[1] ^ b[1]), 0;
std::sort(b + 1, b + n + 1);
b[0] = b[n + 1] = -1;
for (int i = 1, j = 0, st = 0; i <= n + 1; ++ i)
if (b[i] != b[i - 1]) s[st] = j, j = 1, st = i;
else ++ j;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= n; ++ j)
c[++ len] = a[i] ^ b[j];
std::sort(c + 1, c + len + 1);
c[0] = c[len + 1] = -1;
for (int i = 1, j = 0; i <= len + 1; ++ i)
if (c[i - 1] != c[i]) {
if (j >= n) d[++ cnt] = c[i - 1];
j = 1;
} else ++ j;
for (int i = 1; i <= cnt; ++ i) {
memset(used, 0, sizeof used);
bool flag = true;
for (int j = 1; j <= n; ++ j) {
int k = std::lower_bound(b + 1, b + n + 1, a[j] ^ d[i]) - b;
if (a[j] ^ d[i] ^ b[k]) {flag = false; break;}
++ used[k];
}
if (flag) for (int j = 1; j <= n; ++ j)
if (used[j] > s[j]) {flag = false; break;}
if (flag) ans[++ tot] = d[i];
}
printf("%d\n", tot);
for (int i = 1; i <= tot; ++ i) printf("%d\n", ans[i]);
return 0;
}
T2:NKOJ8687 S
题面:
有 \(n\) 个球,每个球有 RGY 三种颜色,现在小 F 觉得如果有两个相邻的球颜色相同很丑。 他每次可以交换相邻两个球,问至少交换多少次才能不丑。
\(1\le n\le 400\)。
数据范围确定了是一个 \(O(n^3)\) 左右的 dp。
直接 dp 会出各种问题,考虑如果确定最终状态我们需要交换多少次。这个可以用逆序对算。
具体而言,答案是这个序列的逆序对:
最终状态里的第 \(i\) 个 \(x\) 字符,所代表的数是原序列第 \(i\) 个 \(x\) 字符的位置。
于是考虑对这个最终状态进行 dp。设计状态方面,我们需要知道当前三种字符分别填到了哪里,最后一个填的是哪个字符。
设 \(dp[i][j][k][ed=0/1/2]\) 表示最后一个 R 在原序列第 \(i\) 个位置,最后一个 G 在原序列第 \(j\) 个位置,最后一个 Y 在原序列第 \(k\) 个位置。\(ed=0,1,2\) 分别表示最终状态中最后一个字符是 R/G/Y。
转移可以二分求逆序对,也可以预处理后 \(O(1)\) 求。二分求是 \(O(n^3\log n)\) 的,预处理后是 \(O(n^3)\) 的,都能过。
以下为二分代码。
#include <cstdio>
#include <cstring>
#include <algorithm>
inline int abs(int k) {return k >= 0 ? k : -k;}
inline int min(const int x, const int y) {return x < y ? x : y;}
int dp[405][405][405][3], nxt[405], frt[3], lst[405], s[405], n;
int R[405], G[405], Y[405], pos[405], cntR, cntG, cntY;
char str[405];
int main() {
memset(dp, 0x3f, sizeof dp);
scanf("%d%s", &n, str + 1);
for (int i = 1; i <= n; ++ i)
if (str[i] == 'R') str[i] = 0, R[++ cntR] = i, pos[i] = cntR;
else if (str[i] == 'G') str[i] = 1, G[++ cntG] = i, pos[i] = cntG;
else str[i] = 2, Y[++ cntY] = i, pos[i] = cntY;
for (int i = 1; i <= n; ++ i) {
nxt[lst[str[i]]] = i, s[i] = s[lst[str[i]]] + 1, lst[str[i]] = i;
if (!frt[str[i]]) frt[str[i]] = i;
}
dp[0][0][0][0] = 0;
for (int i = 0; i <= n; ++ i) if (!i || str[i] == 0)
for (int j = 0; j <= n; ++ j) if (!j || str[j] == 1)
for (int k = 0; k <= n; ++ k) if (!k || str[k] == 2)
for (int ed = 0; ed <= 2; ++ ed) {
if (nxt[i] && ((!i && !j && !k) || ed != 0)) {
int Next = i ? nxt[i] : frt[0];
int tmp = dp[i][j][k][ed];
if (j) tmp += pos[j] - (std::lower_bound(G + 1, G + pos[j] + 1, Next) - G) + 1;
if (k) tmp += pos[k] - (std::lower_bound(Y + 1, Y + pos[k] + 1, Next) - Y) + 1;
dp[Next][j][k][0] = min(dp[Next][j][k][0], tmp);
}
if (nxt[j] && ((!i && !j && !k) || ed != 1)) {
int Next = j ? nxt[j] : frt[1];
int tmp = dp[i][j][k][ed];
if (i) tmp += pos[i] - (std::lower_bound(R + 1, R + pos[i] + 1, Next) - R) + 1;
if (k) tmp += pos[k] - (std::lower_bound(Y + 1, Y + pos[k] + 1, Next) - Y) + 1;
dp[i][Next][k][1] = min(dp[i][Next][k][1], tmp);
}
if (nxt[k] && ((!i && !j && !k) || ed != 2)) {
int Next = k ? nxt[k] : frt[2];
int tmp = dp[i][j][k][ed];
if (i) tmp += pos[i] - (std::lower_bound(R + 1, R + pos[i] + 1, Next) - R) + 1;
if (j) tmp += pos[j] - (std::lower_bound(G + 1, G + pos[j] + 1, Next) - G) + 1;
dp[i][j][Next][2] = min(dp[i][j][Next][2], tmp);
}
}
int ans = min(dp[lst[0]][lst[1]][lst[2]][0],
min(dp[lst[0]][lst[1]][lst[2]][1], dp[lst[0]][lst[1]][lst[2]][2]));
printf("%d", ans < 1e8 ? ans : -1);
return 0;
}
T3 ARC124E Pass to Next
毒瘤题,对我而言理解起来非常困难,也是鸽到现在才搞懂。。。
这题看上去非常不可做,特别是那个奇奇怪怪的累乘,所以我们直接跳过先做一些初步分析,想办法干掉这个累乘:
首先每个人给下一个人的球数不同,不代表最后的 \(x\) 序列不同,因为如果每个人都给了球,每个人少给一个结果是一样的。所以为了不重复,规定只算至少有一个人不给球的方案数。
然而这玩意儿特别难算,所以用所有方案数减去每个人都给了球的方案数。
考虑 \(\prod^n_{i=1}x_i\) 的实际意义,即给完球后每个人再从自己现在有的球中选出一个球的方案数。
而现在有的球有可能是给完别人后剩下的,也有可能是别人给我的,所以分成两种 dp 计算:
\(dp[i][0]\) 表示前 \(i-1\) 个人选完,第 \(i\) 个人从自己剩下的球中选方案数,\(dp[i][1]\) 表示前 \(i\) 个人选完,第 \(i\) 个人从别人给的球中选方案数
注意:一个是前 \(i-1\) 人选完,一个是前 \(i\) 人选完,也就是说 \(dp[i][0]\) 并没有计算第 \(i\) 个人选的方案数。
考虑四种转移:(以下指算所有方案的转移,每个人至少给一个的稍微改改就好)
第 \(i-1\) 个人需要从自己剩下的球中选,而剩下的球可能有 \(0\sim a_{i-1}\) 个。
第 \(i-1\) 个人需要考虑给第 \(i\) 个人多少个球,有 \(a_{i-1}+1\) 种选择。
第 \(i-1\) 个人需要考虑给第 \(i\) 个人多少个球,有 \(a_{i-1}+1\) 种选择,给完了后,第 \(i\) 个人要从中选一个,第 \(i-1\) 个人要从自己剩下的球选一个。
第 \(i-1\) 个人需要考虑给第 \(i\) 个人多少个球,给完了第 \(i\) 个人还要从中选一个。
以上第三个方程还不能直接计算。化一下式子:
然后就可以直接算了。
但这题是一个环,我们先枚举 \(1\) 是从自己剩下的球选还是从 \(n\) 给的球选,两种方案相加即可。
时间复杂度 \(O(n)\)
#include <cstdio>
#include <cstring>
#define int long long
const int mod = 1e9 + 7, inv2 = 500000004, inv6 = 166666668;
int n, a[1000005], f[1000005][2];
inline int s(int n) {return n * (n + 1) % mod * inv2 % mod;}
inline int t(int n) {return n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod;}
int solve(int p, int q) {
memset(f, 0, sizeof f);
f[1][0] = p ^ 1, f[1][1] = p;
for (int i = 2; i <= n; ++ i) {
f[i][0] = (f[i - 1][1] * (a[i - 1] - q + 1) + f[i - 1][0] * s(a[i - 1] - q)) % mod;
f[i][1] = (f[i - 1][1] * s(a[i - 1]) + f[i - 1][0] * (a[i - 1] * s(a[i - 1]) % mod - t(a[i - 1]))) % mod;
}
if (p == 0) return (f[n][1] * (a[n] - q + 1) + f[n][0] * s(a[n] - q)) % mod;
else return (f[n][1] * s(a[n]) + f[n][0] * (a[n] * s(a[n]) % mod - t(a[n]))) % mod;
}
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; ++ i) scanf("%lld", a + i);
printf("%lld", ((solve(0, 0) + solve(1, 0) - solve(0, 1) - solve(1, 1)) % mod + mod) % mod);
return 0;
}
T4:[JOI 2020 Final] 火事
把奇怪的题意转化一下,给你一个序列 \(a\),令 \(mx(l,r)\) 表示 \(a\) 序列区间 \([l,r]\) 最大值,每次询问 \(\sum\limits^r_{i=l} mx(i-t,i)\)。
直接算不可取,自然想到对于每个 \(a_i\),算当前它覆盖到的区间。
所谓它覆盖的区间,指对于当前 \(t\),所有 \(mx(j-t,j)=a_i(i\in [j-t,j])\) 的 \(j\) 构成的集合。显然这个集合是一段区间。特别地,当 \(a_i=a_j\),当且仅当 \(i<j\) 我们认为 \(i\) 覆盖了 \(j\)。
把询问离线下来,按 \(t\) 排序,从前往后推移这个 \(t\)。
令 \(l_i\) 为满足 \(j<i\and a_i\le a_j\) 的最大 \(j\),\(r_i\) 为满足 \(i<j\and a_i<a_j\) 的最小 \(j\)。
则对于当前的 \(t\),\(a_i\) 覆盖的区间即为 \([\max(i,l_i+t+1),\min(i+t,r_i-1)]\)。
从前往后推移 \(t\) 时,当前 \(a_i\) 覆盖的区间长度都可以表示为一个一次函数或常数。只有当如下三种情况时 \(a_i\) 覆盖区间长度的表达式会发生变化:
- \(i=l_i+t+1\)
- \(i+t=r_i-1\)
- \(l_i+t+1>r_i-1\)
对于每个 \(a_i\) 都有一个表达式,\(n\) 个表达式最多修改 \(3n\) 次。
考虑查询。\([l,r]\) 区间的查询可以转化成 \([1,r]-[1,l-1]\)。因此考虑查询 \([1,p]\)。
首先我们用 st 表查出当前覆盖 \(p\) 位置的 \(a_i\),则 \(a_1,a_2...a_{i-1}\) 覆盖的区间一定都在 \([1,p]\) 以内,答案先加上 \(1\sim i-1\) 所有表达式对于当前的 \(t\) 算出来的值。这里可以用树状数组维护表达式前缀和,一颗维护系数(斜率),一颗维护常数(截距)。
对于 \(a_i\),我们可以算出当前它覆盖的区间,也可以算出当前覆盖的区间有多少元素在 \([1,p]\) 以内。然后 \(a_i\) 的贡献也可以计算了。
时间复杂度 \(O(n\log n+q\log n+q\log q)\)。
#include <cstdio>
#include <stack>
#include <vector>
#include <algorithm>
#define int long long
inline int max(const int x, const int y) {return x > y ? x : y;}
int n, q, a[200005], l[200005], r[200005], st[200005][18], Log[200005], ans[200005];
struct BIT {
int c[200005];
inline void update(int x, int d) {
for (register int i = x; i <= n; i += (i & ~i + 1)) c[i] += d;
}
inline int query(int x) {
register int sum = 0;
for (register int i = x; i; i -= (i & ~i + 1)) sum += c[i];
return sum;
}
} slope, constant;
struct Quest {
int l, r, t, id;
inline bool operator < (const Quest a) const {return t < a.t;}
} que[200005];
std::vector<int> vec1[200005], vec2[200005], vec3[200005];
std::stack<int> s;
inline int query(int l, int r) {
int k = Log[r - l + 1];
return a[st[l][k]] >= a[st[r - (1 << k) + 1][k]] ? st[l][k] : st[r - (1 << k) + 1][k];
}
int getans(int p, int t) {
if (!p) return 0;
int k = query(max(1, p - t), p);
return slope.query(k - 1) * t + constant.query(k - 1) + (p - max(k, l[k] ? l[k] + t + 1 : k) + 1) * a[k];
}
signed main() {
scanf("%lld%lld", &n, &q);
for (int i = 1; i <= n; ++ i) scanf("%lld", a + i), st[i][0] = i;
for (int i = 2; i <= n; ++ i) Log[i] = Log[i >> 1] + 1;
for (int i = n; i; -- i) {
while (s.size() && a[s.top()] <= a[i]) l[s.top()] = i, s.pop();
s.push(i);
}
while (s.size()) s.pop();
for (int i = 1; i <= n; ++ i) {
while (s.size() && a[s.top()] < a[i]) r[s.top()] = i, s.pop();
s.push(i);
}
while (s.size()) r[s.top()] = n + 1, s.pop();
for (int i = 1; i <= n; ++ i) {
if (l[i]) vec1[i - l[i] - 1].push_back(i);
vec2[r[i] - i - 1].push_back(i);
if (l[i]) vec3[r[i] - l[i] - 1].push_back(i);
slope.update(i, a[i]), constant.update(i, a[i]);
}
for (register int j = 1; 1 << j <= n; ++ j)
for (register int i = 1; i + (1 << j) - 1 <= n; ++ i)
if (a[st[i][j - 1]] >= a[st[i + (1 << j - 1)][j - 1]]) st[i][j] = st[i][j - 1];
else st[i][j] = st[i + (1 << j - 1)][j - 1];
for (int i = 1; i <= q; ++ i) scanf("%lld%lld%lld", &que[i].t, &que[i].l, &que[i].r), que[i].id = i;
que[0].t = -1;
std::sort(que + 1, que + q + 1);
for (int i = 1; i <= q; ++ i) {
for (int j = que[i - 1].t + 1; j <= que[i].t; ++ j) {
for (int k : vec1[j]) slope.update(k, -a[k]), constant.update(k, (k - l[k] - 1) * a[k]);
for (int k : vec2[j]) slope.update(k, -a[k]), constant.update(k, (r[k] - 1 - k) * a[k]);
for (int k : vec3[j]) {
slope.update(k, slope.query(k - 1) - slope.query(k));
constant.update(k, constant.query(k - 1) - constant.query(k));
}
}
ans[que[i].id] = getans(que[i].r, que[i].t) - getans(que[i].l - 1, que[i].t);
}
for (int i = 1; i <= q; ++ i) printf("%lld\n", ans[i]);
return 0;
}