动态规划专题
最大连续子序列求和
//最大连续子序列和,最简单基础班
//状态转移方程 dp[i]=max{A[i],dp[i-1]+A[i]}
#include
#include
using namespace std;
const int maxn=10010;
int A[maxn],dp[maxn]; //A[i]存放序列,dp[i]存放以A[i]为结尾的联系序列最大和
int main()
{
int n;
while(cin>>n){
if(n==0) break;
for(int i=0;i>A[i];
}
//确定边界,相当于就是本身就是最大和
dp[0]=A[0];
//是从i=1开始的,这是递推,而不是递归
for(int i=1;idp[k]) k=i;
}
cout << dp[k] << endl;
}
return 0;
}
//最大连续子序列和进阶版!!还要输出序列的头和尾
//状态转移方程 dp[i]=max{A[i],dp[i-1]+A[i]}
#include
#include
using namespace std;
const int maxn=10010;
int A[maxn];
struct node{
int start,end,sum;
}dp[maxn];
int main()
{
int n,data;
while(cin>>n){
if(n==0) break;
for(int i=0;i>A[i];
}
//确定边界,相当于就是本身就是最大和
dp[0].sum=A[0];
dp[0].start=dp[0].end=0;
//是从i=1开始的,这是递推,而不是递归
for(int i=1;idp[i-1].sum+A[i]){
dp[i].start=dp[i].end=i;
dp[i].sum=A[i];
//就是自身了
}
else{
dp[i].sum=A[i]+dp[i-1].sum;
dp[i].start=dp[i-1].start;
dp[i].end=i;
}
}
//dp[i]存放所有以A[i]为结尾的序列最大和,还需要比较最大值
int k=0;
//如何找最大值,排序也可,此处选择比较!!,值得学习
for(int i=1;idp[k].sum) k=i;
}
if(dp[k].sum<0)
printf("0 %d %d\n",A[0],A[n-1]);
else
printf("%d %d %d\n",dp[k].sum,A[dp[k].start],A[dp[k].end]);
}
return 0;
}
方法二:很巧妙
#include
#include
//#include
using namespace std;
int main(){
int N,i;
//freopen("input.txt","r",stdin);
while(scanf("%d",&N)!=EOF){
long sum=0,Max=-999999999,x;
for(i=0;i
最大加权子矩阵-矩阵压缩
//最大加权矩形变形,只需要矩阵的和大于k来求矩阵的面积!!
#include
using namespace std;
#define maxn 150
int n,m,t;
int matrix[maxn][maxn];
int ans=-21000000;
int temp[maxn];
int dp[maxn];
void Arrsum(){
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
dp[i]=max(dp[i],dp[i-1]+temp[i]);
ans=max(ans,dp[i]);
}
}
void MatrixSum(){
for(int i=1;i<=n;i++){
memset(temp,0,sizeof(temp));
//这个压缩是所有行分级压缩!!
for(int j=i;j<=n;j++){
//temp是每一列的和
for(int k=1;k<=n;k++){
temp[k]=temp[k]+matrix[j][k];
}
//计算行中最大
Arrsum();
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>matrix[i][j];
}
}
MatrixSum();
printf("%d\n",ans);
return 0;
}
最长不下降子序列
//最长不下降子序列,此处不要求连续,只要求是递增,间隔也可,因而双循环
#include
#include
using namespace std;
const int N=100;
int A[N],dp[N];
int main()
{
int n;
while(cin>>n){
for(int i=1;i<=n;i++) cin>>A[i];
int ans=-1; //记录最大的dp[i]
for(int i=1;i<=n;i++){
dp[i]=1; //边界初始条件,先假设每个元素自成一格子序列
//i是最后一个点,就是边界
for(int j=1;j=A[j]&&(dp[j]+1>dp[i])){
dp[i]=dp[j]+1;
}
}
ans=max(ans,dp[i]);
}
printf("%d",ans);
}
return 0;
}
最长不下降子序列应用
/*N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK, 则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。*/
/*动态规划的最长子列问题,分别从前往后和从后往前寻找以i点为尾的最长子列,
寻找两个子列和的最大值*/
#include
#include
using namespace std;
int main()
{
int N,i,j,maxn;
while(cin>>N){
int high[200],marka[200],markb[200];
for(int i=1;i<=N;i++){
cin>>high[i];
marka[i]=markb[i]=1;
//每点为尾的子列长度最小都为1
}
for(i=2;i<=N;i++) /*从前往后寻找以i点为尾的最长递增子列*/
{
maxn=marka[i];
for(j=i-1;j>=1;j--)
{
if(high[i]>high[j])
//问题出在这里啊
maxn=max(maxn,marka[j]+1);
//maxn=(maxn>marka[j]+1)?maxn:marka[j]+1;
}
marka[i]=maxn;
}
for(i=N-1;i>=1;i--) /*从后往前寻找以i点为尾的最长递增子列*/
{
maxn=markb[i];
for(j=i+1;j<=N;j++)
{
if(high[i]>high[j])
// maxn=(maxn>markb[j]+1)?maxn:markb[j]+1;
maxn=max(maxn,markb[j]+1);
}
markb[i]=maxn;
}
maxn=marka[1]+markb[1];
//寻找点i两个子列和的最大值
for(i=2;i<=N;i++){
maxn=max(maxn,marka[i]+markb[i]);
}
cout<
最长公共子序列-字符串A,B
sadstory
adminsorry
#include
#include
#include
#include
#include
#include
#include
using namespace std;
string str1,str2;
int dp[1001][1001];
//记住模板
int lcs(string str1,string str2)
{
int len1=str1.size();
int len2=str2.size();
//0位已经设置为边界,i and j都从1开始
for(int i=1; i<=len1; i++)
{
for(int j=1; j<=len2; j++)
{
if(str1[i-1] == str2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else if(dp[i-1][j] > dp[i][j-1])
dp[i][j] = dp[i-1][j];
else
dp[i][j] = dp[i][j-1];
}
}
return dp[len1][len2];
}
int main()
{
while(cin>>str1)
{
cin>>str2;
//此处为边界,很关键dp[0][j] and d[i][0]均表示字符串0位与其他各字符串的比较
memset(dp, 0, sizeof(dp));
int ans=lcs(str1, str2);
printf("%d\n", ans);
}
return 0;
}
最长公共子序列求具体LCS
/*
dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1] + (A[i]==B[j] ? 1 : 0)),表示在这三种状态中取到最大值,
(1)第一种状态表示不录入第一个序列的第i个字符时的最长公共子序列,
(2)第二种状态表示不录入第二个序列的第j个字符时的最长公共子序列,
(3)第三种状态表示第一个序列的前i-1个字符与第二个序列前j-1个字符的公共子序列加上最后一个字符的录入状态,如果最后的一个字符相等则录入状态为1,否则为0。
然后根据动归的状态,来判断我们要求得的序列中的字符有哪些。
*/
#include
#include
#include
#include
#include
#include
#include
using namespace std;
string str1,str2;
int dp[1001][1001];
//记住模板,在lcs求长度的基础上进行改变,求最长子序列具体
void lcs(string str1,string str2)
{
int len1=str1.size();
int len2=str2.size();
//0位已经设置为边界,i and j都从1开始
for(int i=1; i<=len1; i++)
{
for(int j=1; j<=len2; j++)
{
if(str1[i-1] == str2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else if(dp[i-1][j] > dp[i][j-1])
dp[i][j] = dp[i-1][j];
else
dp[i][j] = dp[i][j-1];
}
}
}
void llcs(){
int i,j,z=0;
string temp;
i=str1.size(),j=str2.size();
//从尾到头的状态进行判断,根据判断可以知道字符加入没有
while(i!=0&&j!=0){
if(str1[i-1]==str2[j-1]){
//只有当两个字符都相等时才可以加入输出字符串
temp[z++]=str1[--i];
j--;
}
else if(dp[i-1][j]=0;i--)
cout<>str1)
{
cin>>str2;
//此处为边界,很关键dp[0][j] and d[i][0]均表示字符串0位与其他各字符串的比较
memset(dp, 0, sizeof(dp));
lcs(str1, str2);
llcs();
}
return 0;
}
hash字符串求最长公共连续子串
/*
求最长公共子串(连续)的方法,
可以用KMP,当然也可以用字符串hash,
分别计算两个字符串的所有子串的hash值,
然后一一对比,当两个字符串的hash值相等时,
如果长度大于之前访问得到的公共子串长度的最大值,
则更新最大值,并存储此子串的起始位置,
最终得到最长的公共子串~
*/
#include
#include <32/bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1010;
const LL p=1e7+19;
const LL mod=1e9+7;
LL powP[maxn],H1[maxn]={0},H2[maxn]={0};
struct Node{
int hashValue,length,start,end;
Node(int a,int b,int c,int d):hashValue(a),length(b),start(c),end(d){};
};
vector pr1,pr2;
void init(int len)
{
powP[0]=1;
for(int i=1;i<=len;++i){
powP[i]=(powP[i-1]*p)%mod;
}
}
void calH(LL H[],string &str){
H[0]=str[0];
for(int i=1;i &p){
for(int i=0;ians)
{
ans=pr1[i].length;
str=str1.substr(pr1[i].start,pr1[i].end);
}
}
}
return str;
}
int main()
{
string str1,str2;
getline(cin,str1);
getline(cin,str2);
init(max(str1.length(),str2.length()));
calH(H1,str1);
calH(H2,str2);
calSubH(H1,str1.length(),pr1);
calSubH(H2,str2.length(),pr2);
cout << getMax(str1) << endl;
return 0;
}
最长公共子串是最长公共子序列的的特殊情况
public static int lcs(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int result = 0; //记录最长公共子串长度
int c[][] = new int[len1+1][len2+1];
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1.charAt(i-1) == str2.charAt(j-1)) {
c[i][j] = c[i-1][j-1] + 1;
result = max(c[i][j], result);
} else {
c[i][j] = 0;
}
}
}
return result;
}
最长回文子序列
方法一:递归方法,自顶向下
str[0...n-1]是给定的字符串序列,长度为n,假设lps(0,n-1)表示序列str[0...n-1]的最长回文子序列的长度。
1.如果str的最后一个元素和第一个元素是相同的,则有:lps(0,n-1)=lps(1,n-2)+2;例如字符串序列“AABACACBA”,第一个元素和最后一个元素相同,其中lps(1,n-2)表示红色部分的最长回文子序列的长度;
2.如果str的最后一个元素和第一个元素是不相同的,则有:lps(0,n-1)=max(lps(1,n-1),lps(0,n-2));例如字符串序列“ABACACB”,其中lps(1,n-1)表示去掉第一元素的子序列,lps(0,n-2)表示去掉最后一个元素的子序列。
#include
#include
#include
using namespace std;
int lps(string str,int i,int j){
//递归出口
if(i==j)
return 1; //只有一个元素,回文长度为1
if(i>j)
return 0; //字符序列str[i...j]
//如果首尾相同
if(str[i]==str[j])
return lps(str,i+1,j-1)+2;
//如果首尾不相同
return max(lps(str,i,j-1),lps(str,i+1,j));
}
int main()
{
string str;
while(cin>>str){
int n=str.size();
int ans=lps(str,0,n-1);
cout<
方法二:自底向上动态规划方法
#include
#include
#include
#include
using namespace std;
int dp[1000][1000];
int lpsDp(string str,int n){
int temp;
memset(dp,0,sizeof(dp));
//边界条件
for(int i=0;i>str){
int ans=lpsDp(str,str.size());
cout<
最长回文子串,连续的!!
动态规划法:
回文字符串的子串也是回文,比如P[i,j](表示以i开始以j结束的子串)是回文字符串,那么P[i+1,j-1]也是回文字符串。这样最长回文子串就能分解成一系列子问题了。这样需要额外的空间O(N2),算法复杂度也是O(N2)。
首先定义状态方程和转移方程:
P[i,j]=false:表示子串[i,j]不是回文串。P[i,j]=true:表示子串[i,j]是回文串。
P[i,i]=true:当且仅当P[i+1,j-1] = true && (s[i]==s[j])
否则p[i,j] =false;
PATZJUJZTACCBCC
#include
#include
#include
using namespace std;
bool dp[1000][1000];
int maxlength=0;
string flp(string s){
int len=s.size();
int start=0;
//子串长度为1和2的初始化,边界条件
for(int i=0;i0)
return s.substr(start,start+maxlength-1);
return NULL;
}
int main()
{
string str;
while(cin>>str){
memset(dp,0,sizeof(dp));
string ans=flp(str);
cout<
暴力法:
遍历字符串S的每一个子串,去判断这个子串是不是回文,是回文的话看看长度是不是比最大的长度maxlength大。遍历每一个子串的方法要O(n2),判断每一个子串是不是回文的时间复杂度是O(n),所以暴利方法的总时间复杂度是O(n3)。
PATZJUJZTACCBCC
#include
#include
using namespace std;
int maxlength=0;
string flp(string str){
int len=str.size();
int start=0;
for(int i = 0; i < len; i++){
for(int j = i + 1; j < len; j++){
int index1 = 0;
int index2 = 0;
// 对每个子串都从两边开始向中间遍历
for(index1 = i, index2 = j; index1 < index2; index1 ++, index2--) {
if(str[index1] != str[index2])
break;
}
// 若index1=index2,表示串是类似于abcba这种类型;若大于,则是abccba这种类型
if(index1 >= index2 && j - i > maxlength){
maxlength = j - i + 1;
start = i;
}
}
}
if(maxlength>0)
return str.substr(start,start+maxlength-1);
return NULL;
}
int main()
{
string str;
while(cin>>str){
string ans=flp(str);
cout << ans<< endl;
cout << maxlength<< endl;
}
return 0;
}
中心扩展法:
中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)。
但是要考虑两种情况:
1、像aba,这样长度为奇数。
2、想abba,这样长度为偶数
#include
#include
using namespace std;
string flp(string str){
int len=str.size();
int maxlength=0,start=0;
//先处理aba情况
for(int i=0;i=0 && kmaxlength){
maxlength=k-j+1;
start=j;
}
j--;
k++;
}
}
//类似于abba情况,以i,i+!为中心两边扩展
for(int i=0;i=0 && kmaxlength){
maxlength=k-j+1;
start=j;
}
j--;
k++;
}
}
if(maxlength>0)
return str.substr(start,start+maxlength-1);
}
int main()
{
string str;
while(cin>>str){
string ans=flp(str);
cout << ans<< endl;
}
return 0;
}
判断是否为回文串:
#include
#include
#include
using namespace std;
//从中间往两边扫,就有两种情况
int main()
{
string str;
while(cin>>str){
int len=str.size();
int mid =len/2;
bool flag=true;
if(len%2==1){
for(int i=0;mid+i
#include
int main()
{
char a[300];
while(gets(a))
{
int len=strlen(a);
bool flag=1;
for(int i=0;i
实际应用,输出回文子串
Confuciuss say:Madam,I'm Adam.
//最长回文子串暴力法
#include
#include
#include
#include
using namespace std;
bool dp[5010][5010];
int pos[5010];
string str;
string flp(char s[]){
int maxlength=0;
int len=strlen(s);
int start=0,end=0;
//子串长度为1和2的初始化,边界条件
//dp[i][j] 表示以i开始,j结尾的子串是否是回文字符串!!,则dp[i+1][j-1]也是回文字符串
for(int i=0;i0)
return str.substr(start,end-start+1);
return NULL;
}
int main()
{
//终于知道哪里错了
while(getline(cin,str)){
memset(dp,0,sizeof(dp));
memset(pos,0,sizeof(pos));
char temp[5010];
int num=0;
for(int i=0;i
01背包问题
/*
01背包
5 8 //n==5,V==8
3 5 1 2 2 //w[i]
4 5 2 1 3 //c[i]
//表示用重量8达到最大价值c
分为两种策略,
不放第i件物品,即i-1件物品满足条件 dp[i-1][v]
放第i件物品 dp[i-1][v-w[i]]+c[i]
又由于dp[i][v]总是只需要dp[i-1][v]左半部分
故可以直接开一个dp[v]数组
但是枚举方向该位i从1到n,v从V到0(逆序!!!)
dp[v]=max(dp[v],dp[v-w[i]]+c[i])
*/
#include
#include
using namespace std;
const int maxn=100;
const int maxv=1000;
int w[maxn],c[maxn],dp[maxv];
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i>w[i];
}
for(int i=0;i>c[i];
}
//边界
for(int v=0;v<=V;v++){
dp[v]=0;
}
for(int i=0;i=w[i];v--){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}
int maxans=0;
for(int v=0;v<=V;v++){
if(dp[v]>maxans){
maxans=dp[v];
}
}
cout<
完全背包-拆分整数成2的幂的和
/*这道题确实可以用动态规划来解,它其实是一个完全背包求恰装满背包时的方案总数
问题.具体是,因为每一个拆分必须是1,2,4,2^3,...2^19(考虑n最大为10^6),
所以对于一个整数n,看它的这种拆分数有多少个,就相当于现在有20种物品,第i种物品
的花费是2^(i-1),每一种可以重复取, dp[i][j]表示前i种物品恰装满容量为j的物品时
的方案总数,从而dp[i][j] = dp[i-1][j] + dp[i][j-a[i]]
*/
#include
#include
using namespace std;
int n,dp[1000002],a[21];
int main()
{
int i,j,t;
for(i=1;i<=20;i++){
a[i]=(1 << (i-1));
}
dp[0]=1;
while(cin>>n){
memset(dp+1,0,sizeof(dp));
for(i=1;i<=20;i++){
for(j=a[i];j<=n;j++){
//终于明白滚动数字的含义,二维降一维
dp[j]=dp[j]+dp[j-a[i]];
dp[j]=dp[j]%1000000000;
}
}
cout<
动态规划解决括号最长匹配长度
/*方法一没怎么看懂,不过dp[i]应当表示以i号开头时最长长度*/
#include
#include
using namespace std;
int main()
{
string str;
cin>>str;
int maxn=0;
vector dp(str.size(),0);
//vector高级用法,快速初始化
for(int i=str.size()-2;i>=0;i--){
if(str[i]=='('){
int j=i+1+dp[i+1];
if(j
方法二:更容易理解,但是一用例居然没有通过
#include
#include
using namespace std;
int maxAvailiable(string str){
int maxn=0;
int i,j;
int len=str.size();
int *dp=(int *)malloc(sizeof(int)*len);
memset(dp,0,sizeof(int)*len);
//至少有两个才可以匹配,故从i=2开始
for(int i=2;i=0&&str[j]=='('){
dp[i]=dp[i-1]+2;
if(j>0)
//终于懂这里了,j前面的也算是匹配了
dp[i]=dp[i]+dp[j-1];
}
maxn=maxn>str;
cout <
动态规划花钱类问题两种思路
动态规划自底向上方法
/*
若干邮票,选取最小张数凑成一个给定总值
不是贪心算法,居然是0-1背包,求的是最小数量
dp[i][j]表示i个邮票,j表示总量面额,dp表示最小邮票数量
*/
/*
最少邮票数 >> 01动态规划
状态
集合中数字
dp[i][j] 0 1 2 3 4 5 6 7 8 9 10
1 0 1 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞
1 3 0 1 ∞ 1 2 ∞ ∞ ∞ ∞ ∞ ∞
1 3 3 0 1 ∞ 1 2 ∞ 2 3 ∞ ∞ ∞
1 3 3 3 0 1 ∞ 1 2 ∞ 2 ∞ ∞ 3 4
1 3 3 3 4 0 1 ∞ 1 2 2 2 2 3 3 3
状态迁移方程
dp[j] = min{dp[j],dp[j-stamp[i]]+1}
其中dp[j-stamp[i]]+1,表示将第i个邮票加入集合后 凑总量为j的面额 所需要的最少邮票数量
*/
#include
#include
using namespace std;
#define INF 1000
int stamp[1000];
int dp[1000];
int minStamp(int num,int deno){
//邮票个数和总额
int i,j;
//状态全部初始化
for(j=0;j<=deno;j++){
dp[j]=(j==0)?0:INF;
}
for(i=0;i=stamp[i];j--){
if(dp[j-stamp[i]]!=INF)
dp[j]= (dp[j]>deno>>num){
for(int i=0;i>stamp[i];
cout<
递归方法,自顶向下:
/*
递归法解决问题,自顶向下方法,
动态规划确实自底向上!!一个问题两个方向
*/
#include
using namespace std;
int M; //邮票总价值
int N; //n张邮票
int minn; //最小票数
void compute(int data[],int m,int n,int num){
//m是递归中几张邮票面值的和,n表示data还未参与递归的最高位,num表示m由num张邮票相加得到
int i;
if(m==M){
if(numM) return;
for(i=n;i>=0;i--)
//更好理解
compute(data,m+data[i],i-1,num+1);
}
int main()
{
int i,j,k;
while(cin>>M>>N){
minn=N+1;
int data[20];
for(i=0;i>data[i];
}
compute(data,0,N-1,0);
if(minn==N+1)
//如果可以凑成M,则minn<=N;
printf("0\n");
else
printf("%d\n",minn);
}
return 0;
}
递归+动态规划解决组合数问题
/*
放苹果,递归+动态规划
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,
问共有多少种不同的分法?
(用K表示)5,1,1和1,5,1 是同一种分法。
*/
/*
M个苹果放在N个盘子里分法有:dp[M][N], 0 <= M,N <= 10
设dp(m,n) 为m个苹果,n个盘子的放法数目,则先对n作讨论,
当m=n:不同的放法可以分成两类:
1、有至少一个盘子空着,即相当于dp(m,n) = dp(m,n-1);
2、所有盘子都有苹果,相当于可以从每个盘子中拿掉一个苹果,不影响不同放法的数目,即dp(m,n) = dp(m-n,n).
而总的放苹果的放法数目等于两者的和,即 dp(m,n) =dp(m,n-1)+dp(m-n,n)
初始条件说明
当m=0,n=0时,没苹果,没盘子,定为0种放法。这个条件在计算的时候用不到。题设至少一个盘子一个苹果。
当m=0,n>0时,没苹果,有盘子,定为1种放法。这个有点抽象,考虑:dp[1][1]=dp[1][0]+dp[0][1]=0+1。
当m>0,n=0时,有苹果,没盘子,定为0种放法。
dp两条路,第一条n会逐渐减少,终会到达出口n=0;
第二条m会逐渐减少,因为n>m时,会计算dp(m,m) 所以终会到达出口m=0.
*/
/*#include
using namespace std;
int dfs(int m,int n){
if(m>=0&&n==0)
return 0;
else if(m==0&&n>0)
return 1;
else if(m>=n)
return dfs(m,n-1)+dfs(m-n,n);
else
return dfs(m,m);
}
int main()
{
int m,n;
while(cin>>m>>n){
cout<
using namespace std;
int dp[11][11];
int main(){
int m,n;
while(cin>>m>>n){
for(int i=1;i<=m;i++){
dp[i][0]=0;
}
for(int j=1;j<=n;j++){
dp[0][j]=1;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i>=j)
dp[i][j]=dp[i][j-1]+dp[i-j][j];
else
dp[i][j]=dp[i][i];
}
}
cout<