CSU-ACM2025 暑假集训 训练赛1 题解

F - Diameter set

树的直径性质:所有直径的中点重合。(中点可以在顶点,也可以在边上)

可以考虑边转点,将 \(e(u,v)\) 转化为 \(e(u,x)\)\(e(x,v)\),此时所有直径的中点都在顶点上,便于处理。

设直径中心为根节点 \(root\),原树的直径长度为 \(len\),在新树上为直径一端到 \(root\) 的距离。

问题转化为,在树上选择一个大小大于等于 \(2\) 点集 \(S\),满足:若 \(x\in S\),则 \(x\)\(root\) 的距离为 \(len\);若 \(x\in S,y\in S\),则 \(x,y\) 的最近公共祖先为 \(root\)。求这样的点集的方案数。

\(v\in V\)\(root\) 的子节点,设 \(c_v\)\(v\) 子树中到 \(root\) 距离为 \(len\) 的顶点个数,设 \(s\)\(c\) 的总和。

每个子树至多选一个,再减去选一个和不选的方案数,答案为:

\[- s - 1 +\prod_{v\in V}(c_v+1) \]

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 4e5 + 5;
int n, tot;
vector<int> to[N];

int dis[N], pre[N];

void dfs(int k, int fa)
{
    dis[k] = dis[fa] + 1, pre[k] = fa;
    for(auto v : to[k])
    {
        if(v == fa) continue;
        dfs(v, k);
    }
}

int dep[N], sum[N];

void dfs2(int k, int fa)
{
    dep[k] = 1, sum[k] = 1;
    for(auto v : to[k])
    {
        if(v == fa) continue;
        dfs2(v, k);
        if(dep[v] + 1 > dep[k]) dep[k] = dep[v] + 1, sum[k] = sum[v];
        else if(dep[v] + 1 == dep[k]) sum[k] += sum[v];
    }
}

const ll mod = 998244353;

void solve()
{
    n = read(), tot = n;
    for(int i = 1; i < n; ++i)
    {
        int x = read(), y = read();
        ++tot;
        to[x].emplace_back(tot);
        to[tot].emplace_back(x);
        to[y].emplace_back(tot);
        to[tot].emplace_back(y);
    }
    dfs(1, 0);
    int p1 = 0;
    for(int i = 1; i <= tot; ++i)
        if(dis[i] > dis[p1]) p1 = i;
    for(int i = 1; i <= tot; ++i) dis[i] = 0, pre[i] = 0;
    dfs(p1, 0);
    int p2 = 0;
    for(int i = 1; i <= tot; ++i)
        if(dis[i] > dis[p2]) p2 = i;
    int lim = dis[p2] / 2, now = p2;
    while(lim--) now = pre[now];
    dfs2(now, 0);
    int s = 0;
    ll ans = 1;
    lim = dis[p2] / 2;
    for(auto v : to[now])
    {
        if(dep[v] == lim) s += sum[v], ans = ans * (sum[v] + 1) % mod;
    }
    ans = (ans - 1 - s + mod) % mod;
    printf("%lld\n", ans);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

F - Make Pair

\(dp_{l,r}\) 表示区间 \([l,r]\) 完全配对的方案数,转移时考虑枚举与 \(l\) 配对的 \(k\),此时 \([l,k],[k+1,r]\) 各自的配对顺序相互独立,有转移:

\[dp_{l,r}=\sum_{k}[l可以和k配对]dp_{l+1,k-1}\times dp_{k+1,r}\times \binom{(r-l+1)/2}{(k-l+1)/2} \]

\(k=l+1\) 或者 \(k=r\) 时特殊处理。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 405;
const ll mod = 998244353;
int n, m;
int vis[N][N];
ll jc[N], inv[N];
ll qpow(ll a, ll b, ll mod)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ans;
}

