最长上升子序列
代码【O(n*n)】
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)dp[i]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);//严格上升
//if(a[j]<=a[i])dp[i]=max(dp[i],dp[j]+1);非严格
}
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
优化【O(nlogn)】
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+6;
#define ll long long
int d[maxn];
int main(){
int n,x;
cin>>n;
int cnt=0;
for(int i=0;i<n;i++){
cin>>x;
if(x>d[cnt])d[++cnt]=x;
else{
int pi=upper_bound(d+1,d+1+cnt,x)-d;
d[pi]=x;
}
}
cout<<cnt<<"\n";
}
d[i]:当前由数组中构成的lis长为i的最小结尾数据。
性质
由数字1到n的排列使得最长上升子序列长度为n-1共有(n-1)*(n-1)种。
最长公共子序列(LCS)
基本知识
只求长度
串s和串t:
dp[n][m]:s的n长前缀和t的m长前缀的最长公共子序列长度。
1.if(i==0||j==0)dp[i][j]=0;
2.if(s[i-1]==t[j-1])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
3.if(s[i-1]!=t[j-1])dp[i][j]=max(dp[i][j],max(dp[i-1][j],dp[i][j-1]));
输出一个最大LCS串
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=3e3;
string s,t;
int ps[maxn],pt[maxn];
int dp[maxn][maxn];
struct Node{
int ps,pt;
}node[maxn][maxn];
stack<char>st;
int main(){
cin>>s>>t;
int ls,lt;
ls=s.size();
lt=t.size();
int LEN=max(ls,lt);
for(int i=0;i<=LEN;i++){
dp[i][0]=dp[0][i]=0;
}
for(int i=1;i<=ls;i++){
for(int j=1;j<=lt;j++){
if(s[i-1]==t[j-1]){
if(dp[i][j]<dp[i-1][j-1]+1){
dp[i][j]=dp[i-1][j-1]+1;
node[i][j].ps=i-1;
node[i][j].pt=j-1;
}
}else{
if(dp[i][j]<dp[i-1][j]){
dp[i][j]=dp[i-1][j];
node[i][j]=node[i-1][j];
}
if(dp[i][j]<dp[i][j-1]){
dp[i][j]=dp[i][j-1];
node[i][j]=node[i][j-1];
}
}
}
}
int pi,pj;
pi=ls;pj=lt;
int cnt=0;
while(cnt<dp[ls][lt]){
cnt++;
st.push(s[node[pi][pj].ps]);
Node tmp=node[pi][pj];
pi=tmp.ps;
pj=tmp.pt;
}
while(!st.empty()){
cout<<st.top();
st.pop();
}
}
优化
dp[i][j]的值只和dp[i-1][j-1],dp[i-1][j],dp[i][j-1]有关。
现删去一维,保留第二维。当对dp[j]进行讨论时,即dp[j]未赋值之前,那么dp[j]其实是dp[i-1][j],而dp[j-1]是dp[i][j-1],这时候只少了dp[i-1][j-1],其实就是未更新的dp[j-1],所以在计算dp[j-1]之前用last记录一下其值即可。
空间
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1e5+6;
#define ll long long
int dp[maxn];
int main(){
string s,t;
cin>>s>>t;
int ls,lt;
ls=s.size();
lt=t.size();
for(int i=1;i<=ls;i++){
int last=0;
for(int j=1;j<=lt;j++){
int tmp=dp[j];
if(s[i-1]==t[j-1]){
dp[j]=max(dp[j],last+1);
}else{
dp[j]=max(dp[j],dp[j-1]);
}
last=tmp;
}
}
cout<<dp[lt]<<"\n";
}
时间
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+6;
#define ll long long
vector<int>vc[maxn];
int s[maxn],t[maxn];
int d[maxn];
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)scanf("%d",&s[i]);
for(int i=0;i<n;i++)scanf("%d",&t[i]);
for(int i=n-1;i>=0;i--){
vc[t[i]].push_back(i+1);
//记录t串中每一个字符出现过的位置,并降序储存。
}
//以下为优化了的LIS
int pos=0;
for(int i=0;i<n;i++){
int zf=s[i];
for(int j=0;j<vc[zf].size();j++){
int x=vc[zf][j];
if(x>d[pos])d[++pos]=x;
else{
int pi=upper_bound(d+1,d+1+pos,x)-d;
d[pi]=x;
}
}
}
cout<<pos<<"\n";
}
当串中每个字符只出现一次的话:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+6;
#define ll long long
int pos[maxn];
int d[maxn];
int main(){
int n,x;
cin>>n;
for(int i=0;i<n;i++){
cin>>x;
pos[x]=i+1;
}
int cnt=0;
for(int i=0;i<n;i++){
cin>>x;
x=pos[x];
if(x>d[cnt])d[++cnt]=x;
else{
int pi=upper_bound(d+1,d+1+cnt,x)-d;
d[pi]=x;
}
}
cout<<cnt<<"\n";
}
最长公共子串
最大字段和
基础
即找一个连续段,其和最大。原序列存在数组a中。
d[i]:下标i结尾的连续段的最大和。
d[i]=max(d[i-1]+a[i],a[i])
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int ans,sum;
ans=-inf;
sum=0;
for(int i=1;i<=n;i++){
sum=max(sum+a[i],a[i]);
ans=max(sum,ans);
}
二维+动态修改
线段树维护二维最大字段和
hdu 6638:http://acm.hdu.edu.cn/showproblem.php?pid=6638
#define ll long long
const int maxn=3000;
ll mx[maxn*4],pre[maxn*4],last[maxn*4],sum[maxn*4];
void pushup(int x){
sum[x]=sum[2*x]+sum[2*x+1];
mx[x]=max(max(mx[2*x],mx[2*x+1]),last[2*x]+pre[2*x+1]);
pre[x]=max(pre[2*x],sum[2*x]+pre[2*x+1]);
last[x]=max(last[2*x+1],sum[2*x+1]+last[2*x]);
}
void add(int x,int l,int r,int pos,ll val){
if(l==r){
mx[x]+=val;
sum[x]=pre[x]=last[x]=mx[x];
return;
}
int mid=(l+r)>>1;
if(pos<=mid)add(2*x,l,mid,pos,val);
else add(2*x+1,mid+1,r,pos,val);
pushup(x);
}
void init(int x,int l,int r){
mx[x]=pre[x]=last[x]=sum[x]=0;
if(l==r){
return;
}
int mid=(l+r)>>1;
init(2*x,l,mid);
init(2*x+1,mid+1,r);
}
struct Nt{
int x,y;
ll w;
bool operator <(const Nt&o)const{
if(y==o.y)return x<o.x;
return y<o.y;
}
}nt[maxn];
int NUM_x[maxn],NUM_y[maxn];
void work(){
int n;
scanf("%d",&n);
int xx,yy;
ll ww;
for(int i=1;i<=n;i++){
scanf("%d%d%lld",&xx,&yy,&ww);
NUM_x[i]=xx;NUM_y[i]=yy;
nt[i]=(Nt){xx,yy,ww};
}
sort(NUM_x+1,NUM_x+1+n);
int lx=unique(NUM_x+1,NUM_x+1+n)-(NUM_x+1);
sort(NUM_y+1,NUM_y+1+n);
int ly=unique(NUM_y+1,NUM_y+1+n)-(NUM_y+1);
for(int i=1;i<=n;i++){
nt[i].x=lower_bound(NUM_x+1,NUM_x+1+lx,nt[i].x)-(NUM_x);
nt[i].y=lower_bound(NUM_y+1,NUM_y+1+ly,nt[i].y)-(NUM_y);
}
sort(nt+1,nt+1+n);
int cx=1;
ll ans=0;
for(int i=1;i<=ly;i++){
init(1,1,lx);
int poi=cx;
for(int j=i;j<=ly;j++){
while(poi<=n&&nt[poi].y==j){
add(1,1,lx,nt[poi].x,nt[poi].w);
poi++;
}
if(i==j)cx=poi;
ans=max(ans,mx[1]);
}
}
printf("%lld\n",ans);
}
int main(){
int T;
cin>>T;
while(T--){
work();
}
}
最大m字段和
在n元素数组中取m个连续段,求其和的最大值。【这m个连续段互不重合即可】
当m==1时,即为最大字段和。
dp [i] [j]:前j个元素分成i段的答案
dp [i] [j] = max(dp [i] [j] , dp [i] [j-1] + a [j] ),i<=j-1【相当于i != j,这里wa了好几发】
dp [i] [j] = max(dp [i] [j] , dp [i-1] [k] + a [j] ),i-1<=k<=j-1
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1024
用到了滚动数组
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e6+6;
const int inf=0x3f3f3f3f;
int a[maxn];
int dp[maxn][3];
int main(){
int m,n;
while(~scanf("%d%d",&m,&n)){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
memset(dp,0,sizeof(dp));
int ans=-inf;
for(int j=1;j<=m;j++){
int mx=dp[j-1][(j+1)%2];
for(int i=j;i<=n;i++){
int tmp=-inf;//tmp=0会报错
if(i-1>=j)
tmp=max(dp[i-1][j%2]+a[i],tmp);
tmp=max(tmp,mx+a[i]);
dp[i][j%2]=tmp;
mx=max(mx,dp[i][(j+1)%2]);
}
}
for(int i=m;i<=n;i++){
ans=max(ans,dp[i][m%2]);
}
printf("%d\n",ans);
}
}
//dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i]) i-1>=j
//dp[i][j]=max(dp[i][j],dp[k][j-1]+a[i]) j-1<=k<i-1
const int maxn=1e6+6;
const int inf=0x3f3f3f3f;
int a[maxn];
int dp[3][maxn];
void work(int n,int m){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
memset(dp,0,sizeof(dp));
int mx;
for(int i=1;i<=m;i++){
mx=dp[(i+1)%2][i-1];
for(int j=i;j<=n;j++){//n亦可变成n-m+i,减少不必要次数
if(i==j)
dp[i%2][j]=mx+a[j];
else
dp[i%2][j]=max(mx,dp[i%2][j-1])+a[j];
mx=max(mx,dp[(i+1)%2][j]);
}
}
int ans=-inf;
for(int i=m;i<=n;i++){
ans=max(ans,dp[m%2][i]);
}
printf("%d\n",ans);
/* 或者:
for(int i=1;i<=m;i++){
mx=-inf;
for(int j=i;j<=n;j++){
dp[0][j]=max(dp[1][j-1],dp[0][j-1])+a[j];
dp[1][j-1]=mx;
mx=max(mx,dp[0][j]);
}
}
printf("%d\n",mx);
*/
}
int main(){
int n,m;
while(~scanf("%d%d",&m,&n)){
work(n,m);
}
}
最小字段和
各元素求相反数,再求一个最大字段和即可,答案即是所求的相反数。
最小正字段和
求一个连续段,只要和为正即可,然后在这些连续段中,找出和最小的那个。
方法:计算前缀和后,将之升序排序,于是每相邻的两个前缀和之差最小(若位置ok,那么差值即为该段之和。需要注意若前缀和大于0本身也是答案之一)。
题目:https://vjudge.net/problem/51Nod-1065
具体看代码:
#define ll long long
const int maxn=6e5+66;
const int inf=0x3f3f3f3f;
struct NT{
ll x;
int id;
bool operator <(const NT&o)const{
if(x==o.x)return id<o.id;
return x<o.x;
}
}nt[maxn];
int main(){
int n;
scanf("%d",&n);
int x;
nt[0].x=0;
nt[0].id=0;
for(int i=1;i<=n;i++){
scanf("%d",&x);
nt[i]=(NT){x+nt[i-1].x,i};
}
ll ans=inf;
sort(nt+1,nt+1+n);
for(int i=1;i<=n;i++){
if(nt[i].x>0)ans=min(ans,nt[i].x);//这里别忘了
if(nt[i].x>nt[i-1].x&&nt[i].id>nt[i-1].id){
ans=min(ans,nt[i].x-nt[i-1].x);
}
}
printf("%lld\n",ans);
}
最大子矩阵和
类似二维的最大字段和。
在一个大矩阵中找到一个子矩阵,其和最大。
a[i][j]:1~i行的j列的和。
枚举上下边界i、j,然后将每一列的i行到j行的值加起来,于是二维变一维,剩下的就是求最大字段和。
int x;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&x);
a[i][j]=a[i-1][j]+x;
}
}
int ans=-inf;
for(int i=1;i<=n;i++){//上界
for(int j=i;j<=n;j++){//下界
int sum=0;
for(int k=1;k<=m;k++){//夹在上下界之间的列
sum=max(sum+a[j][k]-a[i-1][k],a[j][k]-a[i-1][k]);
ans=max(ans,sum);
}
}
}
复杂度为 n3。
01背包:
每个物品只有一件,各自有各自重量以及价值。一个背包可装一定重量,问如何选择物品,使得背包装的价值最多且不超重。
//物品n个,背包最多装m重。
//f[i][j]表示前i件物品,总重量不超过j的最大价值
for(int i=1;i<=n;i++)
for(int j=m;j>0;j--){
if(w[i]<=j)
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
else f[i][j]=f[i-1][j];
}
//滚掉一维后:
//f[j]表示重量不超过j公斤的最大价值
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){//倒着的!
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
完全背包:
基本条件同01背包,只是完全背包的物品有无限个,即可以一个物品取无限次。
for(int i=1;i<=n;i++){
for(int j = w[i];j <= m;j++){//正着来的,与01背包不同之处
f[j] = max(f[j], f[j-w[i]]+v[i]);
}
}
多重背包:
基本条件同01背包,只是多重背包的物品个数有限,即可以一个物品只可取有限次。
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)//倒着来的,同01背包
for(int k=0;k<=c[i];k++){//取物品i取k次
if(j-k*w[i]<0)break;
f[j] = max(f[j], f[j-k*w[i]]+k*v[i]);
}