写在前面
“模板库”这一系列文章用来复习
模板
由于时间原因,作者无法一一亲自调试其中的程序,也因如此,有一部分程序来自于互联网,如果您觉得这侵犯了您的合法权益,请联系
删除。
对于给您造成的不便和困扰,我表示深深的歉意。
本系列文章仅用于学习,禁止任何人或组织用于商业用途。
本系列文章中,标记*的为选学算法,在
中较少涉及。
动态规划
简单线性动态规划
LIS(最长上升子序列)
【简介】
最长上升子序列( , ),在计算机科学上是指一个序列中最长的单调递增的子序列。
【代码实现】
朴素方法
#include<cstdio>
#include<iostream>
using namespace std;
int n,a[5001],f[5001]={0,1},ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++)
if(a[j]<a[i])f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}
复杂度
贪心实现
#include<cstdio>
#include<algorithm>
using namespace std;
int n,f[1000001],len,x;
int main(){
scanf("%d%d",&n,&x);
f[++len]=x;
for(int i=2;i<=n;i++){
scanf("%d",&x);
if(x>f[len]) f[++len]=x;
else f[lower_bound(f+1,f+len+1,x)-f]=x;
}
printf("%d",len);
return 0;
}
复杂度
LCS(最长公共子序列)
【简介】
是 的缩写,即最长公共子序列。一个序列,如果是两个或多个已知序列的子序列,且是所有子序列中最长的,则为最长公共子序列。
【代码实现】
朴素做法
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[1007],b[1007],dp[1007][1007];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
else f[i][j]=max(f[i-1][j],f[i][j-1]);
printf("%d",f[n][n]);
return 0;
}
复杂度
利用 优化
#include<iostream>
#include<ctype.h>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int a,b[100007],Transformation[1000007],f[1000007];
int main(){
int n=read();
for(int i=1;i<=n;++i)a=read(),Transformation[a]=i;
for(int i=1;i<=n;++i)b[i]=read(),b[i]=Transformation[b[i]];
int o=1;
f[o]=b[o];
for(int i=2;i<=n;++i){
if(b[i]>f[o])f[++o]=b[i];
else f[upper_bound(f+1,f+o+1,b[i])-f]=b[i];
}
cout<<o;
return 0;
}
复杂度
LCIS(最长公共上升子序列)
【简介】
给定两个整数序列,求它们的最长上升公共子序列。
//Cogs 1669神秘的咒语
#include<iostream>
#include<cstdio>
#include<ctype.h>
#include<cstring>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int a[507],b[507],f[507];
int main(){
freopen("codes.in","r",stdin);
freopen("codes.out","w",stdout);
int T=read();
while(T--){
memset(f,0,sizeof f);
int n=read(),ans=0;
for(int i=1;i<=n;++i)a[i]=read();
int m=read();
for(int i=1;i<=m;++i)b[i]=read();
for(int i=1;i<=n;++i){
int Max=0;
for(int j=1;j<=m;++j){
if(a[i]>b[j])Max=max(Max,f[j]);
if(a[i]==b[j])f[j]=Max+1;
}
}
for(int i=1;i<=m;++i)ans=max(ans,f[i]);
printf("%d\n",ans);
}
fclose(stdin);fclose(stdout);
return 0;
}
复杂度
最大子段和问题
【简介】
给定
个整数(可能为负数)组成的序列
求该序列如
的子段和的最大值。当所给的整数均为负数时定义子段和为
,依此定义,所求的最优值为:
【代码实现】
//LuoguP1115最大子段和
#include<iostream>
using namespace std;
int n,maxn=-1000000;
int a[200007],d[200007];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],d[i]=max(d[i-1]+a[i],a[i]);
for(int i=1;i<=n;i++) maxn=max(maxn,d[i]);
cout<<maxn;
return 0;
}
复杂度$Θ(n)$
背包问题动态规划
背包问题
【简介】
背包是在 件物品取出若干件放在空间为 的背包里,每件物品的体积为 ,与之相对应的价值为 。 背包是背包问题中最简单的问题。 背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在 背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。
【代码实现】
#include<cstdio>
#include<cmath>
int f[600007],v[5007],w[5007];
int n,V;
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=V;j>=v[i];j--)
f[j]=fmax(f[j-v[i]]+w[i],f[j]);
printf("%d\n",f[V]);
return 0;
}
复杂度
完全背包问题
【简介】
有
种物品和一个容量为
的背包,每种物品都有无限件可用。
第
种物品的体积是
,价值是
。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
#include<cstdio>
#include<cmath>
int f[600007],v[5007],w[5007];
int n,V;
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;++i) scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;++i)
for(int j=v[i];j<=V;++j)
f[j]=fmax(f[j-v[i]]+w[i],f[j]);
printf("%d\n",f[V]);
return 0;
}
复杂度
多重背包问题
【简介】
有 种物品和一个容量为 的背包。第 种物品最多有 件可用,每件耗费的空间是 ,价值是 。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
【代码实现】
朴素多重背包
#include<cstdio>
#define max(x,y) (x)>(y)?(x):(y)
int n,f[100001],w[7001],v[7001],c[7001],V;
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;++i)scanf("%d%d%d",&w[i],&v[i],&c[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<=c[i];++j)
for(int k=V;k>=w[i];--k)
f[k]=max(f[k],f[k-w[i]]+v[i]);
printf("%d",f[V]);
return 0;
}
复杂度
二进制优化
#include<iostream>
#include<cstdio>
#include<ctype.h>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int vv[37],ww[37],p[37];
int v[157],w[157],f[207];
int main(){
int V=read(),n=read(),N=0;
for(int i=1;i<=n;++i)vv[i]=read(),ww[i]=read(),p[i]=read();
for(int i=1;i<=n;++i){
int p2=1;
while(p[i]>=p2){
p[i]-=p2;
v[++N]=vv[i]*p2,w[N]=ww[i]*p2;
p2<<=1;
}
if(p[i])v[++N]=vv[i]*p[i],w[N]=ww[i]*p[i];
}
for(int i=1;i<=N;++i)
for(int j=V;j>=v[i];--j)
f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d",f[V]);
return 0;
}
复杂度
单调队列优化
#include<cstdio>
int n,m,v[7001],w[7001],c[7001],f[7001],q[7001],ind[7001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d%d",&v[i],&w[i],&c[i]);
for(int i=1;i<=n;i++){
for(int d=0;d<v[i];d++){
int head=0,tail=-1;
for(int j=d,r=0;j<=m;j+=v[i],r++){
int g=f[j]-w[i]*r;
if(head<=tail && ind[head]<j-c[i]*v[i]) head++;
while(head<=tail && q[tail]<=g) tail--;
q[++tail]=g;ind[tail]=j;
f[j]=q[head]+w[i]*r;
}
}
}
printf("%d",dp[m]);
return 0;
}
复杂度
混合背包问题
【简介】
有的物品只可以取一次( 背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
【代码实现】
:本题p[i]为0时是完全背包
#include<iostream>
#include<cstdio>
#include<ctype.h>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int vv[37],ww[37],p[37];
int v[157],w[157],f[207];
int main(){
int V=read(),n=read(),N=0;
for(int i=1;i<=n;++i)vv[i]=read(),ww[i]=read(),p[i]=read();
for(int i=1;i<=n;++i){
if(!p[i])p[i]=V/vv[i];
int p2=1;
while(p[i]>=p2){
p[i]-=p2;
v[++N]=vv[i]*p2,w[N]=ww[i]*p2;
p2<<=1;
}
if(p[i])v[++N]=vv[i]*p[i],w[N]=ww[i]*p[i];
}
for(int i=1;i<=N;++i)
for(int j=V;j>=v[i];--j)
f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d",f[V]);
return 0;
}
复杂度
二维费用的背包问题
【简介】
二维费用的背包问题是指:对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种费用。对于每种费用都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。
设第
件物品所需的两种费用分别为
和
。两种费用可付出的最大值(也即两种背包容量)分别为V和T。物品的价值为
。
【代码实现】(只粘部分代码好了)
for(int i=1;i<=n;i++)
for(int j=V;j>=v[i];j--)
for(int k=T;k>=g[i];k--)
f[j][k]=max(f[j][k],dp[j-v[i]][k-t[i]]+w[i]);
复杂度
分组背包问题
【简介】
有 件物品和一个容量为 的背包。第件物品的费用 是,价值是 。这些物品被划分为K组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
【代码实现】
//Luogu P1757
#include<iostream>
#include<cstdio>
#include<ctype.h>
#include<algorithm>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int t,cc,N;
int f[1005],g[107][1007],w[1007],v[1007];
int main(){
int m=read(),n=read();
for(int i=1;i<=n;++i)v[i]=read(),w[i]=read(),cc=read(),g[cc][++g[cc][0]]=i,N=max(N,cc);
for(int i=1;i<=N;++i)
for(int j=m;j>=0;--j)
for(int k=1;k<=g[i][0];++k)
if(j>=v[g[i][k]]) f[j]=max(f[j],f[j-v[g[i][k]]]+w[g[i][k]]);
cout<<f[m];
return 0;
}
复杂度
依赖背包问题
【简介】
这种背包问题的物品间存在某种“依赖”的关系。也就是说,物品依赖于物品,表示若选物品,则必须选物品。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
【代码实现】
//Luogu P1064
#include<iostream>
#include<ctype.h>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int v[67][3],p[67][3],f[32007];
int main(){
int n=read(),m=read();
for(int i=1;i<=m;++i){
int w=read(),o=read(),q=read();
if(!q)v[i][0]=w,p[i][0]=o;
else if(!v[q][1])v[q][1]=w,p[q][1]=o;
else v[q][2]=w,p[q][2]=o;
}
for(int i=1;i<=m;++i){
for(int j=n;j>0;--j){
int o=v[i][0],oo=v[i][1],ooo=v[i][2],pp=p[i][0];
if(j>=o)f[j]=max(f[j],f[j-o]+pp*o);
if(j>=o+oo)f[j]=max(f[j],f[j-o-oo]+pp*o+p[i][1]*oo);
if(j>=o+ooo)f[j]=max(f[j],f[j-o-ooo]+pp*o+p[i][2]*ooo);
if(j>=o+oo+ooo)f[j]=max(f[j],f[j-o-oo-ooo]+pp*o+p[i][1]*oo+p[i][2]*ooo);
}
}
cout<<f[n];
return 0;
}
复杂度
树形动态规划
【例题】
某大学有 个职员,编号为 。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 ,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
【代码实现】
#include<iostream>
#include<cstdio>
#include<ctype.h>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int head[6007],in[6007],cnt;
struct Edge{int next,to;}edge[12007];
inline void add_edge(int from,int to){
edge[++cnt].next=head[from];
edge[cnt].to=to;head[from]=cnt;
}
int a[6007],f[6007][2];
void dfs(int x){
f[x][1]=a[x];
for(int i=head[x];i;i=edge[i].next){
int to=edge[i].to;dfs(to);
f[x][1]+=f[to][0];
f[x][0]+=max(f[to][0],f[to][1]);
}
}
int main(){
int n=read(),s;
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
add_edge(v,u);in[u]=1;
}
for(int i=1;i<=n;++i)if(!in[i])s=i;dfs(s);
printf("%d\n",max(f[s][0],f[s][1]));
return 0;
}
复杂度
DAG上的动态规划
【例题】
有 个矩形,每个矩形可以用来 描述,表示长和宽。矩形 可以嵌套在矩形 中当且仅当 && 或者 && (相当于 旋转 度)。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。
【代码实现】
#include<algorithm>
#include<cstdio>
#include<cstring>
struct node{
int x,y;
}juxing[1005];
int cmp(node a,node b){
return a.x<b.x||(a.x==b.x&&a.y<b.y);
}
int N,n,ans,maxx,sum[1005];
int main(){
scanf("%d",&N);
while(N--){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%d",&juxing[i].x,&juxing[i].y);
if(juxing[i].x<juxing[i].y){
int temp=juxing[i].x;
juxing[i].x=juxing[i].y;
juxing[i].y=temp;
}
sum[i]=1;
}
std::sort(juxing,juxing+n,cmp);
maxx=1;
for(int i=1;i<n;i++){
ans=0;
for(int j=0;j<i;j++)
if(juxing[j].x<juxing[i].x&&juxing[j].y<juxing[i].y)
ans=std::max(sum[j],ans);
sum[i]=ans+1;
maxx=std::max(sum[i],maxx);
}
printf("%d\n",maxx);
}
return 0;
}
复杂度
数位动规
【例题】
杭州人称那些傻乎乎粘嗒嗒的人为62(音:
)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有
或
的号码。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
【代码实现】
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[10][3];
void Init(){//预处理,算出所有可能
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=8;i++){
dp[i][0]=dp[i-1][0]*9-dp[i-1][1];//在不含不吉利数62和4的首位分别补除了4的9个数字,减去在2前面补6的个数
dp[i][1]=dp[i-1][0];//在不含不吉利数在首位补2
dp[i][2]=dp[i-1][2]*10+dp[i-1][0]+dp[i-1][1];//各种出现不吉利数的情况
}
}
int Solve(int x){
int digit[15];
int cnt=0,tmp=x;
while(tmp){
digit[++cnt]=tmp%10;
tmp/=10;
}
digit[cnt+1]=0;
int flag=0,ans=0;
for(int i=cnt;i>0;i--){
ans+=digit[i]*dp[i-1][2];//由上位所有非吉利数推导
if(flag)ans+=digit[i]*dp[i-1][0];//之前出现非吉利的数字
else{
if(digit[i]>4)ans+=dp[i-1][0];//出现4
if(digit[i]>6)ans+=dp[i-1][1];//出现6
if(digit[i+1]==6&&digit[i]>2) ans+=dp[i][1];//出现62
}
if(digit[i]==4||(digit[i+1]==6&&digit[i]==2))flag=1;
}
return x-ans;//所有的数减去非吉利的数
}
int main(){
int a,b;
Init();
while(~scanf("%d%d",&a,&b)){
if(a==0&&b==0)break;
printf("%d\n",Solve(b+1)-Solve(a));
}
return 0;
}
复杂度
状态压缩动态规划
【例题】
现有 盏灯,以及 个按钮。每个按钮可以同时控制这 盏灯——按下了第 个按钮,对于所有的灯都有一个效果。按下 按钮对于第 盏灯,是下面 个中效果之一:
- 如果 为 ,那么当这盏灯开了的时候,把它关上,否则不管;
- 如果 为 的话,如果这盏灯是关的,那么把它打开,否则也不管;
- 如果 为 ,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
突然想讲一下这个题:
讲解时间!
最多只有 种状态
既然这么少,那我们完全可以用数组来存储.
用 表示灯的开关, 是开, 是关。
关掉第 盏灯: ^ ;
打开第 盏灯: | ;
既然知道操作,接下来就非常简单了
因为 和 都非常小,完全可以循环枚举, 由最小的 转移而来;
int N=(1<<n)-1;
for(int i=N;i>=0;--i){
for(int j=1;j<=m;++j){
int x=i;
for(int l=1;l<=n;++l){
int o=1<<l-1;
if(a[j][l]==1 && i&o)x^=o;
if(a[j][l]==-1 && !(i&o))x|=o;
}
f[x]=min(f[x],f[i]+1);
}
}
核心代码就这么多
【完整代码实现】
#include<iostream>
#include<cstdio>
#include<ctype.h>
#include<cstring>
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
int a[107][17],f[1027];
int main(){
memset(f,0x3f,sizeof f);
int n=read(),m=read(),N=(1<<n)-1;f[N]=0;
for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)a[i][j]=read();
for(int i=N;i>=0;--i){
for(int j=1;j<=m;++j){
int x=i;
for(int l=1;l<=n;++l){
int o=1<<l-1;
if(a[j][l]==1 && i&o)x^=o;
if(a[j][l]==-1 && !(i&o))x|=o;
}
f[x]=min(f[x],f[i]+1);
}
}
printf("%d",f[0]>1e9?-1:f[0]);
return 0;
}