void init()
{
    jc[0] = jc[1] = inv[0] = inv[1] = 1;
    for(int i = 2; i <= 400; ++i) jc[i] = jc[i - 1] * i % mod;
    inv[400] = qpow(jc[400], mod - 2, mod);
    for(int i = 399; i >= 2; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
}

ll C(int n, int m)
{
    if(n < 0 || m < 0 || n < m) return 0;
    return jc[n] * inv[m] % mod * inv[n - m] % mod;
}

ll dp[N][N];

void solve()
{
    n = read(), m = read();
    for(int i = 1; i <= m; ++i)
    {
        int a = read(), b = read();
        vis[a][b] = 1;
    }
    for(int i = 1; i < n + n; ++i) if(vis[i][i + 1]) dp[i][i + 1] = 1;
    for(int len = 4; len <= n + n; len += 2)
        for(int l = 1, r = len; r <= n + n; ++l, ++r)
        {
            if(vis[l][r]) dp[l][r] = dp[l + 1][r - 1];
            if(vis[l][l + 1]) dp[l][r] += dp[l + 2][r] * ((r - l - 1) / 2 + 1) % mod;
            for(int k = l + 3; k < r; k += 2)
                if(vis[l][k]) dp[l][r] += dp[l + 1][k - 1] * dp[k + 1][r] % mod * C((r - l + 1) / 2, (r - k) / 2) % mod;
            dp[l][r] %= mod;
        }
    printf("%lld\n", dp[1][n + n]);
}

int main()
{
    init();
    int T = 1;
    while(T--) solve();
    return 0;
}

G - Groups

DP做法:发现类似斯特林数定义,用斯特林数的递推:设 \(dp_{i,j}\) 表示将前 \(i\) 个数分成 \(j\) 组的方案数。
转移时考虑新加入一个数,放在已有的组还是新组,已有的组中有 \(\lfloor\frac{i-1}{m}\rfloor\) 个组不能选。

\[dp_{i,j}=(j-\lfloor\frac{i-1}{m}\rfloor)\times dp_{i-1,j}+dp_{i-1,j-1} \]

组合意义-二项式反演做法:设 \(cnt_i\) 表示 \([1,n]\) 中模 \(m\)\(i\) 的数的个数,设 \(f(k)\) 表示分成 \(k\) 组,组与组之间有序可为空,设 \(g(k)\) 表示分成 \(k\) 组,组与组之间有序不可为空,则有:

\[f(k)=\prod_{i=0}^{m-1}\binom{k}{cnt_i}\times (cnt_i)!=\sum_{i=1}^{k}\binom{k}{i}g(i) \]

由二项式反演得:

\[g(k)=\sum_{i=1}^{k}(-1)^{k-i}\binom{k}{i}f(i) \]

\(f(k)\)\(g(k)\) 都可以在 \(O(n^2)\) 求出。由于 \(g(k)\) 为组与组之间有序的方案数,最终答案要除以 \(k!\)

优化:注意到 \(cnt_i\) 的取值至多两种,所以 \(f(k)\) 可以在 \(O(n\log n)\) 的时间内求出(快速幂),\(g(k)\) 可以使用NTT求解,复杂度为 \(O(n\log n)\)

DP做法:

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 5005;
const ll mod = 998244353;
int n, m;

ll dp[N][N];

void solve()
{
    int n = read(), m = read();
    dp[0][0] = 1;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            dp[i][j] = (dp[i - 1][j - 1] + 1ll * (j - (i - 1) / m) * dp[i - 1][j] % mod + mod) % mod;
    for(int i = 1; i <= n; ++i) printf("%lld\n", dp[n][i]);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

二项式反演做法:

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 5005;
const ll mod = 998244353;
int n, m;
int vis[N][N];
ll jc[N], inv[N];
ll qpow(ll a, ll b, ll mod)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ans;
}

void init()
{
    jc[0] = jc[1] = inv[0] = inv[1] = 1;
    for(int i = 2; i <= 5001; ++i) jc[i] = jc[i - 1] * i % mod;
    inv[5001] = qpow(jc[5001], mod - 2, mod);
    for(int i = 5000; i >= 2; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
}

ll C(int n, int m)
{
    if(n < 0 || m < 0 || n < m) return 0;
    return jc[n] * inv[m] % mod * inv[n - m] % mod;
}

ll f[N], g[N];

void solve()
{
    int n = read(), m = read();
    int cnt = n / m;
    int x = n - cnt * m, y = m - x; //x个cnt+1,y个cnt
    for(int k = cnt; k <= n; ++k)
    {
        f[k] = qpow(C(k, cnt) * jc[cnt] % mod, y, mod) * qpow(C(k, cnt + 1) * jc[cnt + 1] % mod, x, mod) % mod;
    }
    for(int k = 1; k <= n; ++k)
    {   
        for(int i = 1; i <= k; ++i)
        {
            if((k - i) & 1) g[k] -= C(k, i) * f[i] % mod;
            else g[k] += C(k, i) * f[i] % mod;
        }
        printf("%lld\n", (g[k] % mod + mod) % mod * inv[k] % mod);
    }
}

int main()
{
    init();
    int T = 1;
    while(T--) solve();
    return 0;
}

G - 222

注意到 \(A_i=\frac{2}{9}(10^i-1)\),要求最小的 \(i\) 使得 \(k\mid \frac{2}{9}(10^i-1)\),即 \(\frac{2}{9}(10^i-1)\equiv 0\bmod k\)

引理一:若 \(ax\equiv0\bmod b\),则 \(x\equiv0\bmod \frac{b}{\gcd(a,b)}\)
引理二:若 \(\frac{x}{a}\equiv 0\bmod b\),则 \(x\equiv 0\bmod ab\)

\(m=\frac{9k}{\gcd(9k,2)}\)

于是可以将问题转化为求最小的 \(i\) 使得 \(10^i\equiv 1\bmod m\)

引理三:若 \(a^x\equiv 1\bmod b\),则 \(\gcd(a,b)=1\)

所以当 \(\gcd(10,m) \neq 1\) 时,无解。

否则,由于 \(\gcd(10,m)=1\),所以有 \(10^{\varphi(m)}\equiv 1\bmod m\)

那么最小整数解一定是 \(\varphi(m)\) 的约数,枚举约数判断。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 100;
int p[N], tot;

ll qpow(ll a, ll b, ll mod)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ans;
}

void solve()
{
    tot = 0;
    int K = read();
    if(K & 1) K *= 2;
    K = K / 2 * 9;
    int k = K;
    for(int i = 2; i * i <= k; ++i)
    {
        if(k % i == 0)
        {
            p[++tot] = i;
            while(k % i == 0) k /= i;
        }
    }
    if(k > 1) p[++tot] = k;
    int phi = K;
    for(int i = 1; i <= tot; ++i) phi = phi / p[i] * (p[i] - 1);
    int ans = 0x3f3f3f3f;
    for(int i = 1; i * i <= phi; ++i)
    {
        if(phi % i == 0)
        {
            if(qpow(10, i, K) == 1) ans = min(ans, i);
            if(qpow(10, phi / i, K) == 1) ans = min(ans, phi / i);
        }
    }
    if(ans == 0x3f3f3f3f) printf("-1\n");
    else printf("%d\n", ans);
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

Problem - 632F - Codeforces

简要题意:
给定 \(n\times n\) 的矩阵 \(a\),判断是否满足以下条件:

  1. \(a_{i,i}=0\).
  2. \(a_{i,j}=a_{j,i}\)
  3. \(a_{i,j}\le \max\{a_{i,k},a_{k,j}\}\)
    \(1\le n\le 2500\)

题解:
条件一二容易判断,考虑条件三的意义:建边 \((i,j,a_{i,j})\),对于任意的 \(i,j\),都没有 \(k\) 使得 \(k\) 与两个点的距离都小于 \(i,j\) 的距离。

注意到条件三可以扩展:\(a_{i,j}\le \max\{a_{i,k},a_{k,j}\}\le \max\{a_{i,k},\max\{a_{k,l},a_{l,j}\}\}=\max\{a_{i,k},a_{k,l},a_{l,j}\}\)
在图上就是,\(a_{i,j}\)\(i\) 到的 \(j\) 的最短路径。

考虑 \(kruskal\) 求最小生成树的结论:可能成为最小生成树的边的边集中,相同边权的边将图分为相同的联通块

在本题中就是,每条边都可能成为最小生成树的边,在只考虑 \(<a_{i,j}\) 的边时,\(i\)\(j\) 不联通。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2505;
int n;
int a[N][N];

struct Edge
{
    int i, j, v;
    Edge(){ i = j = v = 0; }
    Edge(int i, int j, int v): i(i), j(j), v(v){}
    bool friend operator < (const Edge &a, const Edge &b)
    {
        return a.v < b.v ;
    }
}E[N * N];

int tot;

int f[N];

int find(int x){ return (x == f[x]) ? x : (f[x] = find(f[x])); }

void merge(int x, int y)
{
    x = find(x), y = find(y);
    if(x == y) return ;
    f[y] = x;
}

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= n; ++j)
            a[i][j] = read();
    for(int i = 1; i <= n; ++i)
    {
        if(a[i][i] != 0)
        {
            printf("NOT MAGIC\n");
            return ;
        }
    }
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= n; ++j)
            if(a[i][j] != a[j][i])
            {
                printf("NOT MAGIC\n");
                return ;
            }
    for(int i = 1; i <= n; ++i) f[i] = i;
    for(int i = 1; i <= n; ++i)
        for(int j = i + 1; j <= n; ++j)
            E[++tot] = (Edge){i, j, a[i][j]};
    sort(E + 1, E + tot + 1);
    int last = 0;
    for(int i = 1; i <= tot; ++i)
    {
        if(E[i].v != E[i - 1].v )
        {
            for(int j = last + 1; j < i; ++j) merge(E[j].i , E[j].j );
            last = i - 1;
        }
        int x = find(E[i].i ), y = find(E[i].j );
        if(x == y)
        {
            printf("NOT MAGIC\n");
            return ;
        }
    }
    printf("MAGIC\n");
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

