一、动态规划模块
1.最长上升子序列
注意:这个子序列(subsequence)和子段(subsegment)是有区别的,后者是连续的
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
int a[5000];
int dp[5000];
int main()
{
int n;
int max=-2;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
dp[i]=1;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j]&&dp[j]+1>dp[i])
{
dp[i]=dp[j]+1;
}
}
if(dp[i]>max)
max=dp[i];
}
cout<<max<<endl;
return 0;
}
深度思考
(1)题目描述设有由n(1≤n≤200)个不相同的整数组成的数列,记为:b(1)、b(2)、……、b(n)若存在i1<i2<i3<…<ie 且有b(i1)<=b(i2)<=…<=b(ie)则称为长度为e的不下降序列。程序要求,当原数列出之后,求出最长的不下降序列。
例如13,7,9,16,38,24,37,18,44,19,21,22,63,15。例中13,16,18,19,21,22,63就是一个长度为7的不下降序列,同时也有7 ,9,16,18,19,21,22,63组成的长度为8的不下降序列。
输入
第一行为n,第二行为用空格隔开的n个整数。
输出
第一行为输出最大个数max(形式见样例);
第二行为max个整数形成的不下降序列,答案可能不唯一,输出一种就可以了,本题进行特殊评测。
#include <iostream>
#include <cmath>
using namespace std;
int a[500];
int dp[500];
int b[500];
int main()
{
int n,max=-1;
int k;
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
dp[i]=1;
for(int i=2; i<=n; i++)
{
for(int j=1; j<i; j++)
{
if(a[i]>=a[j]&&(dp[j]+1>dp[i]))
{
dp[i]=dp[j]+1;
}
if(dp[i]>max)
{
max=dp[i];
k=i;//最长不下降子序列的最后一个元素位置
}
}
}
cout<<"max="<<max<<endl;
/*本质就是对上面操作的逆序*/
int maxs,u;int c=0;
//u作为遍历元素
maxs=max;
u=k-1;
b[++c]=k;//a[b[i]]
while(maxs>1)
{
if(dp[u]==maxs-1&&a[k]>=a[u])
{
b[++c]=u;
maxs--;
k=u;//如果这里是k--,这是行不通的
u--;
}
else u--;
}
for(int i=max;i>=1;i--)
cout<<a[b[i]]<<" ";
return 0;
}
(2)题目描述某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入:
输入导弹依次飞来的高度。
输出:
第一行:最多能拦截的导弹数;//就是最长下降子序列
第二行:要拦截所有导弹最少要配备的系统数。//就是最长不下降子序列
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
int a[5000];
int b[2500];
int dp[5000];
int main()
{
//ios::sync_with_stdio(false);
int max=-1,maxe=-1;
int n,t=1;
while(scanf("%d",&n)!=EOF)
{
a[t]=n;
t++;
}
for(int i=1; i<=t-1; i++)
dp[i]=1;
for(int i=2; i<=t-1; i++)
{
for(int j=1; j<i; j++)
{
if(a[i]<=a[j]&&dp[j]+1>dp[i])
{
dp[i]=dp[j]+1;
}
}
if(dp[i]>max)
{ max=dp[i];
}
}
cout<<max<<endl;
memset(dp,0,sizeof(dp));
for(int i=1; i<=t-1; i++)
dp[i]=1;
for(int i=2;i<=t-1;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j]&&dp[j]+1>dp[i])
dp[i]=dp[j]+1;
}
if(dp[i]>maxe)
maxe=dp[i];
}
cout<<maxe<<endl;
return 0;
}
2.最长公共子序列个数
#include<iostream>
using namespace std;
char a[N],b[N];
int f[N][N];
int main()
{
scanf("%s%s",&a[1],&b[1]);
int len_a=strlen(&a[1]);
int len_b=strlen(&b[1]);
for(int i=0;i<=len_a;i++)
f[i][0]=0;
for(int i=0;i<=len_b;i++)
f[0][i]=0;
for(int i=1;i<=len_a;i++)
for(int j=1;j<=len_b;j++)
if(a[i]==b[j])
f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=max(f[i-1][j],f[i][j-1]);
cout<<f[len_a][len_b]<<endl;
return 0;
}
3.最大子段和
int max = 0;
for(int i = 1; i <= n; i ++){
if(dp[i-1] > 0){
dp[i] = dp[i-1] + A[i];
}else{
dp[i] = A[i];
}//dp[i]=max(dp[i-1]+a[i],a[i])
if(dp[i]> max){
max = dp[i];
}
}
//时间复杂度为O(n)
4.最大子矩阵和
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define INF 0x3f3f3f3f
#define N 1001
using namespace std;
int a[N][N],f[N];
int maxArray(int t[],int n)//这个函数作用是对这个列向量进行遍历,至到找到最大值
int sum=0,maxx=-INF;
for(int i=1; i<=n; i++)
{
if(sum>0)
sum+=t[i];
else
sum=t[i];
if(sum>maxx)
maxx=sum;
}
return maxx;
}
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
cin>>a[i][j];
int maxx=-INF;//最小的
for(int i=1; i<=n; i++)
{
memset(f,0,sizeof(f));
for(int j=i; j<=n; j++)
{
for(int k=1; k<=n; k++)
f[k]+=a[j][k];//把一个子矩阵化成一个列向量
int temp=maxArray(f,n);
if(temp>maxx)
maxx=temp;//总体找最大值
}
}
cout<<maxx<<endl;
return 0;
}
5.poj_2479_Maximum sum(最大两子段和)
题意
在一段数中找出两个连续子段,使其和最大。
分析:
可以联想到求最大子段和的方法,此题就是以此为基础的。
先从左向右做一遍求最大子段和的DP,得到数组dp1[ ];
再从右向左做一遍求最大子段和的DP,得到数组dp2[ ]。
然后更新一下这两个数组,更新方法为:
对于数组dp1[ ],若dp1[i-1]>dp1[i],dp[i]=dp1[i-1],否则不变;
对于数组dp2[ ],若dp2[i+1]>dp2[i],dp[i]=dp2[i+1],否则不变。
以样例为例:
1 -1 2 2 3 -3 4 -4 5 -5
得到
dp1----------->1 1 2 4 7 7 8 8 9 9
dp2----------->9 9 9 7 5 5 5 5 5 -5
最后枚举1~n-1,求dp1[i]+dp2[i+1]的最大值,即为答案。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int dp1[50050],dp2[50050],a[50050];
int main(){
int n,t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
dp1[0]=0;
for(int i=1;i<=n;i++){
if(dp1[i-1]>0)
dp1[i]=dp1[i-1]+a[i];
else dp1[i]=a[i];
}
dp2[n+1]=0;
for(int i=n;i>0;i--){
if(dp2[i+1]>0)
dp2[i]=dp2[i+1]+a[i];
else dp2[i]=a[i];
}
for(int i=2;i<=n;i++){
if(dp1[i]<dp1[i-1]) dp1[i]=dp1[i-1];
}
for(int i=n-1;i>=1;i--){
if(dp2[i]<dp2[i+1]) dp2[i]=dp2[i+1];
}
int bigsum=dp1[1]+dp2[2];
for(int i=1;i<n;i++){
bigsum=max(bigsum,dp1[i]+dp2[i+1]);
}
printf("%d\n",bigsum);
}
return 0;
}
6.复制书稿
【题目描述】
现在要把m本有顺序的书分给k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三和第四本书给同一个人抄写。
现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。
【输入】
第一行两个整数m,k;(k≤m≤500)
第二行m个整数,第i个整数表示第i本书的页数。
【输出】
共k行,每行两个整数,第i行表示第i个人抄写的书的起始编号和终止编号。k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。
【输入样例】
9 3
1 2 3 4 5 6 7 8 9
【输出样例】
1 5
6 7
8 9
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstdlib>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define N 101
#define MOD 2520
#define E 1e-12
using namespace std;
int a[N],b[N],c[N][N];
int sum[N],f[N][N];
int main()
{
int m,n;
cin>>m>>n;
for(int i=1;i<=m;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
sum[0]=0;
memset(f,INF,sizeof(f));
for(int i=1;i<=m;i++)
f[1][i]=sum[i];
//分段取大,状态取小
for(int i=2;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=i-1;k<j;k++)
f[i][j]=min(f[i][j],max(f[i-1][k],sum[j]-sum[k]));
int maxx=f[n][m];
int temp=n;
for(int i=m;i>0;i--)
{
if(a[i]+b[temp]>maxx)
temp--;
b[temp]+=a[i];
c[temp][++c[temp][0]]=i;
}
for(int i=1;i<=n;i++)
cout<<c[i][c[i][0]]<<" "<<c[i][1]<<endl;
return 0;
}
2.cf上的题
题目来源(点击这里)
ac代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int a,b;
cin>>a>>b;
int n=a+b;
string s;
cin>>s;
for(int i=0; i<n; i++)
{
if(s[i]=='?')s[i]=s[n-i-1];
}
a-=count(s.begin(),s.end(),'0');
b-=count(s.begin(),s.end(),'1');
for(int i=0; i<=n/2; i++)
{
if(s[i]=='?' and i!=n-i-1)
{
if(a>1)
{
s[i]=s[n-i-1]='0';
a-=2;
}
else if(b>1)
{
s[i]=s[n-i-1]='1';
b-=2;
}
}
else if(s[i]=='?')
{
if(a)
{
s[i]='0';
a--;
}
else
{
s[i]='1';
b--;
}
}
}
string ans=s;
reverse(ans.begin(),ans.end());
if(ans==s and a==b and b==0)cout<<s<<endl;
else cout<<"-1"<<endl;
}
return 0;
}