模拟赛 R23

T1 - 星际、干涉、融解、轮回、邂逅、再生、安魂曲

题目描述

给定正整数 \(n\),求有多少三元组 \((a,b,c)\) 满足:

  • \(a+b+c=n\)
  • \(1\le a<b<c\le n\)
  • \(a\)\(b\) 的因数,\(b\)\(c\) 的因数

\(n \le 10^9\)

Solution

枚举 \(a\),显然 \(a | n\)。设 \(b = xa, c = xya\),那么 \(x \times (y + 1) = \frac{n}{a} - 1\)。此时我们再枚举一遍 \(x\) 即可,注意一些 corner 因为 \(x > 1\)\(y > 1\)。约数个数在 \(n\) 很大时有更加精确的上界 \(n^{\frac{1}{3}}\)。因此直接枚举复杂度不会爆,当然线性筛筛出来然后单次 \(O(\frac{\sqrt n}{\ln n})\) 也无所谓。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
bitset<N> bs;
int pri[N], cnt;
int num(int T){
    if(T <= 2) return 0;
    int tmp = -2 - !(T & 1), ret = 1;
    for(int i = 1; i <= cnt; ++i){
        if(T % pri[i] == 0){
            int cnt = 0;
            while(T % pri[i] == 0){
                T /= pri[i];
                ++cnt;
            }
            ret *= (cnt + 1);
        }
    }
    if(T != 1) ret *= 2;
    return ret + tmp;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    for(int i = 2; i <= 5e4; ++i){
        if(!bs[i]) pri[++cnt] = i;
        for(int j = 1; j <= cnt; ++j){
            if(i * pri[j] > 5e4) break;
            bs[i * pri[j]] = 1;
            if(i % pri[j] == 0) break;
        }
    }
    int n; cin >> n;
    int ans = 0;
    for(int i = 1; i <= n / i; ++i){
        if(n % i != 0) continue; 
        ans += num(n / i - 1);
        if(i != n / i){
            ans += num(i - 1);
        }
    }
    cout << ans;
    return 0;
}

T2 - 临界、自毁、客体、重生、而你、又在、何处呢

题目描述

二维平面上有 \(n\) 个点 \((x,y)\),且 \(1\le x,y\le N\),每个点是黑色或白色。

对于一个矩形(可以退化为线段或单点),定义其权值为其中黑点个数与白点个数的乘积。

现在让矩形的上下和左右边界取遍 \(1\sim N\),求所有 \(\left(\frac{N(N+1)}{2}\right)^2\) 个矩形的权值和。

答案对 \(2^{64}\) 取模。

\(n \le 2 \times 10^5, N \le 10^9\)

Solution

考虑一对黑点和白点何时会被几个矩形统计到。假设黑点是 \((x, y)\),白点是 \((x', y')\)。那么就会被 \(min(x', x) \times (N - \max(x', x) + 1) \times min(y, y') \times (N - \max(y, y'))\) 统计到。然后分讨做二位数点即可。
赛后才做出一个更劣解,对 \(y\) 轴扫描线,令 \(f_i\) 表示 \(xl \le x \land xr \ge x\) 的所有矩形内黑点的数量之和。这样我们在黑点处加贡献,白点处计算即可,随后是一段区间加,维护 \(ki + b\) 状物。本质相同的。

