CSP-S模拟17

T1.最大匹配

直觉告诉我,这道题是签到题,所以根据套路,一定存在某种排序方式,使得序列直接可以得出答案。所以我分别尝试了按照\(a、b、a+b、a-b\)排序的方法,发现了正解。于是考虑证明一下:对于带绝对值的东西,我们直接把它抽象成数轴上的点,那么\(w_{ij}=max(a_i,b_i)-min(a_j,b_j)\)\(max(a_j,b_j)-min(a_i,b_i)\),发现同一个数对中\(a、b\)的关系随便,所以不妨通过交换使得\(a\le b\),那么原式转化为\(b_i-a_j\)\(b_j-a_i\),实际上也就是从\(2n\)个数对中选\(n\)个,贡献为\(b_i\),其他的贡献为\(-a_i\),先令\(ans=\sum\limits_{i=1}^{2n}b_i\),从中选取一个数,让它的贡献变为\(-a_i\)\(ans\)发生的变化是\(-(a_i+b_i)\),我们想让答案最大,那么只需要选择\((a_i+b_i)\)最小的\(n\)个就行了。

代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define int long long 
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
using namespace std;
const int Z = 2e5 + 100;
inline char getch() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getch(); while (!isdigit(c)) f = c == '-', c = getch(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getch(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; }

int n, m, ans;
struct Two
{
    int a, b;
    friend bool operator <(Two A, Two B) { return A.a + A.b < B.a + B.b; }
}; Two s[Z];
sandom main()
{
    n = read(); m = n << 1;
    rep(i, 1, m) s[i].a = read(), s[i].b = read();
    sort(s + 1, s + 1 + m);
    rep(i, 1, n) ans += max({abs(s[i].a - s[i + n].a), abs(s[i].a - s[i + n].b), abs(s[i].b - s[i + n].a), abs(s[i].b - s[i + n].b)});
    cout << ans;
    return 0;
}

T2.挑战ABC

也是一道非算法题。首先次数上限肯定是\(3\),因为我们只需要把\(A、B、C\)分别覆盖一次即可。但是发现根本到不了这一步——预处理出每种颜色的前缀和。先考虑只操作一次,那情况肯定是只有一个字符出现次数\(< n\),我们先考虑满足它的限制,即我们需要找到一段区间,使得\((r-l+1)-(s_r-s_{l-1})=n-s_n\),含义就是我还需要其他颜色给我补充多少。移项得\(r-s_r=n-s_n+l-s_{l-1}-1\),枚举左端点,发现左边的式子是单调不减的,所以二分找到这个位置,在此基础上判断另外两种颜色是否合法;考虑操作两次,发现一个小性质——一段区间某一种颜色较多,且它本来就需要被减少,这种情况有利于减少操作。所以对于操作两次的情况,我们把最多的颜色压缩成出现次数恰好为\(n\)的,并把这一段区间全部变成最少的颜色,式子为\(s_r-s_{l-r}=s_n-n\),同上单调可二分。这时可能会出现原来最少的颜色现在最多了,但是多出来的这部分一整段都是它,这时我们只需要从这一段里截取一段给另一种就行了。

代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
using namespace std;
const int Z = 1e6 + 10;
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m;
struct opt { int l, r, c; };
vector <opt> ans;
char s[Z];
int sum[3][Z];
inline int calc(int pos, int id, int op) { return op ? id - sum[pos][id] : sum[pos][id]; }
inline int bound(int l, int r, int pos, int val, int op)
{
    int ans = 0;
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (calc(pos, mid, op) >= val) r = mid - 1, ans = mid;
        else l = mid + 1;
    }
    if (ans && calc(pos, ans, op) == val) return ans;
    return 0;
}
void solve1()//操作一次
{
    rep(i, 1, m)
    {
        rep(j, 0, 2) sum[j][i] = sum[j][i - 1];
        sum[s[i] - 'A'][i]++;
    }
    int tot = min(sum[0][m], min(sum[1][m], sum[2][m])), pos, delta = n - tot;
    rep(j, 0, 2) if (tot == sum[j][m]) pos = j;
    for (re l = 1, r; l <= m; ++l)
    {
        r = bound(l, m, pos, delta + l - sum[pos][l - 1] - 1, 1);
        if (r)
        {
            bool op = 1;
            rep(j, 0, 2) if (j != pos) if (sum[j][m] - (sum[j][r] - sum[j][l - 1]) != n) op = 0;
            if (op)
            {
                ans.push_back(opt{l, r, pos});
                printf("%ld\n", ans.size());
                for (auto j : ans) printf("%d %d %c\n", j.l, j.r, j.c + 'A');
                exit(0);
            }
        }
    }
}
void solve2()//操作两次
{
    int tot = max(sum[0][m], max(sum[1][m], sum[2][m])), pos, delta = tot - n;
    rep(j, 0, 2) if (tot == sum[j][m]) pos = j;
    int cnt = min(sum[0][m], min(sum[1][m], sum[2][m])), posp;
    dwn(j, 2, 0) if (cnt == sum[j][m]) posp = j;
    for (re l = 1, r; l <= m; ++l)
    {
        r = bound(l, m, pos, delta + sum[pos][l - 1], 0);
        if (r)
        {
            ans.push_back(opt{l, r, posp});
            rep(j, l, r) s[j] = posp + 'A';
            solve1();
        }
    }
}

