基础dp选做(1)

连续正面的可能性

题目链接

f[i][j][k]表示i枚硬币最多连续j面朝上且最末尾有k面朝上的方法数

则:

(1) k=0 那前n-1位可以是任意数,f[i][j][0]=Σk=0jf[i1][j][k]
(2) 0<k<j 那么j枚连续朝上的面一定不出现在最后面,所以f[i][j][k]=f[i1][j][k1]
(3) k=j 那么有两种可能:只有最后面j位连续正面是唯一最多的,和前面已经有j位连续正面,最后面又有j位连续正面,所以f[i][j][j]=f[i1][j1][j1]+f[i1][j][j1]

目前只想到O(n3)的dp,看起来似乎可以O(n2)(?)

#include <stdio.h>
int n,m,ans;
int f[200][200][200];
int main(void) {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) f[i][0][0]=f[i][i][i]=1;
for(int i=2;i<=n;++i)
{
for(int j=1;j<i;++j)//unnecessery to check j==1 and j==i
{
for(int k=0;k<=j;++k)
f[i][j][0]+=f[i-1][j][k];
for(int k=1;k<j;++k)
f[i][j][k]+=f[i-1][j][k-1];
f[i][j][j]+=f[i-1][j-1][j-1]+f[i-1][j][j-1];
}
}
for(int k=0;k<=m;++k) ans+=f[n][m][k];
printf("%d\n",ans);
/*
for(int i=0;i<=n;++i)
for(int j=0;j<=i;++j)
for(int k=0;k<=j;++k)
printf("f[%d][%d][%d]=%d\n",i,j,k,f[i][j][k]);
*/
return 0;
}

石子合并

难得的是我居然完全记得四年前写题时的情绪

n3的dp,状态转移方程比较好推。对区间的处理是化环为链,链长n*2-1.

注意初始值和端点处理

#include<iostream>
#include<cstdio>
using namespace std;
int a[300],p[300],f[300][300],g[300][300];
int main(){
int n,ans=1000000000;scanf("%d",&n);
for(int i=1;i<n*2;++i)
for(int j=1;j<n*2;++j)
g[i][j]=1000000000;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
a[i+n]=a[i];
g[i+n-1][i+n-1]=g[i][i]=0;
}
for(int i=1;i<n*2;++i) p[i]=p[i-1]+a[i];
for(int m=1;m<n;++m)
for(int l=1;l<n*2-m;++l){
int r=l+m;
for(int k=l;k<r;++k){
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]-p[l-1]+p[r]);
g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]-p[l-1]+p[r]);
}
}
for(int i=1;i<=n;++i)
ans=min(ans,g[i][i+n-1]);
printf("%d\n",ans);ans=0;
for(int i=1;i<=n;++i)
ans=max(ans,f[i][i+n-1]);
printf("%d\n",ans);
return 0;
}

方格取数&传纸条

一开始选的状态无法保证无后效性时,要果断换状态。我一开始设f[i][j][k]表示第i列分别走到第j个和第k个各自的最大值,但是无法保证无后效性。

换状态f[i][j][k][l]表示分别走到(i,j),(k,l)时的最值,有O(n4)的dp

考虑优化,发现只有i+j==k+l时的状态是有意义的,从而优化到O(n3)

还可以优化空间:f[p][i][k]表示每次一共走了p步,其中第一次向右走i步向下走p-i,第二次向右走k步向下走p-k步(代码略)

#include<iostream>
#include<cstdio>
using namespace std;
int n,a[10][10],f[10][10][10][10];
int max4(int a1,int a2,int a3,int a4){
return max(max(a1,a2),max(a3,a4));
}
int main(){
scanf("%d",&n);
while(1){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
if(x==0&&y==0&&z==0) break;
a[y][x]=z;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
int k0=max(1,i+j-n);
for(int k=k0;k<i+j;++k){
int l=i+j-k;
f[i][j][k][l]=max4(f[i-1][j][k-1][l],f[i-1][j][k][l-1],
f[i][j-1][k-1][l],f[i][j-1][k][l-1])+a[i][j]+a[k][l];
if(i==k&&j==l) f[i][j][k][l]-=a[i][j];
}
}
}
printf("%d\n",f[n][n][n][n]);
return 0;
}

编辑距离

