题意:有k个区间【l,r】,从每个区间选一个数使他们和为n,问有多少种方法。
思路:这个问题可以转化为k个【0,r-l】的区间 ,每个区间选一个数使他们和为n-sum(l)。令n = n-sum(l).
这样通过隔板法,就能得到方案数位C(k+n-1,k-1)。但因为会有超出的情况,所以通过容斥去判断超出的情况。
超出0个时候加,超出1个的时候减,超出2个的时候加,超出3个的时候减……
最后二进制枚举子集即可。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int mod = 100000007;
int f[15];
ll pow_mod(ll k,ll n)
{
ll ans = 1;
while(n)
{
if(n&1)
ans = ans*k%mod;
k = k*k%mod;
n >>= 1;
}
return ans;
}
ll cal(ll n,ll m)
{
if(m > n) return 0;
if(m > n-m) m = n-m;
ll a = 1,b = 1;
for(int i = 0; i < m; i++)
{
a = a*(n-i)%mod;
b = b*(i+1)%mod;
}
return a*pow_mod(b,mod-2)%mod;
}
ll lucas(ll n,ll m)
{
if(m==0) return 1;
return cal(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}
int main()
{
int T;
int kase = 0;
scanf("%d",&T);
while(T--)
{
ll k,n;
scanf("%lld%lld",&k,&n);
for(int i = 0; i < k; i++)
{
ll l,r;
cin >> l >> r;
r -= l, n -= l;
f[i] = r;
}
ll ans = 0;
for(int S = 0; S < (1<<k); S++)
{
int flag = 1;
int st = n;
for(int i = 0; i < k; i++)
{
if((1<<i)&S)
{
flag *= -1;
st -= (f[i]+1);
}
}
if(st<0) continue;
ans = (ans + flag*lucas(st+k-1,k-1))%mod;
}
ans = (ans+mod)%mod;
printf("Case %d: %lld\n",++kase,ans);
}
return 0;
}