Mercenaries
思路
今天时间剩的不多, 还是看看得了
发现听过某个巨佬讲这道题, 可惜忘了
你发现约束条件数
先考虑一下有没有什么符合直觉的做法, 你发现他求
算了反正状态不好, 复习一下回寝了
下一次写这个题还要等到考完试补完题, 很蓝的啦
考完复活了
简化题意
有
个人和 对敌对关系, 每一个人有一个条件区间
现在要在这个人中选 若干个人 , 定义一个合法的选择 , 当且仅当对于 中的所有人 , , 且 中所有人互不敌对
求有多少种合法的选择
因为求
先考虑互不敌对这个条件, 你发现因为敌对数很少啊, 所以互不敌对的子集其实是很多的, 我们考虑正难则反, 考虑敌对子集
这个是简单的, 我们可以记录满足了哪些敌对状态, 仅仅只有
知道了满足了那些敌对状态, 那么就知道必须要有哪些人出现, 然后其他人任选
好那么进入正题, 你发现给定「满足敌对状态」, 我们只能「钦定」这些人出现, 也就是说我们只能找到「至少」出现了
然后你套路的令
需要证明的是, 二项式反演能否在这里使用
我们都知道这个柿子
容易发现的是在这种情况下,
那么
说个大家不爱听的, 容斥真王朝了
但是你发现什么呢, 我们之前没有考虑 条件区间 这个东西, 但是这个肯定是要加入讨论的, 也就是说, 求
首先确定
容易发现的是, 对于敌对关系的状态
首先我们记敌对关系产生的子集为
我们考虑当前的约束条件有那些 (假设加入的人的点集为
不好处理的是第二个限制, 我们考虑预处理
可以预处理出最终选点个数一定时(即确定
你容易发现, 满足
这样我们可以计算
实现
为了检验思路的正确性, 还是要自己写代码
框架#
首先是
代码#
#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;
}
总结
正难则反, 转化成「钦定」类问题
「钦定」类问题往往要向「二项式反演」转化
约束条件不好处理的时候, 全部列出来冷静分析
预处理可以方便的解决一些问题
直接用组合数不好解决的问题, 你考虑枚举一些信息方便组合数的计算, 常见的是枚举最终选了多少个
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