2025/7/23 集训NOIP模拟赛

T1 排序题

首先看到绝对值先拆一下试试。

\(\left|x_i - x_j\right| \leq w_i - w_j \Rightarrow \begin{cases} w_i - x_i \geq w_j - x_j \\ w_i + x_i \geq w_j + x_j \end{cases}\)

埋下伏笔。

你直接考虑 \(x_i\) 为横坐标,\(w_i\) 为纵坐标,则一个点 \(i\) 能够连带染色的区域是一个斜边在 \(x\) 轴上的等腰直角三角形,这很不好做。

此时刚才拆的式子就用上了,只需要满足后面大括号的条件,那么令 \(x\) 坐标为 \(w_i + x_i\),纵坐标为 \(w_i - x_i\),然后需要维护的就是该点左下角的矩形。

那么贪心的想,只有右上角的点是需要我们主动去选的(右上角的点定义为最大的点使得其右上角不再有比它大的点)。那么只需要单调栈或者排序什么的做就行。

按照 \(u_i = w_i + x_i\) 从大到小,相同则 \(v_i = w_i - x_i\) 从大到小,如果存在 \(v_i > \max\limits_{j=1}^{i-1}\{v_j\}\) 说明 \((u_i,v_i)\) 为右上角点。

#include <bits/stdc++.h>
#define FILEIO
#define FASTIO
#define KamisatoAyaka return 0
#define int long long
using namespace std;
#ifdef FASTIO
    char buf[1 << 23], *p1, *p2;
    #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 23, stdin), p1 == p2) ? EOF : *p1++)

    inline int read() {
        int res = 0, f = 1;
        char ch = getchar();

        while (!isdigit(ch))
            f = ch == '-' ? -1 : 1, ch = getchar();
        while (isdigit(ch))
            res = res * 10 + (ch ^ 48), ch = getchar();
        return res * f;
    }

    static int ostk[33];
    inline void write(int x) {
        int top = 0;
        if (x < 0)
            x = -x, putchar('-');
        do {
            ostk[top++] = x % 10, x /= 10;
        } while(x);
        while(top) 
            putchar(ostk[--top] + '0');
        putchar('\n');
    }
#endif

constexpr int N = 500100;
int n, ans;
int sta[N], top;

struct points {
    int x, y;
    bool operator < (const points &s) const {
        return x == s.x ? y > s.y : x > s.x;
    }
} point[N];

signed main() {
    #ifdef FILEIO
        freopen("sort.in", "r", stdin);
        freopen("sort.out", "w", stdout);
    #endif

    n = read();
    for (int i = 1, a, b; i <= n; i ++) {
        a = read(), b = read();
        point[i].x = b + a, point[i].y = b - a;
    }

    sort(point + 1, point + n + 1);
    for (int i = 1, mx = -3e18; i <= n; i ++) {
        if (point[i].y > mx) {
            mx = point[i].y;
            ans ++;
        }
    }    
    write(ans);
    KamisatoAyaka;
}

T2 预处理器

不是 \(2022\) 省选的预处理器。

题面翻译过来就是问有多少种构造数组 \(d_i\) 的方案使得其满足 \(\forall i \in [1, n], d_i \in [A_i, B_i]\)\(\sum\limits_{i=1}^{n}{d_i} \in [S,T]\),且满足 \(d_i \bmod 2 = P_i\)

如果 \(A_i \bmod 2 \neq P_i\),则可以 \(A_i \to A_i + 1\),那么最后会在 \((S,T]\) 内考虑。满足下界的话直接 \(S,T\) 都先减去 \(\sum{A_i}\) 再做,这样同时也满足了 \(\forall d_i \geq A_i\) 的限制。

然后你发现选的 \(d_i\) 奇偶性需要相同,那么区间 \([A_i,B_i]\) 内一定是两个两个选,那么记 \(C_i = \lfloor{\frac{B_i - A_i}{2}}\rfloor\),问题就转化成每个 \(i\) 最多选 \(C_i\),且 \(\sum{C_i} \in (\frac{S}{2},\frac{T}{2}]\) 的方案数。考虑前缀和思想,记 \(F(k)\) 表示满足 \(\sum{C_i} \leq k\) 的方案数,最后答案就是 \(F(\frac{T}{2}) - F(\frac{S}{2})\)

下界限制很好做,插板法。但是上界不好做,考虑容斥。

