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 jL iL 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;
}
posted @ 2025-10-05 11:59  Orange_new  阅读(13)  评论(1)    收藏  举报