codeforces1185G1 2100分状压 + dp

题目传送门

题意:

现在有 \dpi{150}n 首歌可以选择,每首歌时长是 \dpi{150}t_i ,种类是 \dpi{150}g_i 。

每首歌最多被选择一次。

你现在想选择两两不同的若干首歌,使这些歌的时长和是 \dpi{150}T

你选择的歌是有顺序的,要求相邻的歌种类不同。

数据范围:\dpi{150} 1 \leqslant n \leqslant 15 , 1 \leqslant T \leqslant 225 , 1 \leqslant t_i \leqslant 15 , 1 \leqslant g_i \leqslant 3 。

题解:

我们状压选歌的集合。

一个集合合法的前提是歌曲播放时长的和是T。

然后看能形成多少种排列。

\dpi{150}dp[i][j][k][0] 表示 \dpi{150} g_i = 1 的人有 \dpi{150} i 个, \dpi{150} g_i = 2 的人有 \dpi{150} j 个, \dpi{150} g_i = 3 的人有 \dpi{150} k 个,第 \dpi{150} i + j + k 个人 \dpi{150} g_i = 1 的排列方案数。

\dpi{150}dp[i][j][k][1] 表示 \dpi{150} g_i = 1 的人有 \dpi{150} i 个, \dpi{150} g_i = 2 的人有 \dpi{150} j 个, \dpi{150} g_i = 3 的人有 \dpi{150} k 个,第 \dpi{150} i + j + k 个人 \dpi{150} g_i = 2 的排列方案数。

\dpi{150}dp[i][j][k][2] 表示 \dpi{150} g_i = 1 的人有 \dpi{150} i 个, \dpi{150} g_i = 2 的人有 \dpi{150} j 个, \dpi{150} g_i = 3 的人有 \dpi{150} k 个,第 \dpi{150} i + j + k 个人 \dpi{150} g_i = 3 的排列方案数。

\dpi{150} dp 数组表示的方案数认为相同的元素排列是相同的。

举个例子,假如现在有两首歌,它们都是 \dpi{150} g_i = 1 ,\dpi{150} dp 算出来的方案数是 \dpi{150}1 ,其实是 \dpi{150}2 。

所以我们需要对于每种歌都乘一下阶乘。

转移方程:

\dpi{150} dp[i][j][k][0] = dp[i - 1][j][k][1] + dp[i - 1][j][k][2]

\dpi{150} dp[i][j][k][1] = dp[i][j - 1][k][0] + dp[i][j - 1][k][2]

\dpi{150} dp[i][j][k][2] = dp[i][j][k - 1][0] + dp[i][j][k - 1][1]

感受:

\dpi{150}n 的范围一看就是状压。

然后排列的话,想一想发现是 \dpi{150} dp 。然后就完了。

不过中间乘阶乘的时候出错了。应该最后再乘的。

代码:

#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 ;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值