Code
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 2e5 + 5, M = N << 2;
int n, V, tot;
ull a[N];
struct BIT{
    ull tr[M];
    int lowbit(int x){ return x & (-x); }
    void clear(){ memset(tr, 0, sizeof(tr)); }
    void add(int x, ull k){
        for(int i = x; i <= tot; i += lowbit(i))
            tr[i] += k;
    }
    ull qry(int x){
        ull res = 0;
        for(int i = x; i; i -= lowbit(i))
            res += tr[i];
        return res;
    }
}k, b;
int get(int x){ return lower_bound(a + 1, a + 1 + tot, x) - a; }
void add(int L, int R, ull kd, ull bd){
    k.add(L, kd), k.add(R + 1, -kd);
    b.add(L, bd), b.add(R + 1, -bd);
}
ull qry(int x){
    return a[x] * k.qry(x) + b.qry(x);
}
map<int, vector<pii> > mp;
int main(){
    // freopen("peace5.in", "r", stdin);
    // freopen(".out", "w", stdout);
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> V;
    for(int i = 1; i <= n; ++i){
        int x, y, c;
        cin >> x >> y >> c;
        mp[y].emplace_back(c, x);
        a[++tot] = x;
    }
    a[++tot] = 1, a[++tot] = 1e9 + 1;
    sort(a + 1, a + 1 + tot);
    tot = unique(a + 1, a + 1 + tot) - a - 1; 
    ull ans = 0;
    for(auto it = mp.begin(); it != mp.end(); ++it){ // le y
        auto &[y, p] = *it;
        sort(p.begin(), p.end(), greater<pii>());
        for(auto [c, x] : p){
            x = get(x);
            // cout << x << ' ' << a[x] << ' ' << y << '\n';
            if(!c) ans += (V - y + 1) * qry(x);
            else{
                add(1, x, (V - a[x] + 1) * y, 0);
                add(x + 1, tot, -a[x] * y, ull(V + 1) * a[x] * y);
            }
        }
        // cout << y << ' ' << ans << '\n';
    }
    k.clear(), b.clear();
    for(auto it = mp.rbegin(); it != mp.rend(); ++it){ // > y
        auto &[y, p] = *it;
        sort(p.begin(), p.end());
        for(auto [c, x] : p){
            x = get(x);
            if(!c) ans += y * qry(x);
            else{
                add(1, x, (V - a[x] + 1) * (V - y + 1), 0);
                add(x + 1, tot, -a[x] * (V - y + 1), ull(V + 1) * a[x] * (V - y + 1));
            }
        }
        // cout << y << ' ' << ans << '\n';
    }
    cout << ans;
    return 0;
}

T3 - 向我施展咒語吧 神明大人!

题目描述

\(n\) 个区间 \([l_i,r_i]\),其中 \(1\le l_i<r_i\le m\)

现在会进行若干次切割操作,每次会给出 \(x,s,t\),表示对 \(s\le i\le t\) 的区间 \([l_i,r_i]\)\(x\) 处切割,保留较长的一段,两段相等时保留左侧的。具体的:

  • 如果 \(x< l_i\) 或者 \(x> r_i\),则无事发生。
  • 如果 \(r_i\ge x\ge \frac{l_i+r_i}{2}\),则令 \(r_i\leftarrow x\)
  • 如果 \(l_i\le x<\frac{l_i+r_i}{2}\),则令 \(l_i\leftarrow x\)

求所有切割操作结束后,每个区间的左右端点。保证所有切割操作的位置 \(x\) 恰好构成 \(1\sim m\) 的排列。

\(n \le 10^5, m \le 10^6\)

Solution

