gym 101669 A : Concerts (dp)

27 篇文章 0 订阅

题目链接:https://codeforc.es/gym/101669

题目大意:有一个人想按顺序参加K个乐队的演唱会,然后得知最近n天每天有一场演唱会,但由于有的乐队演唱会门票价较高,他参加完这乐队的演唱会之后就必须休息a天之后再参加后面的演唱会。现给出它想参加的K个乐队的演唱会的顺序,以及最近n天的演唱会(乐队用大写字母’A’ - ‘Z’ 表示),问他有多少种方案参加完这K场演唱会?

问题可以转化为在一个字符串中求有多少种不同的子串K,不要求连续,但有的字母要求间隔不能太小。

分析:建一个超级远点,在草稿纸上画图,会发现有许多重复的地方,容易想到可以dp。dp[i][j]表示子串从i位开始,原串从第j位开始,可以得到的不同的子串种类数,第一发写的记忆化搜索,T了,换成递推的写法后发现dp有三层循环,即使第三层用了upper_bound优化也可以被卡掉。

解决方法是充分利用状态记忆优化一维,状态总数实际上是1e7,但原来的写法很多状态没有用到,转移的时候需要枚举可转移的状态。实际上dp[i][j+1]可以转移给dp[i][j],这样转移的时候不需要枚举,只要当前i和j位置的字符相等,即可以转移。

体会:这种优化很像完全背包的优化,初学时我以为完全背包需要枚举这种物品可以装的个数来进行转移,这样就需要三重循环,枚举三维,实际上可以充分利用状态来记录子问题最优解,dp[i][j] 表示前i种物品,当前容量为j的最优解,令dp[i][j] = dp[i-1][j]记录不转移的情况,然后从dp[i][j - w[i]]转移,这样就实现了O(1)的转移,去掉了一维。
看代码:
修改后的正解:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
const int mod = 1e9 + 7;
int val[30];
char s[maxn * 10],t[maxn * 10];
int n,k;
int dp[305][maxn * 10];
int main() {
	//freopen("in.txt","r",stdin);
	scanf("%d%d",&k,&n);
	for(int i = 0; i < 26; i++)
		scanf("%d",&val[i]);
	memset(dp,0,sizeof dp);
	scanf("%s",s);
	scanf("%s",t);
	long long res = 0;
	for(int i = 0; i <= n; i++) dp[k][i] = 1;
	for(int i = k - 1; i >= 0; i--) {
		for(int j = n - 1; j >= 0; j--) {
			dp[i][j] = dp[i][j + 1];
			if(t[j] == s[i]) {
				dp[i][j] += dp[i + 1][min(n,j + val[s[i] - 'A'] + 1)];
				dp[i][j] %= mod;
			}
		}
	}
	printf("%d\n",dp[0][0] % mod);
	return 0;
}

原来的写法:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
long long val[30];
char s[maxn],t[maxn];
vector<long long> g[30];
int n,k;
int dp[305][maxn];
int dfs(int cur,long long p) {
	if(dp[cur][p] != -1) return dp[cur][p];
	if(cur > k) {
		return 1;
	}
	long long ans = 0;
	int tmp = s[cur] - 'A';
	long long z = val[s[cur - 1] - 'A'] + p;
	int pos = upper_bound(g[tmp].begin(),g[tmp].end(),z) - g[tmp].begin();
	for(int i = pos; i < g[tmp].size(); i++) {
		ans += dfs(cur + 1,g[tmp][i]) % mod;
		ans %= mod;
	}
	return dp[cur][p] = ans;
}
int main() {
//	freopen("in.txt","r",stdin);
	scanf("%d%d",&k,&n);
	 if(k <= 0 || n <= 0) {
		puts("0");
		return 0;
	} 
	memset(dp,-1,sizeof dp);
	for(int i = 0; i < 26; i++)
		scanf("%lld",&val[i]);
	scanf("%s",s + 1);
	scanf("%s",t + 1);
	for(int i = 1; i <= n; i++)
		g[t[i] - 'A'].push_back(i);
	long long ans = 0;
	for(int i = 0; i < g[s[1] - 'A'].size(); i++) {
		ans += dfs(2,g[s[1] - 'A'][i]) % mod;
		ans %= mod;
	}
	printf("%lld\n",ans);
	return 0;
}

错解递推的版本,这里可以看出来为什么会T,记忆化搜索不太好估复杂度:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
const int mod = 1e9 + 7;
int val[1123];
char s[maxn * 10],t[maxn * 10];
vector<int> g[30];
int n,k;
int dp[305][maxn * 10];
int main() {
	//freopen("in.txt","r",stdin);
	scanf("%d%d",&k,&n);
	for(int i = 0; i < 26; i++)
		scanf("%d",&val[i]);
	memset(dp,0,sizeof dp);
	scanf("%s",s);
	scanf("%s",t);
	for(int i = 0; i < n; i++)
		g[t[i] - 'A'].push_back(i);
	for(int i = 0; i < g[s[k - 1] - 'A'].size(); i++)
		dp[k - 1][g[s[k - 1] - 'A'][i]] = 1;	
	long long res = 0;
	for(int i = k - 2; i >= 0; i--) {
		int p = s[i] - 'A';
		int nxt = s[i + 1] - 'A';
		for(int j = 0; j < g[p].size(); j++) {
			int pos = upper_bound(g[nxt].begin(),g[nxt].end(),g[p][j] + val[p]) - g[nxt].begin();
			for(int z = pos; z < g[nxt].size(); z++) {
				dp[i][g[p][j]] += dp[i + 1][g[nxt][z]];
				dp[i][g[p][j]] %= mod;
			}
			if(i == 0) {
				res += dp[i][g[p][j]];
				res %= mod;
			}
		}
	}
	printf("%lld\n",res % mod);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值