月月查华华的手机
题目描述:
链接:https://ac.nowcoder.com/acm/problem/23053
月月和华华一起去吃饭了。期间华华有事出去了一会儿,没有带手机。月月出于人类最单纯的好奇心,打开了华华的手机。哇,她看到了一片的QQ推荐好友,似乎华华还没有浏览过。月月顿时醋意大发,出于对好朋友的关心,为了避免华华浪费太多时间和其他网友聊天,她要删掉一些推荐好友。但是为了不让华华发现,产生猜疑,破坏了他们的友情,月月决定只删华华有可能搭讪的推荐好友。
月月熟知华华搭讪的规则。华华想与某个小姐姐搭讪,当且仅当小姐姐的昵称是他的昵称的子序列。为了方便,华华和小姐姐的昵称只由小写字母构成。为了更加方便,保证小姐姐的昵称长度不会比华华的长。
现在月月要快速的判断出哪些推荐好友要删掉,因为华华快回来了,时间紧迫,月月有点手忙脚乱,所以你赶紧写个程序帮帮她吧!
分析:
子序列的匹配问题 用到next[i][j] 表示距离第i个位置最近的字母j所在位置 具体实现可以逆着推 不断更新last[j] (表示最晚出现j的位置)
最后就是匹配了
code:
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e6+5;
char s[maxn],t[maxn];
int n;
int next[maxn][30];
int last[30];
void calc();
int main(){
cin>>(s+1);
calc();
cin>>n;
for(int i=1;i<=n;i++){
cin>>(t+1);
int len=strlen(t+1);
int now=0,pd=1;
for(int j=1;j<=len;j++){
now=next[now][t[j]-'a'];
if(!now){
pd=0;break;
}
}
if(pd)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
void calc(){
int strl=strlen(s+1);
for(int i=strl;i>=0;i--){
for(int j=0;j<=25;j++)
next[i][j]=last[j];
last[s[i]-'a']=i;
}
}
最短编辑距离:
链接:https://www.acwing.com/problem/content/904/
给定两个字符串,a 和 b,现要将 a 变成 b。
可以进行的操作有:
1.将 a 中某个字符删除
2.在 a 的某个位置插入某个字符
3.将 a 中的某个字符替换成另一个字符
问,最少几次操作可以实现 a 变成 b
分析:
设dp[i][j] 表示a字符串的前i个变成b字符串的前j个 最少操作步数
删除: dp[i-1][j] + 1
插入: dp[i][j-1] + 1
替换: dp[i-1][j-1] +1 条件是a[i]!=b[i]
code:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];//a的前N个去匹配b的前N个需要编辑的最小操作
int main() {
scanf("%d%s", &n, a + 1);
scanf("%d%s", &m, b + 1);
for (int i = 0; i <= m; i ++ ) f[0][i] = i;//a的前0个字母去匹配b的前i个字母,只能加
for (int i = 0; i <= n; i ++ ) f[i][0] = i;//a的前i个字母去匹配b的前0个字母,只能减
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ ) {// 删除操作 增
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);// 改
if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
printf("%d\n", f[n][m]);
return 0;
}
如果不知道这是一道背包题的话,很难往背包的方面想。我们不妨把题目中给定的6个单词看做六个数量无限的物品,现在他们要装到一个背包中
比如要装一个input,能装入背包的条件是当前装了一些的背包中,再往后需要的字母依次是i,n,p,u,t。最后成功的条件是背包被装满即dp[串长]有值。
dp[i]表示前i个字符是否能完成匹配。如上所述,则dp[i]能由dp[i - len[i]] 推出,当且仅当,子串c[j - len[i] ~ j] 为给定的单词
这里可以用到substr,要尽量优化一下
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <map>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXM = 1000010;
const int MAXN = 1010;
int n;
string c[] = {"", "one", "puton", "out", "output", "in", "input"};
int len[] = {0, 3, 5, 3, 6, 2, 5};
int dp[MAXM];
int main( ) {
scanf("%d", &n);
string s; int l;
for (int i = 1; i <= n; ++ i) {
memset(dp, 0, sizeof(dp));
cin >> s;
l = s.size( );
dp[0] = 1;
for (int j = 1; j <= l; ++ j)
if(s[j - 1] == 'e' || s[j - 1] == 'n' || s[j - 1] == 't') //优化
for (int i = 1; i <= 6; ++ i)
if (j - len[i] >= 0)
if(s[j - len[i]] == 'o' || s[j - len[i]] == 'p' || s[j - len[i]] == 'i') //优化
if (s.substr(j - len[i], len[i]) == c[i])
dp[j] = dp[j] | dp[j - len[i]];
if (dp[l]) printf("YES\n");
else printf("NO\n");
}
}
https://www.luogu.com.cn/problem/P2890
设dp[i][j]表示区间[i,j]变成回文串的最小花费,需要用到区间DP的思想。
考虑如何用一个小区间更新一个大区间。
如果大区间是dp[i][j],若s[i]=s[j],那么dp[i][j]=dp[i+1][j−1],即当前大区间可以由去掉其两端的小区间更新而来而不用花费。
不等的时候,dp[i][j]=min(dp[i+1][j]+min(add[s[i]],del[s[i]]),dp[i][j−1]+min(add[s[j],del[s[j]]])
关键在于前i个和前j个匹配 至于具体怎么匹配的我们不需要晓得
#include<iostream>
#include<algorithm>
using namespace std;
const int M = 2005, N = 256;
int n, m;
char c, s[M];
int del[N], add[N], f[M][M];
int main() {
scanf("%d%d%s", &n, &m, (s+1));
for (int i = 1; i <= n; ++ i) {
cin >> c;
cin >> add[c] >> del[c];
}
for (int L = 2; L <= m; ++ L)
for (int i = 1; i + L - 1 <= m; ++ i) {
int j = i + L - 1;
if (s[i] == s[j]) f[i][j] = f[i + 1][j - 1];
else f[i][j] = min(f[i+1][j] + min(add[s[i]], del[s[i]]),
f[i][j-1] + min(add[s[j]], del[s[j]]));
}
cout<<f[1][m]<<endl;
return 0;
}
https://www.luogu.com.cn/problem/P1136
注意这种交换位置类型的题目
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
return x*f;
}
char s[501];
int dp[501][101][101][3];
int n,k,m;
int sum;
int main(){
memset(dp,128,sizeof(dp));
dp[0][0][0][1]=0;
n=read(),k=read();
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
for(int f=0;f<=k;f++)
{
if(s[i]=='j')
{
dp[i][j][f][0]=max(dp[i-1][j][f][0],dp[i-1][j][f][1]);
if(j!=0) dp[i][j][f][1]=max(dp[i-1][j-1][f][0]+1,dp[i-1][j-1][f][1]);
}
if(s[i]=='z')
{
dp[i][j][f][1]=max(dp[i-1][j][f][0]+1,dp[i-1][j][f][1]);
if(f!=0) dp[i][j][f][0]=max(dp[i-1][j][f-1][0],dp[i-1][j][f-1][1]);
}
}
}
}
for(int i=0;i<=k;i++) sum=max(sum,max(dp[n][i][i][0],dp[n][i][i][1]));
cout<<sum;
}
分析:子序列问题用到next数组
nxt[i][j]表示i位置后面第一个字符j的位置
剩下的就是直接贪心找即可
void slove() {
cin >> s >> t;
s = "?" + s; t = "?" + t;
for (int i = 0; i < 26; i++)id[i] = -1;
for (int i = s.length()-1; i >= 0; i--) {
for (int j = 0; j < 26; j++)nxt[i][j] = id[j];
if (i)id[s[i] - 'a'] = i;
}
int ans = 0;
int j = 1;
while (j < t.length()) {
if (nxt[0][t[j] - 'a'] == -1) { cout << "-1" << endl; return; }
ans++;
int u = 0;
while (j < t.length() && nxt[u][t[j] - 'a'] != -1) {
u = nxt[u][t[j] - 'a'];
j++;
}
}
cout << ans << endl;
}