题意:
就是给你一个都是小写字母字符串t,现在求有多少长度为m的字符串s,满足t是s的一个子序列。
思考:
- 对于这种问题字串够原串方案数的,感觉也是第一次遇到。那么怎么去求呢?其实这有个dp的通用方法。
比如现串abc
假如原串aaabaabbbccaccc
匹配应是aaaba[a]bb[b]ccacc[c]
规则就是:对于某个被匹配到的字符ch开始,从右边第一个开始,到右边下一个匹配的字符左边为止,不得出现跟ch一样的字符。
由子序列 S 构造原序列 T,问方案数这类问题,就是要保证:
如果你在 T 确定 T 的第 i 个字符 ti,匹配的是S中的第p个字符sp
并且你在 T 确定 T 的第 j 个字符 tj,匹配的是S中的第p+1个字符sp+1
除了ti = sp,tj = sp+1以外,还要保证ti+1…tj-1所有字符都不包含ti。 - 那么就可以dp了,定义dp[i][j]为原串匹配的i,现串匹配到j的方案数。转移的时候按上面的公式转移就可以了。
- 写完之后发现,确实是可以。但是这个题目给的数据太大了,没法两重循环去dp。那么对于本题就要换一种方法啦。不过思路本质没变,还是这样。那么还是这样的思路的话,我应该先确定现串的第一个在什么地方和原串匹配,确定好之后,前面的所有地方就26个字母随便填了。对于后面的,后面还剩m-i+1位置,第一个位置确定就是i了,那么剩下的所有位置就有C(m-i,n-1)种方案数填,那么剩下没填的呢?上面分析的就是不能和前面第一个匹配的一样,所以就是25个字母随便填。所以枚举第一个位置,然后组合数求一下就可以了。
代码:
dp方法
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;
int T,n,m,k;
char va[N];
int dp[M][M];
signed main()
{
IOS;
cin>>va+1>>m;n = strlen(va+1);
dp[0][0] = 1; //都匹配到第0位的时候是1
for(int i=1;i<=m;i++) //在匹配现串之前你想怎么放就怎么放
{
dp[i][0] = dp[i-1][0]*26%mod;
}
for(int i=1;i<=m;i++) //匹配到原串的第i位
{
for(int j=1;j<=min(i,n);j++) //匹配到现串的第j位
dp[i][j] = (dp[i][j]+dp[i-1][j-1]+dp[i-1][j]*25%mod)%mod;
} //要么第i位和第j位进行匹配,那么这个位置就填va[j]。要么第i-1位和j匹配,我的第i位随便放个va[j]不一样的就行。
cout<<dp[m][n];
return 0;
}
组合数:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;
int T,n,m,k;
char va[N];
int fact[N],infact[N];
int ksm(int a,int b)
{
int sum = 1;
while(b)
{
if(b&1) sum = sum*a%mod;
a = a*a%mod;
b >>= 1;
}
return sum;
}
void init(int x)
{
fact[0] = infact[0] = 1;
for(int i=1;i<=x;i++) fact[i] = fact[i-1]*i%mod;
infact[x] = ksm(fact[x],mod-2)%mod;
for(int i=x-1;i>=1;i--) infact[i] = infact[i+1]*(i+1)%mod;
}
int C(int a,int b)
{
if(a<b) return 0;
return fact[a]*infact[b]%mod*infact[a-b]%mod;
}
signed main()
{
IOS;
init(2e5+5);
cin>>va+1>>m;n = strlen(va+1);
int ans = 0;
for(int i=1;i<=m-n+1;i++)
{
ans = (ans+C(m-i,n-1)*ksm(26,i-1)%mod*ksm(25,m-i+1-n)%mod)%mod;
}
ans = (ans%mod+mod)%mod;
cout<<ans;
return 0;
}
2021ICPC上海-I. Steadily Growing Steam
题意:
就是给你最多100个物品,每个物品有个质量和价值,然后你最多可以操作s次,可以让某个物品的质量翻倍。现在让你选择一些物品,分到两个集合,分完之后,两个集合的体积要一样。然后你获得的价值就是两个集合所有物品的价值之和。
思考:
- 当时看到就发现质量最大才13很敏感,也就是可以把所有质量加起来,去dp。但是我怎么让两个集合的质量都一样呢?如果质量固定就是多少的话,直接dp质量的一半就可以了,但是这不固定呀。写了几个dp都是错的。实际上这个需要换种dp状态,就是维护一个第一个集合和第二个集合的差值是多少。
- 那么可以定义dp[i][j][k],为用到前i个物品,第一个集合和第二个集合质量差值为j的时候,操作次数用了不超过k次的最大价值是多少。转移的话就是,如果这个物品放到第一个集合,那么差值就变大,如果放到第二个集合差值就变小,然后同时看看要不要操作这个物品,不过记得要继承,这是定义的不超过。那么最后的答案就是dp[n][0][s]。
- 但是第一个和第二个的差值可能是负数,所以还要加给偏移量,都加质量总和的最大值2600就可以了。这样空间也会很有压力,不过发现每次转移都是用到前面一个的状态,所以第一维的100可以变成滚动数组就可以了。
代码:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;
int T,n,m,s;
int va[101],vb[101];
int dp[2][5201][101];
signed main()
{
IOS;
cin>>n>>s;m = 2600;
for(int i=1;i<=n;i++) cin>>va[i]>>vb[i];
for(int i=0;i<=5200;i++)
{
for(int j=0;j<=s;j++)
dp[0][i][j] = dp[1][i][j] = -inf;
}
for(int i=0;i<=s;i++) dp[0][m][i] = dp[1][m][i] = 0; //刚开始差值为0的时候都是0
for(int i=1;i<=n;i++)
{
for(int j=-m;j<=m;j++)
{
for(int k=0;k<=s;k++)
{
dp[i&1][j+m][k] = max(dp[i&1][j+m][k],dp[(i-1)&1][j+m][k]);
dp[i&1][j+m][k] = max(dp[i&1][j+m][k],dp[(i-1)&1][j-vb[i]+m][k]+va[i]);
if(k>=1) dp[i&1][j+m][k] = max(dp[i&1][j+m][k],dp[(i-1)&1][j-2*vb[i]+m][k-1]+va[i]);
dp[i&1][j+m][k] = max(dp[i&1][j+m][k],dp[(i-1)&1][j+vb[i]+m][k]+va[i]);
if(k>=1) dp[i&1][j+m][k] = max(dp[i&1][j+m][k],dp[(i-1)&1][j+2*vb[i]+m][k-1]+va[i]);
}
}
}
cout<<dp[n&1][m][s];
return 0;
}
题意:
就是给你一个长度为n的括号序列,现在问你有多少长度为m的合法括号序列,使得给出的序列是构造的序列的一个子序列。
思考:
- 这个也是明显的由现序列构造原序列。那么dp也要用到哪个策略,必须要和前面匹配的第一个不一样。但这里只有两种字母所以也不用乘以多少了。不过你也要保证括号序列的合法性,所以还要维护一个左括号和右括号的差值。这样就可以去转移了。
- 定义dp[i][j][k],匹配原串的第i位,现串的第j位,同时左括号和右括号的差值位k的方案数。这里不用偏移量,因为始终要保证左括号多于右括号。
- 对于有一点,当匹配到j,如果是左括号,我可以用第i位填左括号与他匹配,但是我也可以填左括号,但是我不和他匹配。但是这样就会错的,因为你这样方案数就多加了,定义的时候就是在匹配到j之后,不能出现与va[j]一样的字符。
- 不过左括号的时候我可以填右括号,或者右括号的时候填左括号,这样不会重复,因为我没和前面一次匹配的字母一样。
代码:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 201,M = 2010;
int T,n,m,k;
char va[N];
int dp[N][N][N];
signed main()
{
IOS;
cin>>T;
while(T--)
{
cin>>n>>m>>va+1;
for(int i=0;i<=m;i++)
{
for(int j=0;j<=n;j++)
{
for(int k=0;k<=m;k++)
dp[i][j][k] = 0;
}
}
dp[0][0][0] = 1;
for(int i=1;i<=m;i++)
{
for(int j=0;j<=i;j++)
dp[i][0][j] = ((j<1?0:dp[i-1][0][j-1])+dp[i-1][0][j+1])%mod;
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=0;k<=i;k++)
{
if(va[j]=='(')
{
if(k>=1) dp[i][j][k] = (dp[i][j][k]+dp[i-1][j-1][k-1])%mod;
if(k+1<=m) dp[i][j][k] = (dp[i][j][k]+dp[i-1][j][k+1])%mod;
}
if(va[j]==')')
{
if(k+1<=m) dp[i][j][k] = (dp[i][j][k]+dp[i-1][j-1][k+1])%mod;
if(k>=1) dp[i][j][k] = (dp[i][j][k]+dp[i-1][j][k-1])%mod;
}
}
}
}
cout<<dp[m][n][0]<<"\n";
}
return 0;
}
题意:
给你一个数组,每种值是一种括号,正就是左括号,负就是右括号。为0的地方就是不确定的。然后一共有m种括号,问你这个给你的数组把0填完后有多少种合法的括号序列。
思考:
- 这个括号种类那么多,没法维护每一种括号的左括号和右括号的差值。那么怎么办呢?看到范围,感觉可以区间dp,其实就是对于[l,r]这段区间的方案数怎么算,才是不多不少的。
- 我们定义dp[l][r]为区间[l,r]可以产生多少种合法的括号序列,但是这样容易重复计数。()()()可以看为() ()()或者()() ()。所以在枚举中转点的时候要注意一下。枚举到中转点k的时候,如果让va[k]和va[l]匹配就不会重复了。
- 那么就是分类如果va[l]>0,那么如果va[k]=0||va[k]=-va[l],那么就可以从dp[l+1][k-1]×dp[k+1,r]来加一次方案数。
如果va[l]==0,那么如果va[k]==0,那么可以随意放括号dp[l+1][k-1]×dp[k+1,r]×m。如果va[k]<0,那么就还是正常的直接固定转移dp[l+1][k-1]×dp[k+1,r]。
可以直接传统的枚举区间dp,也可以用记忆化搜索直接搜。
代码:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;
int T,n,m,k;
int va[N];
int dp[501][501];
int dfs(int l,int r)
{
if(r-l+1==0) return 1;
if((r-l+1)%2!=0) return 0;
if(dp[l][r]!=-1) return dp[l][r];
int sum = 0;
if(va[l]==0)
{
for(int i=l+1;i<=r;i+=2)
{
if(va[i]<0) sum = (sum+dfs(l+1,i-1)*dfs(i+1,r)%mod)%mod;
if(va[i]==0) sum = (sum+dfs(l+1,i-1)*dfs(i+1,r)%mod*m%mod)%mod;
}
}
if(va[l]>0)
{
for(int i=l+1;i<=r;i+=2)
{
if(va[i]==0||va[i]==-va[l])
sum = (sum+dfs(l+1,i-1)*dfs(i+1,r)%mod)%mod;
}
}
dp[l][r] = sum%mod;
return sum;
}
signed main()
{
IOS;
cin>>T;
while(T--)
{
cin>>n>>m;m%=mod;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
dp[i][j] = -1;
}
for(int i=1;i<=n;i++) cin>>va[i];
int ans = (dfs(1,n)%mod+mod)%mod;
cout<<ans<<"\n";
}
return 0;
}