考虑枚举超出限制的子集 \(\mathbb{S} \in \{1,2,\dots,n\}\),表示对于 \(x \in \mathbb{S}\) 该点选了 \(C_x + 1\) 个,那么剩余 \(k - \sum\limits_{x in \mathbb{S}}{(C_x + 1)}\) 个分给其余 \(n + 1\) 个。为什么是 \(n + 1\) 呢?因为最后的限制是小于等于,那么多的就扔到虚拟的第 \(n+1\) 个,即原本是 \(\sum\limits_{i=1}^{d_i} \leq k \to \sum\limits_{i=1}^{n+1}{d_i} = k \enspace (d_i \geq 0)\),然后又可以变成 \(\sum\limits_{i=1}^{n}{(d_i + 1)} = k + n\),这样方案数就是 \(C_n^{k+n}\),乘上容斥系数计算即可,复杂度 \(O(n^22^n)\)

#include <bits/stdc++.h>
#define FASTIO
#define FILEIO
#define int long long
using namespace std;

#ifdef FASTIO
    static int ostk[33];
    char buf[1 << 23], *p1, *p2;
    #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 23, stdin), p1 == p2) ? EOF : *p1++)

    inline int read() {
        int res = 0, f = 1;
        char ch = getchar();
        while (!isdigit(ch))
            f = ch == '-' ? -1 : 1, ch = getchar();
        while (isdigit(ch))
            res = res * 10 + (ch ^ 48), ch = getchar();
        return res * f;
    }

    inline void write(int x) {
        int top = 0;
        if (x < 0)
            x = -x, putchar('-');
        do {
           ostk[top++] = x % 10, x /= 10;
        } while(x);
        while(top)
            putchar(ostk[--top] + '0');
        putchar('\n');
    }
#endif

constexpr int N = 15;
int n, mod, S, T, sum;
int a[N], b[N], c[N];

int gcd(int x, int y) {
    return !y ? x : gcd(y, x % y);
}

inline int calc(int x) {
    if (x < 0)
        return 0;

    int res = 1;
    vector<int> num;

    for (int i = 1; i <= n; i ++)
        num.push_back(x + i);
    for (int i = 1; i <= n; i ++) {
        int t = i;
        for (int &j : num) {
            int g = gcd(t, j);
            t /= g, j /= g;
        }
    }
    for (int j : num)
        res = res * j % mod;
    return res;
}

int F(int k) {
    if (k < 0)
        return 0;

    int res = 0;
    for (int s = 0; s < (1 << n); s ++) {
        int val = k, popcount = 0;
        for (int i = 1; i <= n; i ++) {
            if ((s >> (i - 1)) & 1) {
                val -= (c[i] + 1);
                popcount ++;
            }
        }
        res = (res + calc(val) * ((popcount & 1) ? -1 : 1) % mod + mod) % mod;
    }
    return res;
}

signed main() {
    #ifdef FILEIO
        freopen("preprocessor.in", "r", stdin);
        freopen("preprocessor.out", "w", stdout);
    #endif
    n = read();
    S = read();
    T = read();
    mod = read();
    for (int i = 1, p; i <= n; i ++) {
        a[i] = read();
        b[i] = read();
        p = read();
        a[i] += ((a[i] & 1) != p);
        sum += a[i];
        c[i] = (b[i] - a[i]) / 2;
    }

    S = floor(1.0 * (S - sum - 1) / 2);
    T = floor(1.0 * (T - sum) / 2);
    write((F(T) - F(S) + mod) % mod);
    return 0;
}

T3 计数

\(b_i\) 表示 \(a_i\) 的上界,即所有覆盖 \(a_i\) 的区间 \([l_j,r_j]\)\(m_j\) 的最小值,我们将 \(b\) 单独拿出来考虑,在离散化过后会发现限制条件转换成了:

\[\begin{cases} \forall j \in [l_i',r_i'], b_j \leq m_i' \\ \exists j \in [l_i',r_i'], b_j = m_i' \end{cases} \]

