NOIP2024 补题

T1 - 编辑字符串

在不能修改的字符左右,将原字符串断开。问题等价于每一段可以重排列,最大化两个字符串匹配的位置数量。

从前往后依次处理:

  • 如果两个段能全部匹配上,就让它们全部匹配上,因为如果放弃这里的一个匹配去凑后面的段的匹配,至多也只能凑出一个。故而这种情况下,贪心全部匹配上是不劣的。
  • 如果两个段不能全部匹配上,就意味着除了匹配的部分,一个串剩下的全是 0,另一个剩下的全是 1。同理,贪心地把可以匹配上的部分全部匹配,答案也是不劣的。
//https://www.luogu.com.cn/problem/P11361
//https://www.luogu.com.cn/record/218537233
//https://www.luogu.com.cn/record/218581480
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
	static char st[40]; int top;
    if (x < 0) { putchar('-'); x = -x; }
	top = 0; do st[top++]=x%10 + '0'; while(x/=10);
	while(top) putchar(st[--top]);
}

const int N = 1e5 + 10;
struct seg {
    int r, c[2];
    seg(int _r = 0) {
        r = _r;
        c[0] = c[1] = 0;
    }
};
vector<seg> a[2];
char S[2][N], T[2][N];
int lst[2];
int n;
void ins(int x, int r) {
    if(r <= lst[x]) return;
    a[x].PB(seg(r)); int p = a[x].size() - 1;
    for(int i=lst[x]+1; i<=r; ++i)
        a[x][p].c[S[x][i]-'0']++;
    lst[x] = r;
    // cout << x << ": --- " << a[x].back().r << ' ' << a[x].back().c[0] << ' ' << a[x].back().c[1] << endl;
}

int main() {
    int ts; rd(ts);
    while(ts--) {
        scanf("%d", &n);
        scanf("%s%s%s%s", S[0]+1, S[1]+1, T[0]+1, T[1]+1);

        for(int t=0; t<2; ++t) {
            lst[t] = 0;
            for(int i=1; i<=n; ++i) {
                if(T[t][i] == '0') {
                    ins(t, i-1);
                    ins(t, i);
                }
            }
            ins(t, n);
        }
        int cur[2] = {0, 0};
        int lst = 0;
        int ans = 0;
        while(cur[0] < a[0].size() && cur[1] < a[1].size()) {
            seg x0 = a[0][cur[0]];
            seg x1 = a[1][cur[1]];

            // int len = min(x0.r, x1.r) - lst; lst = min(x0.r, x1.r);
            // int t0 = min(min(x0.c[0], x1.c[0]), len);
            // int t1 = min(min(x0.c[1], x1.c[1]), len-t0);
            // ans += t0 + t1;
            // if(x0.r == x1.r) {
            //     cur[0] ++, cur[1] ++;
            // } else if(x0.r > x1.r) {
            //     cur[1] ++;
            //     a[0][cur[0]].c[0] -= t0;
            //     a[0][cur[0]].c[1] -= t1;
            // }
            // else {
            //     cur[0] ++;
            //     a[1][cur[1]].c[0] -= t0;
            //     a[1][cur[1]].c[1] -= t1;
            // }

            int len = min(x0.r, x1.r) - lst; lst = min(x0.r, x1.r);
            int t0 = min(x0.c[0], x1.c[0]);
            int t1 = min(x0.c[1], x1.c[1]);
            ans += t0 + t1;

            if(x0.r == x1.r) {
                cur[0] ++, cur[1] ++;
            } else if(x0.r > x1.r) {
                cur[1] ++;
                a[0][cur[0]].c[0] -= t0;
                a[0][cur[0]].c[1] -= t1;
                if(t0 + t1 < len) {
                    if(a[0][cur[0]].c[0] > 0) a[0][cur[0]].c[0] -= len - (t0 + t1);
                    else a[0][cur[0]].c[1] -= len - (t0 + t1);
                }
            }
            else {
                cur[0] ++;
                a[1][cur[1]].c[0] -= t0;
                a[1][cur[1]].c[1] -= t1;
                if(t0 + t1 < len) {
                    if(a[1][cur[1]].c[0] > 0) a[1][cur[1]].c[0] -= len - (t0 + t1);
                    else a[1][cur[1]].c[1] -= len - (t0 + t1);
                }
            }
        }
        printf("%d\n", ans);
        a[0].clear(), a[1].clear();
    }
}

T2 - 遗失的赋值

