基础dp选做(1)
连续正面的可能性
设
则:
(1) k=0 那前n-1位可以是任意数,
(2) 0<k<j 那么j枚连续朝上的面一定不出现在最后面,所以
(3) k=j 那么有两种可能:只有最后面j位连续正面是唯一最多的,和前面已经有j位连续正面,最后面又有j位连续正面,所以
目前只想到
#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; }
石子合并
难得的是我居然完全记得四年前写题时的情绪
注意初始值和端点处理
#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)时的最值,有
考虑优化,发现只有i+j==k+l时的状态是有意义的,从而优化到
还可以优化空间: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:从
#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,分是否为终点进行讨论,复杂度
#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]}
,这样就需要枚举两个中断点,复杂度
方程:f[l][r][k]=max{f[l][m][k-1]*f[m+1][r][1]}
,复杂度
#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,结果发现数据水到暴力打满(悲)
一开始的数据水到假做法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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效