sandom main()
{
    scanf("%d %s", &n, s + 1); m = 3 * n;
    rep(i, 1, m)
    {
        rep(j, 0, 2) sum[j][i] = sum[j][i - 1];
        sum[s[i] - 'A'][i]++;
    }
    if (sum[0][m] == n && sum[1][m] == n && sum[2][m] == n) { puts("0"); return 0; }
    solve1();
    solve2();
    return 0;
}

T3.三级跳

一眼就知道是一个线段树扫描线,可惜不知道该怎么整。发现一个性质:对于\(a、b\)这两个位置,一定是\([a, b]\)中的两个最大值——根据条件2得出,\(2b<=a+c\),也就是我想让\(a、b\)相距尽可能近。那么当\([a, b]\)中存在更大值时,我会选择把\(a\)\(b\)移动到那个位置,因为贡献更大且距离更近。一看这玩意符合单调性,那直接可以跑个单调栈了。从右往左扫描左端点,在\(c\)上加入\(w_a+w_b\)的贡献,在弹单调栈的过程中钦定\(a、b\),因为要\(c>=2b-a\),那需要在线段上\([2b-a, n]\)的位置上的\(\delta\)\(w_a+w_b\)\(max\)。线段树要维护的是原数组和\(\delta\)的最大值,实现见代码。

