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

 

区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值