算法之DP

通常DP

都是有模板的,先初始化,而后找到不一样状态下数值的关系,使得某个状态可用另外一个状态由一个固定的方式转移而来,列出状态转移方程,这就是DP;html

例题

P1216 [USACO1.5]数字三角形 Number Trianglesc++

1 f[i][j]+=max(f[i-1][j-1],f[i-1][j]);
方程

P1044 栈git

1 f[i]+=f[j]*f[i-j-1];
方程

P2800 又上锁妖塔算法

1 f[i]=min(f[i-1]+t[i],min(f[i-2]+t[i-1],f[i-3]+t[i-2]));
方程

P1057 传球游戏数组

能够看出,裸的DP是几乎没有难度的,固然某些题除外如P1004P1280等题,值得思考。ide


 

背包问题

背包属于基础DP,但拓展性是最高的。优化

具体能够看dd大牛的《背包九讲》url

如下先讲01背包spa

1 f[v]=max{f[v],f[v-c[i]]+w[i]};

上面的是01背包的转移方程,但v是从V...c[i]的。.net

为何呢?这个方程表明第v个体积的物体的最大值=max(他本身自己的值,v-第i个物体的体积时的最大值+第i个物体的值)

例题

01背包基本是套这个模板,但也不缺少颇有思考性的,如P2370P2979P1156P4544(这个要单调队列优化,但纯背包有60-70分,题解在这里);


 

线段树单调队列优化(都是用线段树水过)

 首先,相信你们都知道什么是移动窗口了吧(简单得一匹好吧,线段树修改查找,傻子都会),创建一个deque(头尾均可进出,但常数能够把你卡到80,连读优都救不了我)

细节看注释吧

#include<bits/stdc++.h>
using namespace std;
deque<int>q;
int n,m,x[2000005];
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
int main()
{
	cin>>n>>m;
	for (register int i=1;i<=n;i++) x[i]=read();
	cout<<0<<endl;//由于是前i个
	for (register int i=1;i<=n-1;i++)
	{
		while (!q.empty()&&x[i]<=x[q.back()]) q.pop_back();//维护单调递增性,由于要求最小
		q.push_back(i);//放入
		while (q.back()-q.front()>=m) q.pop_front();//过时就弹出,deque存编号就很方便了	
		cout<<x[q.front()]<<endl;//由于单调递增,因此头最小
	}
}

P1725森♂之妖精

若是仅仅只是N^2DP相信你们均可以A出来,方程就是f[i]=max{f[i-j...i]}+a[i],但这明显能够优化好吧(把f数组插入线段树里(logn),在查询(logn)),咱们只须要f[i-j...i]的最大值就能够O(1)推了(是推),因此搞个单调队列,像滑动窗口那样作个单调递减队列,每次拿队头推便可

//这是我丑陋的优先队列的代码
#include<bits/stdc++.h>
using namespace std;
priority_queue<int>qx,qy;
const int MAXN=300005;
int ans[MAXN],N,L,R,f[MAXN],maxx;
inline int read()
{
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}
inline bool check(){
    if (!qy.empty()&&qx.top()==qy.top()) return 1;
    return 0;
}
int main(){
    N=read();L=read();R=read();
    for (int i=0;i<=N;i++) ans[i]=read();
    for(int i=1;i<=L-1;i++) qy.push(ans[i]);
    for(int i=L;i<=N;i++){
        qx.push(f[i-L]);
        if(i-R-1>=L) qy.push(f[i-R-1]);
        if (check()==1) qx.pop(),qy.pop();
        f[i]=qx.top()+ans[i];           
    }
     maxx=-99999999;
     for (int i=N-R+1;i<=N;i++) maxx=max(maxx,f[i]);
     cout<<maxx<<endl;
    return 0;
}
//这是luogu上我认为(用deque)比较好看的单调队列代码
#include <bits/stdc++.h> 
using namespace std;
void read(int &x){
    int f=1,r=0;char ch;
    do ch=getchar();while(ch!='-'&&!isdigit(ch));
    if(ch=='-')f=-1,ch=getchar();
    do r=r*10+ch-48,ch=getchar();while(isdigit(ch));
    x=f*r;
}
int n,l,r;
struct info{
    int num,val;
    //分别记录序号和数值,序号用来判断是否超出范畴 
};
int f[200010];//f[i]表示到达点i时可得到的最大冰冻指数 
deque<info> q;//STL tql,单调队列很是方便 
int a[200010];//a为点i的冰冻指数(输入数据)
int main() {
    cin>>n>>l>>r;
    for(int i=0;i<=n;i++)read(a[i]);
    for(int i=l;i<=n;i++){
        while(!q.empty()&&q.back().val<f[i-l])q.pop_back();//弹出较小值 
        q.push_back((info){i-l,f[i-l]});//放当前值 
        if(i-q.front().num>r-l)q.pop_front();//弹出超过范畴值 
        f[i]=q.front().val+a[i];//记录最大值加上该点的冰冻指数放入f[i] 
    }
    cout<<*max_element(f+n-r+1,f+n+1)<<endl;
    //注意输出的是这一段之间的最大值,由于这一段之间的每一位均可如下一步跳出n  
    return 0;
}

 最后,祭出NOIP2017 T4你觉得我会再写吗?其实我写过了啊!

 例题

