题解 CF1400G 【Mercenaries】
upd on 2021.7.21:修了个 typo
先祭一个,div2 打过的最高纪录(要是我拿小号打就是 official rk 3)
我现场竟然 A 了这道题,incredible!
洛谷题面传送门 & Codeforces 题面传送门
楼下的大佬用了一种类似于图论的做法,然鹅我不会/kk,wtcl
注意到
通俗地讲,就是拿总方案数减去有限制不满足的情况就是答案。而计算不合法的情况,可以把有1条限制不满足的方案数加起来。但是这样会算重,故还要扣掉2条限制不满足的方案数,这样会导致3条限制不满足的情况被多减了一遍,以此类推。
具体地讲,假设所有限制组成的集合为
其中
这样子枚举是
先从简单情况入手——计算总方案数,即,不考虑限制的情况下的方案数。我们枚举选中的集合的大小
接下来再考虑加上限制的情况,很明显,与这些限制相关的元素必须都被选中,假设有
这样我们就有
暴力计算肯定会 T,不过注意到
然后用上面的表达式计算出答案就可以了。
怎样计算
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define foreach(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define all(a) a.begin(),a.end()
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,0x3f,sizeof(a))
#define y1 y1010101010101
#define y0 y0101010101010
#define int long long
typedef pair<int,int> pii;
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
int n=read(),m=read();
int cf[300005],l[300005],r[300005],cnt[300005],a[25],b[25];
const int MOD=998244353;
int fac[300005],inv[300005];
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;
}
inline void prework(){
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[300005][45];
inline int getc(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(){
prework();
fz(i,1,n){
l[i]=read(),r[i]=read();
cf[l[i]]++;cf[r[i]+1]--;
}
int num=0;
fz(i,1,n){num+=cf[i];cnt[i]=num;}
fz(i,1,m) a[i]=read(),b[i]=read();
fz(i,0,40){
fz(j,1,n){
sum[j][i]=(sum[j-1][i]+getc(cnt[j]-i,j-i))%MOD;
}
}
// cout<<sum[3][1]<<" "<<sum[1][1]<<endl;
int ans=0;
for(int i=0;i<(1<<m);i++){
int _l=1,_r=n;
set<int> st;
fz(j,1,m){
if(i>>(j-1)&1){
_l=max(_l,l[a[j]]);_r=min(_r,r[a[j]]);st.insert(a[j]);
_l=max(_l,l[b[j]]);_r=min(_r,r[b[j]]);st.insert(b[j]);
}
}
if(_l>_r) continue;
int calc=(sum[_r][st.size()]-sum[_l-1][st.size()]+MOD)%MOD;
int cnt1=__builtin_popcount(i);
if(cnt1&1) ans=(ans-calc+MOD)%MOD;
else ans=(ans+calc)%MOD;
}
cout<<ans<<endl;
return 0;
}
最后来总结一下这题为什么可以想到容斥原理:对于任意一条限制,若要满足它,有
故对于这类数据范围很小,而从正面计算比较困难的题目,可以想到容斥原理,用反面计算。