G - Power Pair

\(x=0\) 时,只有 \(y=0\) 可行;当 \(y=0\) 时,只有 \(x=0\) 可行,以下讨论默认 \(1\le x\le P-1,1\le y\le P-1\)

前置知识:原根 - OI Wiki

简化版前置知识:

  • 素数 \(P\) 一定存在原根 \(g\)
  • \(g,g^2,g^3\cdots,g^{P-1}\) 在模 \(P\) 意义下恰好取遍 \(1\sim P-1\)
  • \(g^a\equiv g^b\bmod P\),则 \(a\equiv b\bmod P-1\)

回到本题:(本题不需要求原根,只使用了原根的性质)

考虑 \(P\) 的原根 \(g\)\(g^1,g^2\cdots,g^{P-1}\) 取遍 \(1\sim P-1\),设 \(x=g^a,y=g^b\),则要满足 \(g^{an}\equiv g^{b}\bmod P\),等价于 \(an\equiv b\bmod P-1\)

枚举 \(a\),由裴蜀定理可知,应有 \(b\mid gcd(a,P-1)\),答案为

\[\sum_{a=1}^{P-1}\frac{P-1}{gcd(a, P-1)} \]

枚举 \(gcd(a,P-1)=d\),设 \(f(d)=\sum_{a}[gcd(a,P-1)=d]\),答案为