P2422(注意,并不是通常单调队列啊,而且几乎不能叫DP,但须要思考时间)


 

优先队列优化(题少不怎么打)

 不写了,单调作的几乎均可以用这个作

要看就看我停课集训DAY1 T3


 

状压DP(就是变相暴力)

 说道状压DP,就不得不说著名的TSP旅行商问题了,这道题须要用到状压DP。

首先先上状压DP基操

这就是基操,状压DP通常都是把状态用二进制数表示出来,通常都是取或不取之类的01状态;

接下来说解旅行商问题。

首先肯定数组维数:2维,由于一维太难推了,且有后效性。二维表示什么呢?分别表示当前状态和最后走到何处。

而后就是枚举状态和循环了

第一层循环 i 枚举每一个状态

第二层循环 j 枚举下一步到达的点

第三层枚举从k点走到j点

而后if ((1<<(j-1)&i)==0)表示j点在此状态还未走到,if ((1<<k-1)&i)表示k点在此状态已走到。(废话)

DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j])就是对比走来和当前花费了(真的暴力-1s)

而后就是喜闻乐见的代码放送了

#include<bits/stdc++.h>
using namespace std;
int DP[25][(1<<20)-1],N,MINN=2e9,LIS[25][25];
int main()
{
	cin>>N;
	int MAXN=(1<<N)-1;
	for (int i=1;i<=N;i++)
	 for (int j=1;j<=N;j++)
	  cin>>LIS[i][j];
	memset(DP,0x3f,sizeof(DP));
	DP[1][1]=0;//初始化 
	for (int i=0;i<=MAXN;i++)
	{
		for (int j=1;j<=N;j++)//到达j点 
		if ((1<<(j-1)&i)==0)//到达点未被走过 
		 for (int k=1;k<=N;k++)//从k过来 
		 { 
		  if ((1<<k-1)&i) DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j]);
		 }		
	}
	for (int i=2;i<=N;i++) MINN=min(MINN,DP[i][(1<<N)-1]+LIS[i][1]);//要走回去啊,但不能不走(因此i从2开始) 
	cout<<MINN<<endl;
	return 0;
}

luogu的P3092是道很好的题目

但在此只放代码,有注释的

#include<bits/stdc++.h>
using namespace std;
int m,n,a[100010],sum[100010],c[20],b[20],f[1<<16],k,maxx=-1,sum1;
int main(){
    cin>>k>>n;
    for(int i=1;i<=k;i++) cin>>c[i],sum1+=c[i];
    for(int i=1;i<=n;i++) cin>>a[i],sum[i]=sum[i-1]+a[i];
    for(int i=0;i<=(1<<k)-1;i++)//硬币状态
        for(int j=1;j<=k;j++)
            if((i&1<<j-1)){//没有被选
                int wz=f[i^1<<j-1];
                wz=upper_bound(sum+1,sum+n+1,sum[wz]+c[j])-sum;//买连续一段店铺
                f[i]=max(f[i],wz-1);//更新状态
            }
    for(int i=0;i<=(1<<k)-1;i++)
        if(f[i]==n){//个数知足
            int sum=0;
            for(int j=1;j<=k;j++) if(i&1<<j-1) sum+=c[j];//若是是1表明用了
            maxx=max(maxx,sum1-sum);//剩下最大价值
        }
    if(maxx<0) cout<<-1<<endl;else cout<<maxx<<endl;
    return 0;
}

果真,状压DP是一种很优美暴力的作法呢

例题

P1879P1896


 

树形DP(我最不擅长)

 蒟蒻真的不会啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!


 

其余算法套DP(二分什么的)

NOIP2017 T4  二分加单调队列+DP题解

 DAY3 T4 luogu P1772 最短路+DP(真的少见)

 


 

还有一些提升组的就不列了(如斜率优化,数位,插头DP)

相关文章
相关标签/搜索