题目传送门
题意:
现在有 首歌可以选择,每首歌时长是
,种类是
。
每首歌最多被选择一次。
你现在想选择两两不同的若干首歌,使这些歌的时长和是 。
你选择的歌是有顺序的,要求相邻的歌种类不同。
数据范围: 。
题解:
我们状压选歌的集合。
一个集合合法的前提是歌曲播放时长的和是T。
然后看能形成多少种排列。
表示
的人有
个,
的人有
个,
的人有
个,第
个人
的排列方案数。
表示
的人有
个,
的人有
个,
的人有
个,第
个人
的排列方案数。
表示
的人有
个,
的人有
个,
的人有
个,第
个人
的排列方案数。
数组表示的方案数认为相同的元素排列是相同的。
举个例子,假如现在有两首歌,它们都是 ,
算出来的方案数是
,其实是
。
所以我们需要对于每种歌都乘一下阶乘。
转移方程:
感受:
的范围一看就是状压。
然后排列的话,想一想发现是 。然后就完了。
不过中间乘阶乘的时候出错了。应该最后再乘的。
代码:
#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
typedef pair<int , int> pli ;
const int maxn = 20 ;
const ll mod = 1e9 + 7 ;
int n , T ;
int t[maxn] , g[maxn] ;
int dp[maxn][maxn][maxn][5] ;
int cnt[5] ;
ll f[maxn] ;
void init()
{
f[0] = 1ll ;
for(int i = 1 ; i <= 15 ; i ++) f[i] = f[i - 1] * i % mod ;
dp[1][0][0][0] = 1 ;
dp[0][1][0][1] = 1 ;
dp[0][0][1][2] = 1 ;
for(int i = 0 ; i <= 15 ; i ++)
for(int j = 0 ; i + j <= 15 ; j ++)
for(int k = 0 ; i + j + k <= 15 ; k ++)
{
if(i + j + k <= 1) continue ;
if(i >= 1)
{
dp[i][j][k][0] += dp[i - 1][j][k][1] ;
dp[i][j][k][0] %= mod ;
dp[i][j][k][0] += dp[i - 1][j][k][2] ;
dp[i][j][k][0] %= mod ;
}
if(j >= 1)
{
dp[i][j][k][1] += dp[i][j - 1][k][0] ;
dp[i][j][k][1] %= mod ;
dp[i][j][k][1] += dp[i][j - 1][k][2] ;
dp[i][j][k][1] %= mod ;
}
if(k >= 1)
{
dp[i][j][k][2] += dp[i][j][k - 1][0] ;
dp[i][j][k][2] %= mod ;
dp[i][j][k][2] += dp[i][j][k - 1][1] ;
dp[i][j][k][2] %= mod ;
}
}
}
ll cal(int x)
{
ll ans = 0 ;
int sum = 0 ;
for(int i = 0 ; i < 3 ; i ++) cnt[i] = 0 ;
for(int i = 0 ; i < n ; i ++)
if(x & (1 << i)) cnt[g[i + 1]] ++ , sum += t[i + 1] ;
if(sum != T) return 0ll ;
for(int i = 0 ; i < 3 ; i ++)
ans += dp[cnt[0]][cnt[1]][cnt[2]][i] , ans %= mod ;
for(int i = 0 ; i < 3 ; i ++)
ans *= f[cnt[i]] , ans %= mod ;
return ans ;
}
void solve()
{
int num = (1 << n) ;
ll ans = 0ll ;
for(int i = 0 ; i < num ; i ++)
ans += cal(i) , ans %= mod ;
printf("%lld\n" , ans) ;
}
int main()
{
scanf("%d%d" , &n , &T) ;
for(int i = 1 ; i <= n ; i ++)
scanf("%d%d" , &t[i] , &g[i]) , g[i] -- ;
init() ;
solve() ;
return 0 ;
}