\[\sum_{d}\frac{P-1}{d}f(d) \]

求解 \(f(d)\):设 \(g(d)=\sum_{a}[gcd(a,P-1)\mid d]=\frac{P-1}{d}\)

则有 \(g(d)=f(d)+f(2d)+\cdots\)
倒序 \(O((P-1的约数个数)^2)\) 容斥即可。
\(10^{12}\) 至多约有 \(7000\) 约数。

进一步化简:\(f(d)=\sum_{a}[gcd(a,P-1)=d]=\sum_{a}[gcd(a,\frac{P-1}{d})=1]=\varphi(\frac{P-1}{d})\)

每次 \(O(\sqrt{d})\) 求解 \(\varphi\) 函数也可以通过。

理论上存在将 \(P-1\) 质因子分解后 \(DFS\) 枚举约数,同时求出约数的 \(\varphi\) 函数,将复杂度降到 \(O(约数个数)\)

容斥做法
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const ll mod = 998244353;
ll P;
vector<ll> d, f, g;

void solve()
{   
    P = read();
    ll t = sqrtl(P - 1);
    for(ll i = 1; i <= t; ++i)
    {
        if((P - 1) % i == 0)
        {
            d.emplace_back(i);
            if((P - 1) / i != i) d.emplace_back((P - 1) / i);
        }
    }
    sort(d.begin(), d.end());
    f.resize(d.size()), g.resize(d.size());
    for(int i = 0; i < d.size(); ++i) g[i] = (P - 1) / d[i];
    for(int i = (int)(d.size()) - 1; i >= 0; --i)
    {
        f[i] = g[i];
        for(int j = i + 1; j < d.size(); ++j)
            if(d[j] % d[i] == 0) f[i] -= f[j];
        f[i] %= mod;
    }
    ll ans = 0;
    for(int i = 0; i < d.size(); ++i) ans += (P - 1) / d[i] % mod * f[i] % mod;
    printf("%lld\n", (ans + 1) % mod);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

Problem - 526F - Codeforces

学习分治必做的经典好题。

由于每行每列恰好有一个怪兽,设 \(p_i\) 为第 \(i\) 行的怪兽所处的列数。

考虑第 \(l\) 行到第 \(r\) 行的怪兽合法的条件:

\[(\max_{l\le k\le r}p_k) - (\min_{l\le k\le r}p_k)=r-l \]

即区间极差等于区间长度-1

由于 \(max/min\) 函数不满足消去律,考虑分治,从 \(mid\) 位置向两端扩展。

设当前分治区间为 \([l,r]\),只考虑跨过 \([mid,mid+1]\) 的区间是否合法。

\(mxl[i]\) 表示 \([i,mid]\) 的最大值,\(mnl[i]\) 表示 \([i,mid]\) 的最小值,\(mxr[i]\) 表示 \([mid+1,i]\) 的最大值,\(mnr[i]\) 表示 \([mid+1,i]\) 的最小值。

合法区间 \([l',r']\) 一定属于四种情况之一:

  • 最大值,最小值都在 \([l',mid]\)
  • 最大值,最小值都在 \([mid+1,r']\)
  • 最大值在 \([l',mid]\),最小值在 \([mid+1,r']\)
  • 最小值在 \([l',mid]\),最大值在 \([mid+1,r']\)

case 1:最大值,最小值都在 \([l',mid]\)

枚举 \(l'\in [l,mid]\),极差为 \(mxl[l']-mnl[l']\),合法右端点 \(r'\) 应满足 :

  • \(r'\ge mid+1\)
  • \(mxl[l']-mnl[l']=r'-l'\)
  • \(mxr[r']<mxl[l']\)
  • \(mnr[r']>mnl[l']\)

可以 \(O(1)\) 判断,case 2 同理。

case 3:最大值在 \([l',mid]\),最小值在 \([mid+1,r']\)

枚举 \(l'\in [l',mid]\),合法右端点 \(r'\) 应满足:

  • \(r'\ge mid+1\)
  • \(mxl[l']-mnr[r']=r'-l'\),即 \(mxl[l']+l'=mnr[r']+r'\)
  • \(mxl[l']>mxr[r']\)
  • \(mnl[l']>mnr[r']\)

当固定 \(l'\) 时,满足条件:\(mxl[l']>mxr[r']\)\(r'\)\([mid+1,r]\) 的一段前缀;
满足条件 \(mnl[l']>mnr[r']\)\(r'\)\([mid+1,r]\) 的一段后缀,且随着 \(l'\) 减小单调移动,可以用两个指针记录并维护区间交,用桶记录值为 \(mnr[l']+l'\)\(r'\) 的个数。case 4 同理。

由于求解 \([l,r]\) 的答案的复杂度为 \(O(r-l+1)\),分治有 \(\log n\) 层,总复杂度为 \(O(n\log n)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define re register 
#define ll long long 
inline int read()
{
    int x = 0, f = 0; char c = getchar();
    while (c < '0') f |= (c == '-'), c = getchar();
    while (c >= '0') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 3e5 + 10;
int n, a[N], maxx[N], minn[N];
ll ans;
int cnt[N << 2];

void solve(int l, int r)
{
    if(l == r) { ++ans; return ; }
    int mid = (l + r) >> 1;
    solve(l, mid), solve(mid + 1, r);
    maxx[mid] = minn[mid] = a[mid];
    maxx[mid + 1] = minn[mid + 1] = a[mid + 1];
    for (re int i = mid - 1; i >= l; --i)
    {
        maxx[i] = max(a[i], maxx[i + 1]);
        minn[i] = min(a[i], minn[i + 1]);
    }
    for (re int i = mid + 2; i <= r; ++i)
    {
        maxx[i] = max(a[i], maxx[i - 1]);
        minn[i] = min(a[i], minn[i - 1]);
    }
    // 需要找的区间是,r - l == maxx - minn
    // 转化为r = maxx - minn + l或者l = r - maxx + minn
    
    // 最大值最小值都在左边
    int R = mid;
    for (re int i = mid; i >= l; --i)
    {
        while (R < r && maxx[R + 1] < maxx[i] && minn[R + 1] > minn[i]) ++R;
        if(maxx[i] - minn[i] + i >= mid + 1 && maxx[i] - minn[i] + i <= R) ++ans;
    }
    // 最大值最小值都在右边
    int L = mid + 1;
    for (re int i = mid + 1; i <= r; ++i)
    {
        while (L > l && maxx[L - 1] < maxx[i] && minn[L - 1] > minn[i]) --L;
        if(i - maxx[i] + minn[i] >= L && i - maxx[i] + minn[i] <= mid) ++ans;
    }
    // 最大值在左边,最小值在右边
    // r - l == maxx - minn --> l + maxx = r + minn
    // 开一桶记录右边每一个点的r + minn,然后指针控制满足要求的区间
    L = mid + 1, R = mid;
    for (re int i = mid; i >= l; --i)
    {
        while (R < r && maxx[R + 1] < maxx[i]) ++R, ++cnt[R + minn[R]];
        while (L <= R && minn[L] > minn[i]) --cnt[L + minn[L]], ++L;
        ans += cnt[i + maxx[i]];
    }
    while (L <= R) --cnt[L + minn[L]], ++L;
    // 最小值在左边,最大值在右边
    // r - l == maxx - minn --> l - minn = r - maxx
    // 开桶记录右边每一个点的r - maxx
    L = mid + 1, R = mid;
    for (re int i = mid; i >= l; --i)
    {
        while (R < r && minn[R + 1] > minn[i]) ++R, ++cnt[R - maxx[R] + n];
        while (L <= R && maxx[L] < maxx[i]) --cnt[L - maxx[L] + n], ++L;
        ans += cnt[i - minn[i] + n]; 
    }
    while (L <= R) --cnt[L - maxx[L] + n], ++L;
}

int main()
{
    n = read();
    for (re int i = 1; i <= n; ++i)
    {
        int x = read(), y = read();
        a[x] = y;
    }
    solve(1, n);
    printf ("%lld\n", ans);
    return 0;
}
posted @ 2025-08-19 13:03  梨愁浅浅  阅读(65)  评论(0)    收藏  举报