集训Dp复习

1.线性

「BZOJ1609」麻烦的聚餐

分别求一遍连续非下降/上升子序列长度,用总长减去,取最小值即可,主要\(O(n^2)\)优化

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
int n,f[maxn],a[maxn],top,ans;
int main(){
//	freopen("1.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
	f[++top]=a[1];
	for(int i=2;i<=n;++i){
		if(a[i]>=f[top]){
			f[++top]=a[i];
			continue;
		}
		int x=upper_bound(f+1,f+1+top,a[i])-f;
		f[x]=a[i];
	}
	ans=n-top;
	f[top=1]=a[n];
	for(int i=n-1;i>=1;--i){
		if(a[i]>=f[top]){
			f[++top]=a[i];
			continue;
		}
		int x=upper_bound(f+1,f+1+top,a[i])-f;
		f[x]=a[i];
	}
	printf("%d\n",min(ans,n-top));
	return 0;
}

「P2066」机器分配

\(f[i][j]\)表示i个公司分j台机器所得的最大利润

转移方程:\(f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]),1<=i<=n,1<=j<=m,0<=k<=j\)

Code



#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=20;
int f[maxn][maxn],a[maxn][maxn],n,m,ans,xx,shu[maxn];
void B(int x,int y){
  if (x==0) return;
  for (int k=0;k<=y;k++){
  	if (ans==f[x-1][k]+a[x][y-k]){
        ans=f[x-1][k];
  		B(x-1,k);
		printf("%d %d\n",x,y-k);
        break;
     }
  }
}
int main(){
//	freopen("1.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			scanf("%d",&a[i][j]);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			for(int k=0;k<=j;++k){
			f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]);
		}
	}
	printf("%d\n",f[n][m]);
	ans=f[n][m];
	B(n,m);
	return 0;
}

2.树形

没有上司的舞会

相邻节点不能在一起,0表示不参加,1表示参加。
主要要搞清0/1分别由谁转移,详见代码,不赘述。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=6000+5;
typedef long long ll;
struct Edge{int next,to;}e[maxn*2];
int n,a[maxn],f[maxn][2],cnt,x,y,head[maxn];
void Add(int x,int y){
	e[++cnt].to=y;
	e[cnt].next=head[x];
	head[x]=cnt;
}
void dfs(int u,int fa){
	f[u][1]=a[u];
	int xx=0;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
		f[u][1]=max(f[u][1],f[v][0]+a[u]);
		xx+=max(f[v][1],f[v][0]);
	}
	f[u][0]=xx;
}
int main(){
//	freopen("1.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
	for(int i=1;i<n;++i)scanf("%d%d",&x,&y),Add(x,y),Add(y,x);
	scanf("%d%d",&x,&y);
	dfs(1,0);
	printf("%d\n",max(f[1][1],f[1][0]));
	return 0;
}

小胖守皇宫

题目就是说,每相邻的两个节点必须有一个人守着,并有一定的花费,给你个节点花费,求花费最小。
0表示儿子守着,1是自己守,2是父亲守。
那个tot变量有些迷,待更新吧,树形的和后面的状压还得持续更新

Code



#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=1500+5,Inf=0x3f3f3f3f;
typedef long long ll;
struct Edge{int next,to;}e[maxn*2];
int n,a[maxn],f[maxn][3],cnt,x,m,k,head[maxn];
void Add(int x,int y){
	e[++cnt].to=y;
	e[cnt].next=head[x];
	head[x]=cnt;
}
void dfs(int u,int fa){
	f[u][1]=a[u];
	int tot=Inf;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
		f[u][1]+=min(f[v][0],min(f[v][1],f[v][2]));
		f[u][0]+=min(f[v][1],f[v][0]);
		f[u][2]+=min(f[v][1],f[v][0]);
		tot=min(tot,f[v][1]-f[v][0]);
	}
	if(tot>0)f[u][0]+=tot;
}
int main(){
	//freopen("1.in","r",stdin);
	scanf("%d",&n);
	int aa;
	for(int i=1;i<=n;++i){
		scanf("%d%d%d",&k,&aa,&m);
		a[k]=aa;
		while(m--){
			scanf("%d",&x);
			Add(k,x);Add(x,k);
		}
	}
	if(n==1)printf("%d\n",a[1]);
	else{
		dfs(1,0);
		printf("%d\n",min(f[1][0],f[1][1]));
	}
	return 0;
}

3.区间

整数划分

和乘积最大那道题很像,不过更复杂一点,要输出。
f[i][j]表示前i位划分为m部分的最大乘积。(就这个部分关键要想到)
预处理出任意i~j位的数a[i][j]
转移:\(f[i][j]=max(f[i][j],f[k][j-1]*a[k+1][i]),j-1<=k<=i-1\)
边界:f[0][0]=1(因为是相乘,所以为1不能为0)
转移时记录前i位划分为j段的最后一个“*”前面那位数,递归输出。

Code

#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=23;
ll a[maxn][maxn],f[maxn][maxn];
int t,hua[maxn][maxn],m,n,len;
char s[maxn];
void P(int x,int y){
	if(y==0)return;
	P(hua[x][y],y-1);
	for(int i=hua[x][y]+1;i<=x;++i)printf("%c",s[i]);
	printf(" ");
}
void C(){
	memset(f,0,sizeof f);
	memset(a,0,sizeof a);
	memset(hua,0,sizeof hua);
	memset(s,0,sizeof s);
}
void R(){
	scanf(" %s %d",s+1,&m);
	n=strlen(s+1),m=min(m,n);
	for(int i=1;i<=n;++i)
		for(int j=i;j<=n;++j) a[i][j]=a[i][j-1]*10LL+s[j]-'0';
	memset(f,-1,sizeof f);
	f[0][0]=1;
}
void Solve(){
	for(int i=1;i<=n;++i)
		for(int j=1;j<=min(i,m);++j)
			for(int k=j-1;k<=i-1;++k)
				if(f[i][j]<f[k][j-1]*a[k+1][i]) f[i][j]=f[k][j-1]*a[k+1][i],hua[i][j]=k;
}
void Pr(){
	printf("%lld\n",f[n][m]);
	P(n,m);
	printf("\n");
}
int main(){//可怜的主函数
	//freopen("1.in","r",stdin);
	scanf("%d",&t);
	while(t--){
		C();R();
		Solve();
		Pr();
	}
	return 0;
}