方程很简单,注意边界的实际意义:
f[i][0]=f[0][i]=i

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int inf = 1000000000;
char a[4000],b[4000];
int f[4000][4000];
int min3(int x1,int x2,int x3){
return min(min(x1,x2),x3);
}
int main(){
scanf("%s%s",a+1,b+1);
int la=strlen(a+1),lb=strlen(b+1);
for(int i=1;i<=la;++i) f[i][0]=f[i][lb+1]=i;
for(int j=1;j<=lb;++j) f[0][j]=f[la+1][j]=j;
for(int i=1;i<=la;++i){
f[i][0]=i;
for(int j=1;j<=lb;++j)
if(a[i]==b[j])
f[i][j]=f[i-1][j-1];
else f[i][j]=min3(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;
}
printf("%d\n",f[la][lb]);
return 0;
}

饥饿的奶牛

Sol1:从O(n2)的dp用二分查找优化到O(nlogn),二分查找写挂了好几次,学习一下另外一种不用递归的二分查找(

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,f[300000][2];
struct rg{
int l,r;
}a[300000];
bool cmp(rg x,rg y){return x.r<y.r;}
int fnd1(int x,int l,int r){//find the maxium who <x and return its index
if(l>=r) return l;
int mid=(l+r)/2;
if(a[mid].r>=x)
return fnd1(x,l,mid-1);
else if(r-mid==1)
if(a[r].r<x) return r;
else return mid;
return fnd1(x,mid,r);
}
int fnd2(int x,int l,int r){
int ans=0;//ans must have a initial value
while(l<=r){
int mid=(l+r)/2;
if(a[mid].r>=x){r=mid-1;}
else {ans=mid;l=mid+1;}
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&a[i].l,&a[i].r);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;++i){
f[i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=f[fnd2(a[i].l,0,i-1)+1][0]+a[i].r-a[i].l+1;
}
printf("%d\n",max(f[n][1],f[n][0]));
return 0;
}

Sol2:对格子进行dp,分是否为终点进行讨论,复杂度O(M+nlogn)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,f[4000000];
struct rg{
int l,r;
}a[300000];
bool cmp(rg x,rg y){
if(x.r==y.r) return x.l<y.l;
return x.r<y.r;}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d",&a[i].l,&a[i].r);
sort(a+1,a+n+1,cmp);
int cnt=1;
for(int i=1;i<=a[n].r;++i){
if(i==a[cnt].r){
f[i]=max(f[i],f[i-1]);
f[i]=max(f[a[cnt].l-1]+a[cnt].r-a[cnt].l+1,f[i]);
++cnt;--i;
}
else f[i]=max(f[i],f[i-1]);
}
printf("%d\n",f[a[n].r]);
return 0;
}

数字游戏

区间dp,一开始想到的方程是 f[l][r][k]=max{f[l][m][k0]*f[m+1][r][k-k0]},这样就需要枚举两个中断点,复杂度O(mn4),但是实际上这里面有很多状态是重复计算的,因为从同一l,r的任何一个k0来进行转移都是一样的。所以可以优化掉这一维。

方程:f[l][r][k]=max{f[l][m][k-1]*f[m+1][r][1]},复杂度O(mn3)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#define inf 100000000000
using namespace std;
ll n,ans,a[200],f[200][200][200],g[200][200][200];
ll mod (ll x){
return (x%10+10)%10;
}
int main(){
int M;
scanf("%lld%d",&n,&M);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
a[i+n]=a[i];
}
for(int i=2;i<=n*2-1;++i) a[i]=mod(a[i]+a[i-1]);
for(int l=1;l<=n*2-1;++l)
for(int r=l;r<=n*2-1;++r){
f[l][r][1]=g[l][r][1]=mod(a[r]-a[l-1]);
for(int k=2;k<=M;++k) g[l][r][k]=inf;
}
for(int k=2;k<=M;++k){
for(int l=1;l<n*2-1;++l){
for(int r=l+k-1;r<=n*2-1;++r){
for(int m=l;m<r;++m){
f[l][r][k]=max(f[l][r][k],f[l][m][k-1]*f[m+1][r][1]);
g[l][r][k]=min(g[l][r][k],g[l][m][k-1]*g[m+1][r][1]);
}
}
}
}
ans=inf;
for(int i=1;i<=n;++i)
ans=min(ans,g[i][i+n-1][M]);
printf("%lld\n",ans);
for(int i=1;i<=n;++i)
ans=max(ans,f[i][i+n-1][M]);
printf("%lld\n",ans);
return 0;
}

统计单词个数

为了做这个题特意去学了KMP,结果发现数据水到暴力打满(悲)

一开始的O(n2)做法假了,因为当前子串中嵌套其他子串是合法的,但是分段就是非法的,所以不能单纯的列O(1)的状态转移方程,必须要枚举断点进行dp。数据水到假做法80分,浪费我好多时间

dp一旦结合字符串就会出现边界的问题,断点一定要从n-j+1枚举到i,而不能从n开始,因为mid太大而导致的不合法状态会影响最终的转移结果。

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
string a="$",b[10];
int n,m,k;
int f[1000][100],sum[1000][1000];
int g(int x){
for(int i=1;i<=m;++i){
int len=b[i].length(),x1=x,flag=0;
for(int j=0;j<len;++j){
if(a[x1]!=b[i][j]) {flag=1;break;}
++x1;
}
if(!flag) return len;
}
return 0;
}
void init(){
cin>>n>>k;for(int i=1;i<=n;++i) {string x;cin>>x;a+=x;}
n*=20;a+="$";
cin>>m;for(int i=1;i<=m;++i) cin>>b[i];
for(int i=1;i<=m;++i){
for(int j=1;j<i;++j)
if(b[i].length()<b[j].length()){
string tmp=b[i];b[i]=b[j];b[j]=tmp;
}
}
for(int j=n;j>=1;--j){
for(int i=j;i>=1;--i){
sum[i][j]=sum[i+1][j];
int x=g(i);
if(x!=0&&x+i-1<=j) ++sum[i][j];
}
}
}
void dp(){
for(int j=1;j<=k;++j){
for(int i=n;i>=1;--i){
int x=g(i);
if(!x) f[i][j]=max(f[i+1][j],f[i+1][j-1]);
else{
for(int mid=n-j+1;mid>=i;--mid){
f[i][j]=max(f[i][j],f[mid+1][j-1]+sum[i][mid]);
}
}
}
}
}
int main(){
init();dp();cout<<f[1][k]<<endl;
return 0;
}
posted @   hcx1999  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示