题目链接: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
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
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
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;
}