由于切割位置形成一个排列,那么记 \(p_i\) 表示切 \(i\) 这一刀是第几个操作,如果我们想知道一个区间第一次被切的那一刀,直接查询 \(\min p_{l\cdots r}\) 即可。我们直接模拟这个过程,可以获得一些分数。
现在的问题是对于每个线段,切割的次数有可能很大,可能卡到 \(O(n)\) 级别。
我们对每个线段三等分,我们发现当切割位置落在左边/右边时,我们很容易地判断应该丢掉哪边。这是由于中间的长度总是不小于两边的长度。而对于切到中间的操作,我们发现这至少会使整个线段长度 \(\times \frac{2}{3}\)。因此对于每一轮操作,我们先找到中间那一段第一次被切的一刀 \(i\),然后向两边找。找到左边最右的那个 \(j\),满足 \(p_j < p_i\),和右边最左的那个。这样我们就知道在切 \(i\) 之前,整个线段的形态了,然后直接模拟切 \(i\) 这刀会丢到哪边。这样的轮数是不会超过 \(O(\log m)\) 的。
对于 \((s, t)\) 的限制,我们对线段编号一维扫面线即可。这样要维护的就是单点修改,区间最小值和线段树二分。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef tuple<int, int, int> tpi;
typedef pair<int, int> pii;
int n, m, l[N], r[N], id, inf;
vector<pii> op[N];
struct Seg{
    int tr[N << 2];
    #define ls(p) p << 1
    #define rs(p) p << 1 | 1
    void upd(int x, int k, int p = 1, int pl = 1, int pr = m){
        if(pl == pr) return tr[p] = k, void();
        int mid = (pl + pr) >> 1;
        if(x <= mid) upd(x, k, ls(p), pl, mid);
        else upd(x, k, rs(p), mid + 1, pr);
        tr[p] = min(tr[ls(p)], tr[rs(p)]); 
    } 
    vector<tpi> rge;
    void get(int L, int R, int p = 1, int pl = 1, int pr = m){
        if(L <= pl && R >= pr) return rge.emplace_back(p, pl, pr), void();
        int mid = (pl + pr) >> 1;
        if(L <= mid) get(L, R, ls(p), pl, mid);
        if(R > mid) get(L, R, rs(p), mid + 1, pr);
    }
    pii qry(int L, int R){
        rge.clear(); get(L, R);
        int mn = inf;
        for(auto [p, _, __] : rge) mn = min(mn, tr[p]);
        if(mn == inf) return {-1, mn};
        for(auto [p, pl, pr] : rge){
            if(tr[p] == mn){
                while(pl != pr){
                    int mid = (pl + pr) >> 1;
                    if(tr[ls(p)] == mn) p = ls(p), pr = mid;
                    else p = rs(p), pl = mid + 1;
                }       
                return {pl, mn};
            }
        }
    }
    int find_last(int L, int R, int x){
        rge.clear(); get(L, R);
        for(int i = rge.size() - 1; i >= 0; --i){
            auto [p, pl, pr] = rge[i];
            if(tr[p] < x){
                while(pl != pr){
                    int mid = (pl + pr) >> 1;
                    if(tr[rs(p)] < x) pl = mid + 1, p = rs(p);
                    else pr = mid, p = ls(p);
                }
                return pl;
            }
        }
        return L;
    }
    int find_first(int L, int R, int x){
        rge.clear(); get(L, R);
        for(auto [p, pl, pr] : rge){
            if(tr[p] < x){
                while(pl != pr){
                    int mid = (pl + pr) >> 1;
                    if(tr[ls(p)] < x) pr = mid, p = ls(p);
                    else pl = mid + 1, p = rs(p);
                }
                return pl;
            }
        }
        return R;
    }
}tr;
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> m >> id;
    memset(tr.tr, 0x3f, sizeof(tr.tr));
    inf = tr.tr[0];
    for(int i = 1; i <= n; ++i) cin >> l[i] >> r[i];
    for(int i = 1; i <= m; ++i){
        int x, L, R; 
        cin >> x >> L >> R;
        op[L].emplace_back(x, i);
        op[R + 1].emplace_back(x, inf);
    }
    for(int i = 1; i <= n; ++i){
        for(auto [x, k] : op[i]) tr.upd(x, k);
        int pl = l[i], pr = r[i];
        while(pl + 1 != pr){
            int ml = pl + (pr - pl + 1) / 3, mr = pr - (pr - pl + 1) / 3;
            auto [k, v] = tr.qry(ml, mr);
            pl = tr.find_last(pl, ml, v);
            pr = tr.find_first(mr, pr, v);
            int mid = (pl + pr + 1) >> 1;
            if(k == -1) break;
            if(k >= mid) pr = k;
            else pl = k;
        }
        cout << pl << ' ' << pr << '\n';
    }
    return 0;
}

T4 - 最后看到的是那样的梦

题目描述

有一张 \(n\) 个点的无向图,初始图中没有任何边。

接下来不断重复以下操作:

  • \(\deg_i\) 为第 \(i\) 个点当前的度数(即有多少个点和它有直接连边),从所有满足 \(\deg_u,\deg_v\) 中至少有一个是 \(0\) 的点对 \((u,v)\) 中等概率选取一个(例如,假设当前有 \(k\) 个点的度数非零,则一共有 \(\binom{n}{2}-\binom{k}{2}\) 种可能选取的点对),然后加一条边 \((u,v)\)

最终无法进行操作,即不存在孤立点时停止操作。

给定 \(m\),求恰好 \(m\) 次操作后停止的概率,答案对给定的质数 \(P\) 取模。

$ n \le 10^7$

Solution

