Mercenaries

思路

今天时间剩的不多, 还是看看得了
发现听过某个巨佬讲这道题, 可惜忘了

你发现约束条件数 m 很小啊, 容易想到状压, 但这是后事了
先考虑一下有没有什么符合直觉的做法, 你发现他求 n 个元素的子集? 这我写鸡毛啊
算了反正状态不好, 复习一下回寝了
下一次写这个题还要等到考完试补完题, 很蓝的啦


考完复活了

简化题意

n 个人和 m 对敌对关系, 每一个人有一个条件区间 [li,ri]
现在要在这 n 个人中选 若干个人 , 定义一个合法的选择 {S} , 当且仅当对于 {S} 中的所有人 i , li|S|ri, 且 {S} 中所有人互不敌对
求有多少种合法的选择

因为求 n3×105 这个的所有子集显然不可能, 考虑有没有什么好的性质可以避开
先考虑互不敌对这个条件, 你发现因为敌对数很少啊, 所以互不敌对的子集其实是很多的, 我们考虑正难则反, 考虑敌对子集
这个是简单的, 我们可以记录满足了哪些敌对状态, 仅仅只有 2201 个状态
知道了满足了那些敌对状态, 那么就知道必须要有哪些人出现, 然后其他人任选

好那么进入正题, 你发现给定「满足敌对状态」, 我们只能「钦定」这些人出现, 也就是说我们只能找到「至少」出现了 k 个敌对状态的方案数, 套路的, 记为 f(k)

然后你套路的令 g(k) 为「恰好」出现了 k 个敌对状态的方案数, 然后就计算出来了


需要证明的是, 二项式反演能否在这里使用

我们都知道这个柿子

f(k)=i=kn(ik)g(i)g(k)=i=kn(1)ik(ik)f(i)

容易发现的是在这种情况下, f(k) 的表达式仍然正确, 所以仍然可以这样反演

那么

ans=g(0)=i=0n(1)if(i)

说个大家不爱听的, 容斥真王朝了


但是你发现什么呢, 我们之前没有考虑 条件区间 这个东西, 但是这个肯定是要加入讨论的, 也就是说, 求 f(k) 的方法还需要讨论

首先确定 f(k) 的意义是什么 : f(k) 表示「钦定」k 个敌对关系必须成立的满足条件区间的子集方案数
容易发现的是, 对于敌对关系的状态 S , 我们可以「钦定」出那些人必须在选择集合之中, 如果这些人之间不满足条件区间, 那么一定不成立, 否则考虑向外填其他不在敌对关系之中的人, 但是如何确定满足条件区间的要求?

首先我们记敌对关系产生的子集为 U , 那么先找到 U
L=maxiUliR=miniUri , 最终的子集大小一定要在 [L,R] 之间
我们考虑当前的约束条件有那些 (假设加入的人的点集为 P , 其中 PU=):

  • L|U|+|P|R
  • iP,li|U|+|P|ri

不好处理的是第二个限制, 我们考虑预处理
可以预处理出最终选点个数一定时(即确定 |U|+|P|) , 可能的选点方案数
你容易发现, 满足 lixri 的数的个数是一定的, 我们记为 tx
这样我们可以计算 f(k)

f(k)=|S|=k|U|i=LR(ti|U|i|U|)

实现

为了检验思路的正确性, 还是要自己写代码

框架#

首先是 t 数组的计算用差分处理, 然后就是组合数部分要预处理不然会 TLE

代码#

#include <bits/stdc++.h>
#define int long long
const int MAXM = 50;
const int MAXVAL = 3e5 + 20;
const int MOD = 998244353;

inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int n, m;

int cf[MAXVAL], l[MAXVAL], r[MAXVAL], cnt[MAXVAL], a[MAXM], b[MAXM];
int fac[MAXVAL], inv[MAXVAL];

int qpow(int x, int e) {
    int ans = 1;
    while (e) {
        if (e & 1) ans = ans * x % MOD;
        x = x * x % MOD; e >>= 1;
    }
    return ans;
}

void init() {
    fac[0] = 1;
    for (int i = 1; i <= 300000; i++) fac[i] = fac[i - 1] * i % MOD;
    for (int i = 0; i <= 300000; i++) inv[i] = qpow(fac[i], MOD - 2);
}

int sum[MAXVAL][MAXM];
inline int C(int x, int y) {
    if (x < y || x < 0 || y < 0) return 0;
    return fac[x] * inv[y] % MOD * inv[x - y] % MOD;
}

signed main()
{
    n = read(); m = read();
    init();

    /*读入 + 差分处理*/
    for (int i = 1; i <= n; i++) {
        l[i] = read(), r[i] = read();
        cf[l[i]]++; cf[r[i] + 1]--;
    }

    int num = 0;
    for (int i = 1; i <= n; i++) {
        num += cf[i]; cnt[i] = num; }
    
    for (int i = 1; i <= m; i++) a[i] = read(), b[i] = read();

    /*预处理系数*/
    for (int i = 0; i <= 40; i++) for (int j = 1; j <= n; j++) sum[j][i] = (sum[j - 1][i] + C(cnt[j] - i, j - i)) % MOD;
    
    int ans = 0;
    for (int S = 0; S < (1 << m); S++) {
        int L = 1, R = n;
        std::set<int> st;
        for (int j = 1; j <= m; j++)
            if (S >> (j - 1) & 1) {
                st.insert(a[j]), L = std::max(L, l[a[j]]), R = std::min(R, r[a[j]]);
                st.insert(b[j]), L = std::max(L, l[b[j]]), R = std::min(R, r[b[j]]);
            }
        if (L > R) continue;

        int calc = (sum[R][st.size()] - sum[L - 1][st.size()] + MOD) % MOD;
        int cnt1 = __builtin_popcount(S);
        if (cnt1 & 1) ans = (ans - calc + MOD) % MOD;
        else ans = (ans + calc) % MOD;
    }
    printf("%lld\n", ans);
    return 0;
}

总结

正难则反, 转化成「钦定」类问题
「钦定」类问题往往要向「二项式反演」转化

约束条件不好处理的时候, 全部列出来冷静分析
预处理可以方便的解决一些问题
直接用组合数不好解决的问题, 你考虑枚举一些信息方便组合数的计算, 常见的是枚举最终选了多少个

posted @   Yorg  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示