什么样的限制可以带来非法的方案:

  • 某一对 \(i, j\) 满足 \(c_i = c_j, d_i \neq d_j\)
  • 对于某个区间 \([l, r]\)
    • 对于某个 \(i\)\(c_{i} = l\) ,且 \(d_i = a_l\)
    • \(a_{x + 1} = b_x\) 对于所有的 \(x \in [l, r-1]\)
    • 对于某个 \(j\)\(c_j = r\) ,且 \(d_j \neq b_{r-1}\)

\(c_i\) 去重并排序。由上可知,相邻的两个 \(c_i\) 之间的 \((a_x, b_x)\) 的合法性是独立的,分别算出方案数相乘即可。

//https://www.luogu.com.cn/problem/P11362
//https://www.luogu.com.cn/record/218530542
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
	static char st[40]; int top;
    if (x < 0) { putchar('-'); x = -x; }
	top = 0; do st[top++]=x%10 + '0'; while(x/=10);
	while(top) putchar(st[--top]);
}

const int mod = 1e9 + 7;
const int N = 1e5 + 10;

inline ll Pow(ll x, ll y) {
    int res = 1;
    for(; y; y>>=1, x = 1ll * x * x % mod)
        if(y&1) res = 1ll * res * x % mod;
    return res;
}

int m, c[N];
ll n, v;

map<int,int> mp;
int main() {
    int ts; rd(ts);
    while(ts--) {
        rd(n), rd(m), rd(v);
        mp.clear();
        bool flag = true;
        for(int i=1, d; i<=m; ++i) {
            rd(c[i]), rd(d);
            if(mp.count(c[i])) {
                if(mp[c[i]] != d) flag = false;
            } else {
                mp[c[i]] = d;
            }
        }
        sort(c + 1, c + m + 1);
        m = unique(c + 1, c + m + 1) - c - 1;
        if(!flag) {
            printf("0\n");
            continue;
        }
        ll res = Pow(v, (c[1] - 1) * 2) % mod;
        for(int i=1; i<m; ++i) {
            ll tot = Pow(v, (c[i+1] - c[i]) * 2);
            ll tmp = 1ll * Pow(v, c[i+1] - c[i] - 1) * (v-1) % mod;
            res = res * (tot - tmp) % mod;
        }
        res = res * Pow(v, (n - c[m]) * 2) % mod;
        res = (res % mod + mod) % mod;
        printf("%lld\n", res);
    }
    return 0;
}

T3 - 树的遍历

固定根节点,观察发现新树合法的充要条件为:

  • 一个结点的所有邻边,在新树中是连续的一条链
  • 结点到根的路径上的那条邻边必定是链的端点

对于某棵新树,合法的根结点必形成一条链,用“点减边”的容斥(每个关键点为根的方案数 - “相邻”关键点均作为根的方案数),即可保证每棵新树枝只被数一次。

//https://www.luogu.com.cn/problem/P11363
//https://www.luogu.com.cn/record/218550260
/*
for a path of length (#edges) L and has X 'roots' (and both ends of the path are 'roots'),
(X may not include all roots on the path)
its contribution is (-1)^(X-1) * (\prod_{i inside the path} (1/(deg[i]-1))

TOT = \prod_i (deg[i]-1)!
*/

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define PII pair<int,int>
#define FIR first
#define SEC second
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
	static char st[40]; int top;
    if (x < 0) { putchar('-'); x = -x; }
	top = 0; do st[top++]=x%10 + '0'; while(x/=10);
	while(top) putchar(st[--top]);
}

const int N = 1e5 + 10;
const int mod = 1e9 + 7;

inline ll Pow(ll x, ll y) {
    int res = 1;
    for(; y; y>>=1, x = 1ll * x * x % mod)
        if(y&1) res = 1ll * res * x % mod;
    return res;
}

vector<int> G[N];
vector<PII > E;
int mk[N];
int n;
int dep[N], deg[N];
void dfs1(int u, int last) {
    dep[u] = dep[last] + 1;
    deg[u] = G[u].size();
    for(int v : G[u]) if(v != last) dfs1(v, u);
}
int fac[N], inv[N];
void getfac(int n) {
    fac[0] = 1; for(int i=1; i<=n; ++i) fac[i] = 1ll * fac[i-1] * i % mod;
    inv[n] = Pow(fac[n], mod-2); for(int i=n; i>=1; --i) inv[i-1] = 1ll * inv[i] * i % mod;
    
    for(int i=n; i>=1; --i) inv[i] = 1ll * inv[i] * fac[i-1] % mod;
}

ll tot, ans;
ll f[N];