代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
using namespace std; 
const int Z = 5e5 + 10; 
inline char getch() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getch(); while (!isdigit(c)) f = c == '-', c = getch(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getch(); return f ? -x : x; }
inline void write(int x) { static int wrt[20]; int TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int max(int a, int b) { return a > b ? a : b; }

int n, m, ans[Z];
int w[Z], s[Z], top;
struct tree
{
    int max, sum, lz;
    #define lk (rt << 1)
    #define rk (rt << 1 | 1)
    #define mid (L + R >> 1)
}; tree tr[Z << 2];
inline void change(int rt, int val)
{
    tr[rt].lz = max(tr[rt].lz, val);
    tr[rt].sum = max(tr[rt].sum, tr[rt].max + val);
}
inline void pushup(int rt)
{
    tr[rt].max = max(tr[lk].max, tr[rk].max);
    tr[rt].sum = max(tr[lk].sum, tr[rk].sum);
}
inline void pushdown(int rt) { if (tr[rt].lz) change(lk, tr[rt].lz), change(rk, tr[rt].lz), tr[rt].lz = 0; }
void build(int rt, int L, int R)
{
    if (L == R) { tr[rt].sum = tr[rt].max = w[L]; return; }
    build(lk, L, mid), build(rk, mid + 1, R);
    pushup(rt);
}
void update(int rt, int L, int R, int l, int r, int val)
{
    if (l < 1 || l > r) return;
    if (l <= L && r >= R) { change(rt, val); return; }
    pushdown(rt);
    if (l <= mid) update(lk, L, mid, l, r, val);
    if (r > mid) update(rk, mid + 1, R, l, r, val);
    pushup(rt);
}
int query(int rt, int L, int R, int l, int r)
{
    if (l <= L && r >= R) return tr[rt].sum;
    pushdown(rt);
    int res = 0;
    if (l <= mid) res = max(res, query(lk, L, mid, l, r));
    if (r > mid) res = max(res, query(rk, mid + 1, R, l, r));
    return res;
}
struct joi
{
    int l, r, id;
    friend bool operator <(joi A, joi B) { return A.l > B.l; }
}; joi que[Z];

sandom main()
{
    n = read();
    rep(i, 1, n) w[i] = read();
    build(1, 1, n);
    m = read();
    rep(i, 1, m) que[i].l = read(), que[i].r = read(), que[i].id = i;
    sort(que + 1, que + 1 + m);
    int j = 1;
    dwn(i, n, 1)
    {
        while (top && w[s[top]] < w[i]) update(1, 1, n, 2 * s[top] - i, n, w[i] + w[s[top]]), top--;
        update(1, 1, n, 2 * s[top] - i, n, w[i] + w[s[top]]); s[++top] = i;
        while (que[j].l == i) ans[que[j].id] = query(1, 1, n, i, que[j].r), j++;
    }
    rep(i, 1, m) write(ans[i]);
    return 0;
}

T4.经典线性基

我建议直接参考官方题解,这样你会觉得这道题很扯淡。
image
翻译:把\([L, R]\)拆分成多个二进制长度的区间,也就是给出的性质\([t*2^k, (t+1)*2^k]\),然后当\(k\)足够大时,这段区间中的质数的线性基一定可以覆盖所有位置,因为质数是随机分布的,且我们有足够多的质数。那么对于小区间,直接暴力筛一遍质数,然后插入线性基。“我并没有证明,但是很难相信这是错的。”

代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define int long long 
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
using namespace std;
const int Z = 1e6 + 10; 
inline char getch() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getch(); while (!isdigit(c)) f = c == '-', c = getch(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getch(); return f ? -x : x; }
inline void write(int x) { static int wrt[20]; int TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int max(int a, int b) { return a > b ? a : b; } 

int p[42], ans;
inline int insert(int x)
{
    dwn(i, 40, 0)
        if (x & (1ll << i))
        {
            if (p[i]) x ^= p[i];
            else { p[i] = x; return 1; }
        }
    return 0;
}
bool prime[Z];
int ip[Z];
void Is_Prime(int n)
{
    rep(i, 2, n)
    {
        if (!prime[i]) ip[++ip[0]] = i;
        for (re j = 1, k; j <= ip[0] && (k = i * ip[j]) <= n; ++j)
        {
            prime[k] = 1;
            if (i % ip[j] == 0) break;
        }
    }
}
void solve(int l, int r, int k)
{
    memset(prime, 0, sizeof(prime));
    if (k > 16) { rep(i, 1, k) ans += insert(1ll << i); return; }
    rep(j, 1, ip[0]) for (re i = max((l - 1) / ip[j] + 1, 2) * ip[j]; i <= r; i += ip[j]) prime[i - l] = 1;
    rep(i, l, r) if (!prime[i - l]) ans += insert(i);
}

sandom main()
{
    int T = read();
    Is_Prime(1e6);
    while (T--)
    {
        memset(p, 0, sizeof(p)); ans = 0;
        int l = read(), r = read() + 1; 
        rep(i, 0, 40) if (((1ll << i) & l) && l + (1ll << i) <= r) solve(l, l + (1ll << i) - 1, i), l += 1ll << i;
        dwn(i, 40, 0) if (l + (1ll << i) <= r) solve(l, l + (1ll << i) - 1, i), l += 1ll << i;
        write(1ll << ans);
    }
    return 0;
}
posted @ 2022-10-08 07:37  sandom  阅读(70)  评论(0编辑  收藏  举报