首先,可以对原题意转化。我们称一次选的两个点一个是非孤立点,一个是孤立点的为一操作,选的两个点都是孤立点的是二操作,那么最终操作的次数就是 \(n\) - 二操作的次数。现在我们可以随机一个长度为 \(n \choose 2\) 的所有边的排列。如果我们碰到一个不合法的边,就跳过。否则就执行该操作。不难发现,这与原题意等价,因为接下来每条合法的边第一次出现的概率都是一样的。
现在令 \(k = n - m\),我们要求 \(({n \choose 2})!\) 种排列中恰好执行 \(k\) 次 2 操作的概率。先容斥一下,接下来我们求至少有 \(x\) 次 2 操作的概率。
先把这 \(x\) 条边选出来,是 \({n \choose 2x}(2x - 1)!!\)。其中涉及到一个经典组合,给定 \(2x\) 个点,求他们两两配对成 \(x\) 条边的方案数(不计边集内的顺序)。那么第一个点有 \(2x - 1\) 个点可以与之配对,剩下 \(2x - 2\) 个点中编号最小的点又有 \(2x - 3\) 个可以跟他配对,最后就是 \((2x - 1)(2x - 3) \cdots 1 := (2x - 1)!!\)
然后发现变成了一个拓扑序计数的问题。我们先为选出来的这 \(x\) 条边强行钦定一个拓扑序(也就是乘一个 \(x!\))。然后限制就是所有与它相邻的边都不能在它之前出现。不妨设这 \(x\) 条边的拓扑序是 \((0, 1)\to(2, 3) \cdots \to(2x - 2, 2x - 1)\)。对于一条边 \((i, j)\),如果两个点都不在这 \(2x\) 点之内,那显然对他们没有要求。否则,我们找到 \((i, i \oplus 1)\)\((j, j \oplus 1)\) 更靠后的那一个(如果不在 \(2x\) 点之内就选另一个)作为父亲。发现对于这 \(2x\) 个点和它所有相邻的边,形成了一棵树的结构。这棵树的主干就是这 \(x\) 条边,每个点又附着一些别的边,而除了这主干上的 \(x\) 条边,别的都是叶子节点。
为了计数树的拓扑序,我们研究一下子树大小。与 \((2x - 2, 2x - 1)\) 相邻的边有 \(2(n - 2)\),同理与 \((2x - 4, 2x - 3)\) 相邻但不与 \((2x - 2, 2x - 1)\) 相邻的边有 \(2(n - 4)\)。以此类推,从下至上第 \(i\) 个点的子树大小就是 \(i + \sum_{j \le i} 2(n - 2j) = i + 2ni - 2i \times (i + 1) = i(2n - 2i - 1)\)
到这就可以开始算了,拓扑序个数就是

\[({n \choose 2})!\prod_{i = 1}^{x} \frac{1}{i(2n - 2i - 1)} = \frac{({n \choose 2})!}{x!}\prod_{i = 1}^{x} \frac{1}{(2n - 2i - 1)} = \frac{({n \choose 2})!(2n - 2x - 3)!!}{x!(2n - 3)!!} \]

最后算概率,总的排列个数除掉了,然后别忘了把容斥加上。

\[Ans = \sum_{x = k}^{\lfloor \frac{n}{2} \rfloor}(-1)^{x - k}{x \choose k}{n \choose 2x}(2x - 1)!!x!\frac{(2n - 2x - 3)!!}{x!(2n - 3)!!} \]

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e7 + 5;
typedef long long ll;
int n, m, mod;
int inv[N], fac[N], ifac[N], ffac[N], iffac[N];
int C(int n, int m){
    if(m > n) return 0;
    return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
void add(ll &x, ll y){ (x += y) %= mod; }
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> m >> mod;
    inv[1] = 1;
    for(int i = 2; i <= 2e7; ++i) inv[i] = (1ll * (mod - mod / i) * inv[mod % i]) % mod;
    fac[0] = ifac[0] = 1;
    for(int i = 1; i <= 2e7; ++i) fac[i] = (1ll * fac[i - 1] * i) % mod, ifac[i] = (1ll * ifac[i - 1] * inv[i]) % mod;
    ffac[0] = ffac[1] = iffac[0] = iffac[1] = 1;
    for(int i = 2; i <= 2e7; ++i) ffac[i] = (1ll * ffac[i - 2] * i) % mod, iffac[i] = (1ll * iffac[i - 2] * inv[i]) % mod;
    int k = n - m;
    ll ans = 0;
    for(int x = k; x <= n / 2; ++x){
        ll p = 1ll * ((x - k) % 2 ? mod - 1 : 1) * C(x, k) % mod;
        add(ans, p * C(n, 2 * x) % mod * ffac[2 * x - 1] % mod * ffac[2 * n - 3 - 2 * x] % mod * iffac[2 * n - 3] % mod);
    }
    cout << ans;
    return 0;
}

Summary

赶紧恢复一下状态啊,这个 T1 做 1h,T2 想得太混乱了。状态不好时以部分分为主。

posted @ 2025-11-12 09:41  Hengsber  阅读(13)  评论(0)    收藏  举报