void dfs2(int u, int last) {
    f[u] = 0;
    for(int v : G[u]) if(v != last) {
        dfs2(v, u);
        ans = (ans - f[v] * f[u] % mod * tot) % mod;
        f[u] = (f[u] + f[v] * inv[deg[u] - 1]) % mod;
    }
    if(mk[u]) {
        ans = (ans - f[u] * tot) % mod;
        f[u] = 1;
    }
}

int main() {
    getfac(100000);
    int tcc, ts; rd(tcc), rd(ts);
    while(ts--) {
        int q; rd(n), rd(q);
        for(int i=1, x, y; i<n; ++i) {
            rd(x), rd(y);
            G[x].PB(y), G[y].PB(x);
            E.PB(MP(x, y));
        }
        dfs1(1, 0);

        for(int i=1, x; i<=q; ++i) {
            scanf("%d", &x); --x;
            int v = dep[E[x].FIR] < dep[E[x].SEC] ? E[x].SEC : E[x].FIR;
            mk[v] = 1;
            // cout << v << endl;
        }

        tot = 1;
        for(int i=1; i<=n; ++i) tot = tot * fac[deg[i] - 1] % mod;
        ans = tot * q % mod;
        dfs2(1, 0);
        printf("%lld\n", (ans % mod + mod) % mod);

        for(int i=1; i<=n; ++i) G[i].clear(), mk[i] = 0;
        E.clear();
    }
}

T4 - 树上查询

称满足 \(LCA^*(l, r) \neq LCA^*(l-1, r)\)\(LCA^* (l, r) \neq LCA^*(l, r+1)\) 的区间 \([l, r]\) 是极大的。

如果我们能找出所有的极大区间以及区间 LCA 深度 \((l_i, r_i, dep_i)\),我们就能这样计算答案:

  • 对于询问的 \((l, r, k)\),我们对所有的 \(r_i - l_i +1 \ge k\)\(l_i \le r - k + 1 \vee r_i \ge l + k - 1\)\(i\),计算 \(dep_i\) 最大值;如果没有这样的 \(i\),则答案为 \(dep(LCA^*(l, r))\)
  • 也就是二维偏序,可以扫描线+线段树可以处理(我的代码写的是可持久化线段树)

考虑如何计算 \(LCA^*(l, r)\):发现以任意顺序(例如,按照编号从小到大的顺序)在树上遍历这些点,必经过它们的 \(LCA\),必不会经过 \(LCA\) 以上的点;进而,\(LCA^*(l, r)\) 就是 \(LCA(l, l+1), LCA(l+1, l+2), \cdots LCA(r-1, r)\) 中深度最浅者,从而有 \(dep(LCA^*(l, r)) = \min_{l \le i < r} dep(LCA(i, i+1))\)

\(dep(LCA(i, i+1))\) 的权值建立笛卡尔树,极大的区间必然是树上某个结点所代表的区间,后者的数量为 \(2n-1\)。总复杂度 \(O(n \log n)\)

// query if in [l, r-k+1] exists a left end with >= k segment length
// 处理出合并前后所有的区间,以及所需深度
// 对 k 可持久化
// 查出 >=k 的树上,区间内的所需深度最大值
// 询问离线下来就不需要可持久化了,直接线段树即可

// https://www.luogu.com.cn/problem/P11364
// https://www.luogu.com.cn/record/218580277
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define PII pair<int,int>
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
	static char st[40]; int top;
    if (x < 0) { putchar('-'); x = -x; }
	top = 0; do st[top++]=x%10 + '0'; while(x/=10);
	while(top) putchar(st[--top]);
}

const int N = 5e5 + 10;
int n;
int dep[N];
namespace Graph {
    vector<int> G[N];
    int p[N][20];

    void dfs(int u, int last) {
        dep[u] = dep[last] + 1;
        p[u][0] = last;
        for(int v : G[u]) if(v!=last) dfs(v, u);
    }
    int LCA(int x, int y) {
        if(dep[x] < dep[y]) swap(x, y);
        for(int j=19; j>=0; --j) if(dep[x] - (1<<j) >= dep[y]) x = p[x][j];
        if(x == y) return x;
        for(int j=19; j>=0; --j) if(p[x][j] != p[y][j]) x=p[x][j], y=p[y][j];
        return p[x][0];
    }
    void predo() {
        rd(n);
        for(int i=1, x, y; i<n; ++i) {
            rd(x), rd(y);
            G[x].PB(y), G[y].PB(x);
        }
        dfs(1, 0);
        for(int j=1; j<=19; ++j) {
            for(int u=1; u<=n; ++u) {
                p[u][j] = p[p[u][j-1]][j-1];
            }
        }
    }
}
using Graph::LCA;

