A. Double Perspective
题解
skip
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e3 + 9;
struct Line{int id, a, b;
} l[N];
bool cmp(Line x, Line y){if(x.a == y.a)return x.b > y.b;return x.a < y.a;
}
int ans[N], T, n, cnt;
int main(){scanf("%d", &T);while(T--){scanf("%d", &n);for(int i = 1; i <= n; i++){scanf("%d%d", &l[i].a, &l[i].b);l[i].id = i;}sort(l + 1, l + n + 1, cmp);cnt = 0;for(int i = 1; i <= n; i++)if(l[i].b > l[ans[cnt]].b)ans[++cnt] = i;printf("%d\n", cnt);for(int i = 1; i <= cnt; i++)printf("%d ", l[ans[i]].id);printf("\n");}return 0;
}
B. Stay or Mirror
题解
skip
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 9;
int a[N], T, n, ans;
int main(){scanf("%d", &T);while(T--){scanf("%d", &n);for(int i = 1; i <= n; i++)scanf("%d", &a[i]);ans = 0;for(int i = 1; i <= n; i++){int l = 0, tot = 0;for(int j = 1; j <= n; j++){if(a[j] > a[i]){if(j < i)l++;tot++;}}ans += min(l, tot - l);}printf("%d\n", ans);}return 0;
}
C1.C2.C3 Interactive RBS
题解
根据我们这种猜测类型交互题的惯用套路,我们希望能先确定下来一个 \((\)。此时我们直接二分查找最短的前缀 \([1, mid]\),使得合法括号子序列数大于 \(0\),此时 \(mid - 1\) 一定是一个 \((\)。如果找不到这样的前缀,那么只可能是 \())\dots)((\dots(\) 这种情况,此时 \(n\) 位置一定是一个 \((\)。我们将这个位置设为 \(L\)。
那么现在就可以有一个最暴力的想法,那就是直接将一个还没确定的位置 \(i\) 与 \(L\) 组成询问 ? 2 L i
,此时如果答案是 \(1\),那么 \(i\) 是 \()\),否则 \(i\) 是 \((\)。不过这种做法一个 Version 都过不了。
于是我们考虑能否一次确定多个位置是左括号还是右括号,不过此时我们不能简单地询问 ? 4 L i L L j
(L i
和 L j
中间的 L
是为了将两边的贡献独立开),此时如果返回 \(1\),那么我们不知道是 \(i\) 造成的贡献还是 \(j\) 造成的贡献。于是我们考虑将 L i
重复 \(k\) 次,如果 \(i\) 是 \((\),那么就会造成 \(\displaystyle\frac{k (k + 1)}{2}\) 的贡献,否则就不造成贡献。注意此时我们不能让 \(\displaystyle\frac{k_1 (k_1 + 1)}{2} \pm \frac{k_2 (k_2 + 1)}{2} \pm \dots \pm \frac{k_m (k_m + 1)}{2} = \frac{k_{m + 1} (k_{m + 1} + 1)}{2}\),因为这样我们还是不知道是谁造成的贡献。
打表可以发现 \(\{1, 2, 3, 5, 7, 10, 15, 21, 30, 43, 61, 87, 123\}\) 是一组合法的 \(k\) 的取值,此时我们可以一次性确定 \(13\) 个位置是左括号还是右括号,因此交互次数为 \(\left\lfloor \displaystyle\frac{1000}{13} \right\rfloor + 11 = 88\) 次,足以通过 \(3\) 个 Version。
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 9, M = 1e3 + 9;
int a[14] = {0, 1, 3, 6, 15, 28, 55, 120, 231, 465, 946, 1891, 3828, 7626}, b[14] = {0, 1, 2, 3, 5, 7, 10, 15, 21, 30, 43, 61, 87, 123};
vector <int> ans[N], vec;
void dfs(int now, int sum){if(now == 14){ans[sum] = vec;return;}vec.push_back(now);dfs(now + 1, sum + a[now]);vec.pop_back();dfs(now + 1, sum);
}
int T, n, L, tmp;
char ch[M];
int main(){dfs(1, 0);cin >> T;while(T--){cin >> n;for(int i = 1; i <= n; i++)ch[i] = ' ';int l = 1, r = n, L = n;while(l <= r) {int mid = (l + r) >> 1;cout << "? " << mid << " ";for(int i = 1; i <= mid; i++)cout << i << " ";cout << endl;cin >> tmp;if(tmp > 0){L = mid - 1;r = mid - 1;} elsel = mid + 1;}for(int i = 1; i <= n; i += 13){int sum = 0;for(int j = i; j <= min(i + 12, n); j++){sum += b[j - i + 1] * 2;if(j != min(i + 12, n))sum++;}cout << "? " << sum << " ";for(int j = i; j <= min(i + 12, n); j++){for(int k = 1; k <= b[j - i + 1]; k++)cout << L << " " << j << " ";if(j != min(i + 12, n))cout << L << " ";}cout << endl;cout.flush();cin >> tmp;for(int j = 0; j < (int)ans[tmp].size(); j++)ch[i + ans[tmp][j] - 1] = ')';for(int j = i; j <= min(i + 12, n); j++)if(ch[j] == ' ')ch[j] = '(';}cout << "! ";for(int i = 1; i <= n; i++)cout << ch[i];cout << endl;cout.flush();}return 0;
}
D. Permutation Blackhole
题解
首先,我们注意到一旦将一个点涂成了黑色,那么左右两边就独立了,不会再互相贡献。因此我们考虑区间 DP。设 \(dp_{l, r}\) 表示将区间 \([l, r]\) 从全部为 \(0\) 变成 \(s[l, r]\) 的方案数。但是我们发现在枚举一个区间的分界点 \(k\) 时,它会对 \(l - 1\) 或者 \(r + 1\) 造成贡献,而这会使得 DP 不方便转移。
于是我们将 DP 再增加两个维度。设 \(dp_{l, r, a, b}\) 表示将区间 \([l, r]\) 从全部为 \(0\) 变成 \(s[l, r]\),对 \(l - 1\) 造成了 \(a\) 的贡献,对 \(r - 1\) 造成了 \(b\) 的贡献的方案数。此时再来枚举 \(k\),如果 \(s_k \neq -1\),那么我们只需要考虑 \([l, k - 1]\) 和 \([k + 1, r]\) 分别造成了多少贡献,乘在一起就可以更新答案。注意到左右两边是独立的,因此还需要乘以一个归并的系数 \(\displaystyle\binom{r - l}{k - l}\)。如果 \(s_k = -1\),那么我们就将 \([l, k - 1]\) 可能造成的贡献加起来,\([k + 1, r]\) 可能造成的贡献加起来,两边再乘起来更新答案,相当于做了一个类似卷积的操作,注意此时还是需要乘以归并的系数。
不过此时时间复杂度为 \(\mathcal O(n^3 \max^3 \{s_i\})\),无法通过此题。注意到如果一个位置 \(k\) 左边已经有一个位置 \(p\) 对 \(k\) 造成贡献了,那么如果还要再选一个位置 \(q\) 对 \(k\) 造成贡献,那么 \(q\) 一定在 \(p, k\) 中点的右侧,不然 \(q\) 就对 \(p\) 造成贡献。因此 \(s_k\) 不能超过 \(2 \times \log n\),如果有 \(s_k\) 不满足条件,那么问题一定无解。于是现在时间复杂度就降低到了 \(\mathcal O(n^3 \log^3 n)\),可以通过此题。
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e2 + 9, LOGN = 9, MOD = 998244353;
int qpow(int a, int b){int res = 1;while(b > 0){if(b & 1)res = res * a % MOD;a = a * a % MOD;b >>= 1;}return res;
}
int dp[N][N][LOGN * 2][LOGN * 2], fac[N], inv[N], s[N], n, T;
bool flag;
int binom(int n, int m){if(n < m)return 0;return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
signed main(){fac[0] = 1;for(int i = 1; i <= 100; i++)fac[i] = fac[i - 1] * i % MOD;inv[100] = qpow(fac[100], MOD - 2);for(int i = 99; i >= 0; i--)inv[i] = inv[i + 1] * (i + 1) % MOD;scanf("%lld", &T);while(T--){scanf("%lld", &n);for(int i = 1; i <= n; i++){scanf("%lld", &s[i]);if(s[i] > 2 * LOGN)flag = true;}if(flag){printf("0\n");continue;}memset(dp, 0, sizeof(dp));for(int len = 1; len <= n; len++){for(int l = 1; l + len - 1 <= n; l++){int r = l + len - 1;for(int a = 0; a < LOGN; a++){for(int b = 0; b < LOGN; b++){if((l == 1 && a != 0) || (r == n && b != 0))continue;for(int k = l; k <= r; k++){int x = a, y = b;if(l != 1 && (r == n || k - l + 1 <= r - k + 1))x--;if(r != n && (l == 1 || r - k + 1 < k - l + 1))y--;if(x < 0 || y < 0 || (k == l && x != 0) || (k == r && y != 0))continue;// cout << l << " " << r << " " << a << " " << b << " " << k << " " << endl;if(len == 1){if(x == 0 && y == 0 && s[l] <= 0)dp[l][r][a][b] = 1;continue;}int c = binom(r - l, k - l);if(s[k] != -1){if(k == l)dp[l][r][a][b] = (dp[l][r][a][b] + dp[k + 1][r][s[k]][y]) % MOD;else if(k == r)dp[l][r][a][b] = (dp[l][r][a][b] + dp[l][k - 1][x][s[k]]) % MOD;elsefor(int i = 0; i <= s[k]; i++)dp[l][r][a][b] = (dp[l][r][a][b] + dp[l][k - 1][x][i] * dp[k + 1][r][s[k] - i][y] % MOD * c % MOD) % MOD;} else {int sum1 = 0, sum2 = 0;for(int i = 0; i < LOGN; i++){sum1 = (sum1 + dp[l][k - 1][x][i]) % MOD;sum2 = (sum2 + dp[k + 1][r][i][y]) % MOD;}if(k == l)sum1 = 1;if(k == r)sum2 = 1;dp[l][r][a][b] = (dp[l][r][a][b] + sum1 * sum2 % MOD * c % MOD) % MOD;}}}}}}printf("%lld\n", dp[1][n][0][0]);}return 0;
}
E. Induced Subgraph Queries
题解
这种权值会变化的区间第 \(k\) 小问题,很难不想到莫队。考虑到区间移动时,如果带了一个 \(log\),那么时间复杂度会非常极限,因此我们考虑值域分块,这样修改的时间复杂度就降低到了 \(O(1)\),于是平衡下来的时间复杂度就是 \(\mathcal O(n \sqrt m)\)。
现在来考虑如何莫队。我们发现当一个端点右移到 \(x\) 时,会花费 \(\mathcal O(\deg_x)\) 的时间复杂度修改。这就导致我们不能像普通莫队那样按照编号分块,否则,如果是一个菊花图,每次询问区间都从一个叶子节点,变成这个叶子节点加上中心,再变成另一个叶子节点,这样就炸了。于是我们希望平衡一下 \(l\) 在每个块移动的时间复杂度。于是我们对度数分块,将度数和为 \(\sqrt{2 \times m}\) 的几个点分在一个块内。此时时间复杂度就是真正的 \(\mathcal O(n \sqrt m)\)。
注意到如果有一堆点的度数为 \(0\),那么如果它们被分到了一个块内,那么会导致 \(l\) 在每个块内移动的时间复杂度再次不平衡,因此我们需要给每个点的度数提前加 \(1\)(毕竟可以用无数个 \(\deg_u = 0\) 在块内,但是只会有 \(\sqrt{2 * m}\) 个 \(\deg_u = 1\) 在块内),这样就会避免这种情况。这也启发我们,只要能让块内的修改次数平衡,那么对任何维度分块再莫队都是对的
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define MAXSIZE (1 << 20)
char buf[MAXSIZE], *p1, *p2;
char pbuf[MAXSIZE], *pp;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) ? EOF : *p1++)
#define pc putchar
inline int read(){int x = 0, f = 1;char ch = gc();while(!isdigit(ch)){if(ch == '-') f = -1;ch = gc();}while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = gc();}return x * f;
}
inline void write(int x){if(x < 0){x = -x;pc('-');}static short Stack[64], top = 0;do {Stack[++ top] = x % 10;x /= 10;} while(x);while(top)pc(Stack[top--] | 48);
}
const int N = 3e5 + 9, SN = 6e2 + 9;
struct Edge{int v, nex;
} e[N];
int head[N], ecnt;
void addEdge(int u, int v){e[++ecnt] = Edge{v, head[u]};head[u] = ecnt;
}
struct Query{int id, l, r, k;
} q[N];
int b1[N], b2[N];//b1:度数分块,b2:值域分块
inline bool cmp(Query x, Query y){if(b1[x.l] ^ b1[y.l]) return x.l < y.l;if(b1[x.l] & 1) return x.r > y.r;return x.r < y.r;
}
int sum1[SN], sum2[N];
void modify(int id, int x){sum1[b2[id]] += x;sum2[id] += x;
}
int deg[N], res[N], ans[N], L, R, T, n, m, qq;
void add(int x){res[x] = 0;for(int i = head[x]; i; i = e[i].nex){int v = e[i].v;if(L <= v && R >= v){modify(res[v], -1);res[v] ^= x;modify(res[v], 1);res[x] ^= v;}}modify(res[x], 1);
}
void del(int x){res[x] = 0;for(int i = head[x]; i; i = e[i].nex){int v = e[i].v;if(L <= v && R >= v){modify(res[v], -1);res[v] ^= x;modify(res[v], 1);res[x] ^= v;}}modify(res[x], -1);
}
void init(){for(int i = 1; i <= n; i++){deg[i] = 1;ans[i] = b1[i] = head[i] = 0;}for(int i = 0; i <= 2 * n; i++)sum2[i] = 0;for(int i = 0; i <= ceil(sqrt(2.0 * n)); i++)sum1[i] = 0;ecnt = 0;
}
int main(){T = read();while(T--){n = read();m = read();init();int k1 = sqrt(2 * n + 2 * m), k2 = sqrt(2 * n);for(int i = 1; i <= 2 * n; i++)b2[i] = (i - 1) / k2 + 1;for(int i = 1; i <= m; i++){int u, v;u = read();v = read();addEdge(u, v);addEdge(v, u);deg[u]++;deg[v]++;}int sum = 0, tot = 1;for(int i = 1; i <= n; i++){b1[i] = tot;sum += deg[i];if(sum > k1){sum = 0;tot++;}}qq = read();for(int i = 1; i <= qq; i++){q[i].l = read();q[i].r = read();q[i].k = read();q[i].id = i;}sort(q + 1, q + qq + 1, cmp);L = 1, R = 0;for(int i = 1; i <= qq; i++){while(L > q[i].l)add(--L);while(R < q[i].r)add(++R);while(L < q[i].l)del(L++);while(R > q[i].r)del(R--);sum = 0, tot = 0;while(sum + sum1[tot] < q[i].k){sum += sum1[tot];tot++;}tot--;tot *= k2;tot++;while(sum + sum2[tot] < q[i].k){sum += sum2[tot];tot++;}ans[q[i].id] = tot;}for(int i = 1; i <= qq; i++){write(ans[i]);pc('\n');}}return 0;
}