Codeforces 1400 G. Mercenaries —— 容斥

This way

题意:

现在有n个人,让你组一支队伍,并且每个人都有限制,如果你选择这个人,那么组队人数必须在li~ri之间。并且有m条限制,表示一对人不能同时选择。
问你最终有多少种选择方法。

题解:

正难则反
我们考虑当前已经确定了有k个人要选,
那么对于m个限制中的第i个,如果k在l~r中,也就是可以选的情况有 C n u m [ k ] − 2 k − 2 C_{num[k]-2}^{k-2} Cnum[k]2k2
num[i] 表示选i个人有多少的人符合这条限制

明显这样子去做会有重复的情况,那么我们需要考虑容斥,也就是枚举m的所有状态,然后加上影响。
假设现在我们有add条限制,涉及tot个人当前限制可以取的人数区间为l~r,那么这个限制的影响就是
∑ i = l r C n u m [ i ] − t o t i − t o t ∗ ( a d d % 2 ? − 1 : 1 ) \sum\limits_{i=l}^{r}C_{num[i]-tot}^{i-tot}*(add\%2?-1:1) i=lrCnum[i]totitot(add%2?1:1)

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,M=45;
#define ll long long 
const ll mod=998244353;
ll fac[N],inv[N],sum[M][N];
struct node{
    int l,r;
}p[N],lim[M];
int num[N],vis[N];
ll qpow(ll a,ll b){ll ans=1;for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;}

ll C(int n,int m){
    if(n<m||m<0)return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
    fac[0]=inv[0]=1;
    for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;
    inv[N-1]=qpow(fac[N-1],mod-2);
    for(int i=N-2;~i;i--)
        inv[i]=inv[i+1]*(i+1)%mod;
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&p[i].l,&p[i].r),num[p[i].l]++,num[p[i].r+1]--;
    for(int i=1;i<=n;i++)
        num[i]+=num[i-1];
    for(int i=0;i<m;i++)
        scanf("%d%d",&lim[i].l,&lim[i].r);
    ll ans=0;
    for(int i=1;i<=n;i++)
        if(i<=num[i])
            ans=(ans+C(num[i],i))%mod;
    for(int i=1;i<M;i++)
        for(int j=i;j<=n;j++)
                sum[i][j]=(sum[i][j-1]+C(num[j]-i,j-i))%mod;
    for(int i=1;i<(1<<m);i++){
        int l=1,r=n,tot=0;
        for(int j=0;j<m;j++){
            if(!(i&(1<<j)))continue;
            int p1=lim[j].l,p2=lim[j].r;
            l=max(l,max(p[p1].l,p[p2].l)),r=min(r,min(p[p1].r,p[p2].r));
            if(!vis[p1])tot++;
            if(!vis[p2])tot++;
            vis[p1]=vis[p2]=1;
        }
        for(int j=0;j<m;j++){
            if(!(i&(1<<j)))continue;
            int p1=lim[j].l,p2=lim[j].r;
            vis[p1]=vis[p2]=0;
        }
        //l=max(l,tot);
        if(l>r)continue;
        int add=__builtin_popcount(i);
        ans=(ans+(sum[tot][r]-sum[tot][l-1]+mod)*(add%2?-1:1)+mod*5)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值