const int M = N * 40;
struct Seg {
    int lson[M], rson[M], mx[M], ncnt;
    int rt[N];
    inline void push_up(int c) {
        mx[c] = max(mx[lson[c]], mx[rson[c]]);
    }
    int pos, dep;
    void ins(int l, int r, int &c) {
        if(!c) c = ++ncnt;
        else {
            int u = c;
            c = ++ ncnt;
            lson[c] = lson[u];
            rson[c] = rson[u];
            mx[c] = mx[u];
        }

        if(l == r) {
            mx[c] = max(mx[c], dep);
            return;
        }
        int mid = l + r >> 1;
        if(pos <= mid) ins(l, mid, lson[c]);
        else ins(mid + 1, r, rson[c]);
        push_up(c);
    }
    vector<PII > Q[N];
    void ad(int l, int r, int dep) {
        // cerr << "add seg: " << l << ' ' << r << ' ' << dep << endl;
        // cerr << "add seg: " << r - l + 1 << ' ' << l << ' ' << dep << endl;
        Q[r-l+1].PB(MP(l, dep));
    }
    void main() {
        for(int k=n; k>=1; --k) {
            if(k < n) rt[k] = rt[k+1];
            for(auto X : Q[k]) {
                pos = X.FIR, dep = X.SEC;
                ins(1, n, rt[k]);
            }
        }
    }
    int ql, qr, qt;
    void qry(int l, int r, int c) {
        if(!c) return;
        if(ql <= l && qr >= r) {
            qt = max(qt, mx[c]);
            return;
        }
        int mid = l + r >> 1;
        if(ql <= mid) qry(l, mid, lson[c]);
        if(qr > mid) qry(mid + 1, r, rson[c]);
    }

    int QRY(int l, int r, int k) {
        ql = l, qr = r-k+1, qt = 0;
        qry(1, n, rt[k]);
        return qt;
    }
    // int QRY(int l, int r, int k) {
    //     int res = 0;
    //     for(int kk = k; kk <= n; ++kk) {
    //         for(auto X : Q[kk]) {
    //             int l0 = X.FIR;
    //             int r0 = X.FIR + kk - 1;
    //             int l1 = max(l0, l);
    //             int r1 = min(r0, r);
    //             if(r1 - l1 + 1 >= k)
    //                 res = max(res, X.SEC);
    //         }
    //     }
    //     return res;
    // }
}L, R;

namespace GetSeg {
    int LG[N];
    int rmq[20][N];
    int val[N];
    int getmin(int x, int y) {
        return val[x] < val[y] ? x : y;
    }
    void getrmq() {
        for(int i=2; i<=n; ++i) LG[i] = LG[i>>1] + 1;
        for(int i=1; i<n; ++i) val[i] = dep[LCA(i, i+1)], rmq[0][i] = i;
        for(int j=1; j <= 19; ++j) {
            for(int i=1; i + (1<<j) - 1 <n; ++i)
                rmq[j][i] = getmin(rmq[j-1][i], rmq[j-1][i + (1<<j-1)]);
        }
    }
    int segminpos(int l, int r) {
        int L = LG[r - l + 1];
        return getmin(rmq[L][l], rmq[L][r - (1<<L) + 1]);
    }
    int segminval(int l, int r) {
        int p = segminpos(l, r-1);
        return val[p];
    }
    void build(int l, int r) {
        if(l == r) {
            L.ad(l, l, dep[l]);
            R.ad(n-l+1, n-l+1, dep[l]);
            return;
        }
        int u = segminpos(l, r-1);
        L.ad(l, r, val[u]);
        R.ad(n-r+1, n-l+1, val[u]);
        build(l, u), build(u + 1, r);
    }
    void main() {
        getrmq();
        build(1, n);
    }
}

// int ans[N];

int main() {
    // freopen("query2.in", "r", stdin);
    // freopen("out.txt", "w", stdout);
    
    Graph::predo();
    GetSeg::main();
    L.main(), R.main();
    int l, r, k, q;
    rd(q);
    while(q--) {
        rd(l), rd(r), rd(k);
        int tmp = GetSeg::segminval(l, r);
        print(max(max(L.QRY(l, r, k), R.QRY(n-r+1, n-l+1, k)), tmp));
        putchar('\n');
        // printf("!-%d-\n", max(L.QRY(l, r, k), R.QRY(n-r+1, n-l+1, k)));
    }
    return 0;
}
posted @ 2025-05-29 04:37  zhongyuwei  阅读(52)  评论(0)    收藏  举报