\(n'\) 为新的范围。

考虑 dp,记 \(f_i\) 表示考虑了前 \(i\) 个数,钦定第 \(i\) 个数为限制的 \(m_k'\),且满足了前缀所有区间限制的方案数。那么转移一定是从上一个最靠近 \(i\) 的区间左端点转移,则有:

\[f_i = (m_k'^{len} - (m_k' - 1)^{len}) \sum\limits_{j<i}{f_j \times (m_k' - 1)^{slen_{i-1} - slen_{j}}} \]

\(len\)\(slen\) 是指由于 \(n \leq 9 \times 10^8\),所以我们需要分段考虑,\(slen\) 则是 \(len\) 的前缀和。用线段树区间乘区间和维护转移。

离散化后预处理出 \(b\),对于覆盖 \(b\) 的每个区间求解,最后答案在 \(f_{n' + 1}\) 取到,乘起来即可。

#include <bits/stdc++.h>
#define FILEIO
#define FASTIO
#define int long long
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;

#ifdef FASTIO
    static int ostk[33];
    char buf[1 << 23], *p1, *p2;
    #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 23, stdin), p1 == p2) ? EOF : *p1++)

    inline int read() {
        int res = 0, f = 1;
        char ch = getchar();
        while (!isdigit(ch))
            f = ch == '-' ? -1 : 1, ch = getchar();
        while (isdigit(ch))
            res = res * 10 + (ch ^ 48), ch = getchar();
        return res * f;
    }

    inline void write(int x) {
        int top = 0;
        if (x < 0)
            x = -x, putchar('-');
        do {
            ostk[top++] = x % 10, x /= 10; 
        } while(x);
        while(top)
            putchar(ostk[--top] + '0');
        putchar('\n');
    }
#endif

constexpr int N = 2010;
constexpr int mod = 998244353;
constexpr int inf = 0x3f3f3f3f3f3f3f3f;
int Test;
int n, Q, A;
int s[N], sx[N], cnts, nx;
int leaf[N], ans, f[N];
vector<int> q[N], v[N];

struct require {
    int l, r, x;
} a[N];

inline int fpow(int x, int exp) {
    if (!exp)
        return 1;
    int res = 1;
    for (; exp; exp /= 2) {
        if (exp & 1)
            res = res * x % mod;
        x = x * x % mod;
    }
    return res;
}

inline int length(int p) {
    return (p & 1) ? (s[p / 2 + 1] - s[p / 2] - 1) : 1;
}

inline bool cmp(int x, int y) {
    return a[x].r == a[y].r ? a[x].l > a[y].l : a[x].r < a[y].r;
} 

namespace sgta {
    struct segmentree {
        int sum, cov, mul;
        segmentree() { 
            sum = 0, cov = -1, mul = 1; 
        }
    } sgt[N << 2];

    void build(int rt, int l, int r) {
        sgt[rt] = segmentree();
        if (l == r)
            return;
        int mid = (l + r) / 2;
        build(lson, l, mid);
        build(rson, mid + 1, r);
    }

    inline void pushdown(int rt) {
        if (~sgt[rt].cov) {
            sgt[lson].cov = sgt[lson].sum = sgt[rt].cov;
            sgt[rson].cov = sgt[rson].sum = sgt[rt].cov;
            sgt[rt].cov = -1;
        }
        if (sgt[rt].mul > 1) {
            sgt[lson].mul = sgt[rt].mul * sgt[lson].mul % mod;
            sgt[lson].sum = sgt[rt].mul * sgt[lson].sum % mod;
            sgt[rson].mul = sgt[rt].mul * sgt[rson].mul % mod;
            sgt[rson].sum = sgt[rt].mul * sgt[rson].sum % mod;
            sgt[rt].mul = 1;
        }
    }

    inline void pushup(int rt) {
        sgt[rt].sum = (sgt[lson].sum + sgt[rson].sum) % mod;
    }

    void update(int rt, int ql, int qr, int l, int r, int val, bool sign) {
        if (ql > qr)
            return;
        if (ql <= l && qr >= r) {
            if (sign) {
                sgt[rt].mul = 1;
                sgt[rt].cov = sgt[rt].sum = val;
            }
            else {
                sgt[rt].sum = sgt[rt].sum * val % mod;
                sgt[rt].mul = sgt[rt].mul * val % mod;
            }
            return;
        }

        pushdown(rt);
        int mid = (l + r) / 2;
        if (ql <= mid) 
            update(lson, ql, qr, l, mid, val, sign);
        if (qr > mid)
            update(rson, ql, qr, mid + 1, r, val, sign);
        pushup(rt);
    }

    int query(int rt, int ql, int qr, int l, int r) {
        if (ql > qr)
            return 0;
        if (ql <= l && qr >= r)
            return sgt[rt].sum;
        
        pushdown(rt);
        int mid = (l + r) / 2, res = 0;
        if (ql <= mid)
            res = (res + query(lson, ql, qr, l, mid)) % mod;
        if (qr > mid)
            res = (res + query(rson, ql, qr, mid + 1, r)) % mod;
        return res;
    }
}

namespace sgtb {
    struct segmentree {
        int minn, tag;
        segmentree() {
            tag = inf;
        }
    } sgt[N << 2];

    void build(int rt, int l, int r) {
        sgt[rt] = segmentree();
        if (l == r)
            return;
        int mid = (l + r) / 2;
        build(lson, l, mid);
        build(rson, mid + 1, r);
    }

    void cover(int rt, int ql, int qr, int l, int r, int val) {
        if (ql > qr)
            return;
        if (ql <= l && qr >= r) {
            sgt[rt].tag = min(sgt[rt].tag, val);
            return;
        }
        int mid = (l + r) / 2;
        if (ql <= mid)
            cover(lson, ql, qr, l, mid, val);
        if (qr > mid)
            cover(rson, ql, qr, mid + 1, r, val);
    }

    void down(int rt, int l, int r, int val) {
        val = min(val, sgt[rt].tag);
        if (l == r) {
            leaf[l] = val;
            sgt[rt].minn = length(l) ? val : 0;
            return;
        }
        
        int mid = (l + r) / 2;
        down(lson, l, mid, val);
        down(rson, mid + 1, r, val);
        sgt[rt].minn = max(sgt[lson].minn, sgt[rson].minn);
    }

    int query(int rt, int ql, int qr, int l, int r) {
        if (ql > qr)
            return inf;
        if (ql <= l && qr >= r)
            return sgt[rt].minn;
        int mid = (l + r) / 2, res = ~inf;
        if (ql <= mid)
            res = max(res, query(lson, ql, qr, l, mid));
        if (qr > mid)
            res = max(res, query(rson, ql, qr, mid + 1, r));
        return res;
    }
}

void init() {
    cnts = nx = 0, ans = 1;
    memset(leaf, 0, sizeof(leaf));
}

void solve() {
    n = read();
    Q = read();
    A = read();
    init();
    for (int i = 1; i <= Q; i ++) {
        s[++cnts] = a[i].l = read();
        s[++cnts] = a[i].r = read();
        sx[++nx] = a[i].x = read();
    }
    sort(s + 1, s + cnts + 1);
    cnts = unique(s + 1, s + cnts + 1) - (s + 1);
    sort(sx + 1, sx + nx + 1);
    nx = unique(sx + 1, sx + nx + 1) - (sx + 1);
    s[cnts + 1] = n + 1;
    n = cnts << 1 | 1;

    sgtb::build(1, 1, n);
    for (int i = 1; i <= nx; i ++)
        q[i].clear(), v[i].clear();
    for (int i = 1; i <= Q; i ++) {
        a[i].l = (lower_bound(s + 1, s + cnts + 1, a[i].l) - s) << 1;
        a[i].r = (lower_bound(s + 1, s + cnts + 1, a[i].r) - s) << 1;
        a[i].x = lower_bound(sx + 1, sx + nx + 1, a[i].x) - sx;
        q[a[i].x].push_back(i);
        sgtb::cover(1, a[i].l, a[i].r, 1, n, a[i].x);
    }

    sgtb::down(1, 1, n, inf);
    for (int i = 1; i <= Q; i ++) {
        bool flag = false;
        flag |= (sgtb::query(1, a[i].l, a[i].r, 1, n) == a[i].x);
        if (!flag) {
            ans = 0;
            write(ans);
            return;
        }
    }
    
    for (int i = 1; i <= n; i ++) {
        if (leaf[i] < inf && length(i)) 
            v[leaf[i]].push_back(i);
        else if (length(i))
            ans = ans * fpow(A, length(i)) % mod;
    }
    for (int t = 1; t <= nx; t ++) {
        int nf = v[t].size(), p = -1;
        memset(f, 0, sizeof(int) * (nf + 5));
        sort(q[t].begin(), q[t].end(), cmp);

        f[0] = 1;
        sgta::build(1, 1, nf);
        for (int i = 1; i <= nf; i ++) {
            int len = length(v[t][i - 1]);
            int T = f[i - 1] * ((fpow(sx[t], len) - fpow(sx[t] - 1, len) + mod) % mod) % mod;

            int nxt = i != nf ? v[t][i] : inf, maxl = 0;
            while (p < (int)q[t].size() - 1 && a[q[t][p + 1]].r < nxt)
                ++p, maxl = max(maxl, a[q[t][p]].l);
            if (maxl) {
                auto it = lower_bound(v[t].begin(), v[t].end(), maxl);
                maxl = it - v[t].begin() + 1;
            }

            if (!maxl) {
                f[i] = f[i - 1] * fpow(sx[t], len) % mod;
            }
            else {
                f[i] = (T + fpow(sx[t] - 1, len) * sgta::query(1, maxl, i - 1, 1, nf) % mod) % mod;
                sgta::update(1, 1, maxl - 1, 1, nf, 0, 1);
            }
            sgta::update(1, i, i, 1, nf, T, 1);
            sgta::update(1, 1, i - 1, 1, nf, fpow(sx[t] - 1, len), 0);
        }
        ans = ans * f[nf] % mod;
    }
    write(ans);
    return;
}

signed main() {
#ifdef FILEIO
    freopen("count.in", "r", stdin);
    freopen("count.out", "w", stdout);
#endif

    Test = read();
    while (Test--)
        solve();
    return 0;
}
posted @ 2025-07-24 11:45  xAlec  阅读(17)  评论(0)    收藏  举报