Codeforces Round 1040 (Div. 1)
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;
} else
l = 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;
else
for(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;
}
本文来自博客园,作者:Orange_new,转载请注明原文链接:https://www.cnblogs.com/JPGOJCZX/p/19126444

浙公网安备 33010602011771号