矩阵连乘

题意是什么鬼我看不懂......
解释一下:
可以这么理解:对于三个连续的矩阵,分两步求,再将这两步结果相加。

A:2 * 3 B:3 * 4 C:4 * 5

(A * B) * C=(2 * 3 * 4)+(2 * 4 * 5)

A * ( B * C)=(3 * 4 * 5)+(2 * 3 * 5)

f[i][j]表示第i个到第j个的矩阵乘积最小运算量

转移:\(f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i-1]*a[k]*a[j]),i<=k<j\)

边界:\(f[i][j]=Inf,f[i][i]=0,1<=i<=n\)

Code



#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=105,Inf=0x7f7f7f7f;
int n,f[maxn][maxn],a[maxn];
int main(){
//	freopen("1.in","r",stdin);
	memset(f,Inf,sizeof f);
	scanf("%d",&n);
	for(int i=0;i<=n;++i)scanf("%d\n",&a[i]);
	for(int i=1;i<=n;++i)f[i][i]=0;
	for(int l=2;l<=n;++l)
		for(int i=1,j;i+l-1<=n;++i){
			j=i+l-1;
			for(int k=i;k<j;++k) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i-1]*a[k]*a[j]);
		}
	printf("%d\n",f[1][n]);
	return 0;
}

凸多边形的三角剖分

也是区间的套路,由简单衍生出来

先顺时针给各个顶点编上号,\(f[i][j]\)表示连接i号顶点和j号顶点,所形成的下面的一个多边形的最小剖分值。

转移:\(f[i][j]=min(f[i][k]+f[k][j]+a[i] * a[k] * a[j]),i+1<=k<=j-1\)

边界:初始化\(f[i][i+2]=a[i] * a[i+1] * a[i+2],(1<=i<=n-2)\)

Code



#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=50+5;
int n;
ll a[maxn],f[maxn][maxn];
int main(){
	//freopen("1.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%lld\n",&a[i]);
	for(int i=1;i+2<=n;++i)f[i][i+2]=a[i]*a[i+1]*a[i+2];
	for(int d=3;d<=n;++d){
		for(int i=1;i+d-1<=n;++i){
			int j=i+d-1;
			for(int k=i+1;k<j;++k){
				if(f[i][j])f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[k]*a[i]*a[j]);
				else f[i][j]=f[i][k]+f[k][j]+a[k]*a[i]*a[j];
			}
		}
	}
	printf("%lld\n",f[1][n]);
	return 0;
}


多边形

李煜东的《算法进阶指南》P284~286
我就不再解释啦

Code



#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=50+5,Inf=1e9+10;
int n,data[maxn<<1];
int f[maxn<<1][maxn<<1][3];
int ff[maxn<<1];
char c[maxn<<1]; 
int q,w,e,r;
int ans_point=(-1)*Inf;
void Read(){
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>c[i]>>data[i];
        c[i+n]=c[i];
        data[i+n]=data[i];
    }
}
void Solve(){
    for(int i=1;i<=n;++i){
        for(int j=1;j<=(n<<1);++j){
            for(int k=1;k<=(n<<1);++k){
                f[j][k][1]=(-1)*Inf;
                f[j][k][2]=Inf;
            }
        }
        for(int j=1;j<=(n<<1);++j){
            f[j][j][1]=f[j][j][2]=data[j];
            if(c[j+1]=='t')  f[j][j+1][1]=f[j][j+1][2]=data[j]+data[j+1];
            else f[j][j+1][1]=f[j][j+1][2]=data[j]*data[j+1];
        }
        for(int l=2;l<=n;++l){
            for(int j=i;j+l<=i+n;++j){
                int k=j+l-1;
                for(int x=j;x<k;++x){
                    if(c[x+1]=='t'){
                        f[j][k][1]=max(f[j][k][1],f[j][x][1]+f[x+1][k][1]);
                        f[j][k][2]=min(f[j][k][2],f[j][x][2]+f[x+1][k][2]);
                    }else{
                        q=f[j][x][1]*f[x+1][k][1];
                        w=f[j][x][2]*f[x+1][k][2];
                        e=f[j][x][1]*f[x+1][k][2];
                        r=f[j][x][2]*f[x+1][k][1];
                        f[j][k][1]=max(f[j][k][1],max(max(q,w),max(e,r)));
                        f[j][k][2]=min(f[j][k][2],min(min(q,w),min(e,r)));
                    }
                }
            }
        }
        ff[i]=f[i][i+n-1][1];
    }
    for(int i=1;i<=n;++i){
        ans_point=max(ans_point,ff[i]);
    }
    printf("%d\n",ans_point);
    for(int i=1;i<=n;++i){
        if(ans_point==ff[i]) printf("%d ",i);
    }
}
int main(){
//	freopen("1.in","r",stdin);
    Read();
    Solve();
    return 0;
}

低价回文

\(soda\)的博客吧

4.状压

.......我实在没时间写了,待更新吧

猜你喜欢

转载自www.cnblogs.com/Lour688/p/13189649.html