第一题 石子合并问题--直线版
可以自行在vj上查题,最基础的石子问题,昨天没过,今天过了,vj有毒。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=101;
const int INF=1<<30;
int Min[maxn][maxn];
int Max[maxn][maxn];
int a[maxn],sum[maxn];
int main()
{
int n;
while(cin>>n&&n!=0)
{
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sum[0]=0;
for(int i=1;i<=n;i++)
{
Min[i][i]=0;
Max[i][i]=0;
sum[i]=sum[i-1]+a[i];
}
for(int v=2;v<=n;v++)
{
for(int i=1;i<=n-v+1;i++)
{
int j=i+v-1;//j代表终点
Min[i][j]=INF;
Max[i][j]=-1;
int tmp=sum[j]-sum[i-1];
for(int k=i;k<j;k++)
{
Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+tmp);
Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+tmp);
}
}
}
cout<<Min[1][n]<<" ";
cout<<Max[1][n]<<endl;
}
return 0;
}
第二题 石子合并问题--圆形版
就是上一题的一个进阶, 但是我找bug找了很久都没有找到。我是真的不知道为啥。样例都能过。
第三题 Brackets
这道题其实就相当于石子合并直线版的变形问题。他动态规划方程就是dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k+1][j]+2)相当于第i个和第k个匹配好。思路逻辑很简单,就是要注意边界问题,字符串是从0开始的还是从1开始数的。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=101;
char str[maxn];
int dp[maxn][maxn];
//dp[i][j]表示从第i个到第j个括号的最大匹配数目
int main()
{
while(scanf("%s",str+1)&&str[1]!='e')
{
memset(dp,0,sizeof(dp));
int len=strlen(str+1);
for(int i=len-1;i>=0;i--)
{
for(int j=i+1;j<=len;j++)
{
dp[i][j]=dp[i+1][j];//没有匹配到
for(int k=i+1;k<=j;k++)//枚举区间i+1---j
{
if((str[i]=='('&&str[k]==')')||(str[i]=='['&&str[k]==']'))
{
dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k+1][j]+2);
//第i个和第k个匹配到了
}
}
}
}
cout<<dp[1][len]<<endl;
}
return 0;
}
第四题 Halloween Costumes
版本一 55ms
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=101;
int dp[maxn][maxn];
int a[maxn];
int main()
{
int t,cnt=0;
cin>>t;
while(t--)
{
cnt++;
int n;
cin>>n;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=n;i>=1;i--)//从后往前
{
for(int j=i;j<=n;j++)
{
dp[i][j]=dp[i+1][j]+1;
for(int k=i+1;k<=j;k++)
{
if(a[i]==a[k])
{
dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
}
}
}
}
printf("Case %d: %d\n",cnt,dp[1][n]);
}
return 0;
}
版本2 :50ms
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=201;
int a[maxn];
int dp[maxn][maxn];
int main()
{
int t,cnt=0;
cin>>t;
while(t--)
{
cnt++;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=0;i<n;i++)
{
dp[i][i]=1;
}
for(int d=1;d<=n;d++)//问题规模
{
for(int i=1;i<=n-d+1;i++)//枚举起点
{
int j=i+d-1;//终点
dp[i][j]=dp[i+1][j]+1;
for(int k=i+1;k<=j;k++)
{
if(a[i]==a[k])
dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
}
}
}
printf("Case %d: %d\n",cnt,dp[1][n]);
}
return 0;
}
第五题 Palindrome subsequence
随后补题解,这个题做的我感觉自己dp还没有入门,基础dp我自己能写出来,感觉分析问题的能力还是很差。这道题我根本想不到如何分析,看了题解后才恍然大悟。
首先是区间dp,那么就要想到区间dp的一般做法,枚举起点,终点,中间点。
dp[i][j]表示区间i到j的回文数目。做题之前要知道什么是回文,以及如何判断回文。i--j的前一个状态,其实也就是子问题是i+1--j和i--j-1但是这两个状态都有共同的子状态i+1--j-1所以要把i+1--j-1减掉。当str[i]==str[j]时,也就是说i和j构成回文,此时状态方程就成了dp[i][j]+dp[i+1][j-1]+1一定要记得加1,因为i和j已经构成了回文。所以要加1.还有一个需要注意的地方是i和j未匹配时。dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod。一定要记得+mod,因为前面的可能为负数,并不是说dp[i+1][j-1]比dp[i+1][j]+dp[i][j-1]大,只是都是取模后的,无法比较大小。加上才能过。 https://blog.csdn.net/L954688947/article/details/50620302
具体可以看这位大佬的博客,我是看他的,嘻嘻嘻。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1001;
const int mod=10007;
char str[maxn];
int dp[maxn][maxn];
int main()
{
int t,cnt=0;
cin>>t;
while(t--)
{
cnt++;
memset(dp,0,sizeof(dp));
cin>>str;
int len=strlen(str);
for(int i=0;i<len;i++)
{
dp[i][i]=1;
}
for(int j=0;j<=len-1;j++)
{
for(int i=j-1;i>=0;i--)
{
dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod;
//直接取模后可能是负数,所以要加mod
//不是说dp[i+1][j-1]比dp[i+1][j]+dp[i][j-1]大
//是因为三个都是取模后的比不了大小
if(str[i]==str[j])//两个字母相同,可以组成回文
{
dp[i][j]=(dp[i][j]+dp[i+1][j-1]+1)%mod;
}
}
}
printf("Case %d: %d\n",cnt,dp[0][len-1]);
}
return 0;
}
H - String painter
这道题样例看懂了一切好说。状态规划方程不难推导。一般求区间最优解就是区间DP。 正常情况下,dp[i][j]=dp[i+1][j]+1。如果说str2[i]=str2[k]的时候,dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j])。注意ans[maxn]数组的用法,首先ans[i]初始化,接下来,如果对应位置相等的话,这个位置不刷。否则的话 ans[i]=min(ans[i],ans[j]+dp[j+1][i]),dp[i][j]表示a中i到j段变成b需要的操作次数。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=105;
char str1[maxn];
char str2[maxn];
int dp[maxn][maxn];//a中i到j段变成b需的操作次数
int ans[maxn];
int main()
{
while(scanf("%s%s",str1+1,str2+1)!=EOF)
{
memset(dp,0,sizeof(dp));
memset(ans,0,sizeof(ans));
int len1=strlen(str1+1);
for(int d=1;d<=len1;d++)
{
for(int i=1;i<=len1-d+1;i++)
{
int j=i+d-1;
dp[i][j]=dp[i+1][j]+1;
for(int k=i+1;k<=j;k++)
{
if(str2[i]==str2[k])
dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]);
}
}
}
for(int i=1;i<=len1;i++)
{
ans[i]=dp[1][i];
}
for(int i=1;i<=len1;i++)
{
if(str1[i]==str2[i])
{
ans[i]=ans[i-1];//对应位置相等,这个位置不刷
}
else
{
for(int j=1;j<=i;j++)
{
ans[i]=min(ans[i],ans[j]+dp[j+1][i]);
}
}
}
cout<<ans[len1]<<endl;
}
return 0;
}