集训之各种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;
}

低价回文

Code

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=2e3+5;
int f[maxn][maxn],n,m,v[30];
char s[maxn],ss;
int main(){
//	freopen("1.in","r",stdin);
	scanf("%d%d %s",&n,&m,s+1);
	for(int i=1,x,y;i<=m;++i)scanf(" %c%d%d",&ss,&x,&y),v[ss-'a']=min(x,y);
	for(int i=m;i>=1;--i)
		for(int j=i+1;j<=m;++j){
			if(s[i]==s[j])f[i][j]=f[i+1][j-1];
			else f[i][j]=min(f[i+1][j]+v[s[i]-'a'],f[i][j-1]+v[s[j]-'a']);
		}
	printf("%d\n",f[1][m]);
	return 0;
}

4.状压

终于到了状压dp了,说实话我的状压dp真的不好,前几天集训时遇到状压的题我都不会,前两天没有时间,今天不能再拖了,总结一下。

互不侵犯

\(N * N\)的棋盘里面放\(K\)个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共\(8\)个格子。

给你N和K的值,求方案数

\(1<=N<=9,0<=K<=N * N\).
我们用\(f[i][S][k]\)表示前i行放k个国王,且第i行状态为S时的方案数。状态0是不放国王,1是放国王。
边界是\(f[0][0][0]=1\),这样才能第一层转移到底层时为1(也可以直接将第一整行全预处理出来,方案数都为1)
转移是比较简单的,不需要常数了:\(f[i][S][k]+=f[i-1][s][k-cnt]\),(本行由上一行转移过来)这个k是S中1的个数,不一定是总数K,s是上一行的状态,与S不能冲突,(不能互相攻击)。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1<<9;
int n,m;
ll f[10][maxn][82];
int lowbit(int x){return x&-x;}
int Q(int x){//求x的二进制中1的个数
	int cnt=0;
	for(int i=x;i;i-=lowbit(i))cnt++;
	return cnt;
}
int main(){
	scanf("%d%d",&n,&m);
	f[0][0][0]=1;
	int maxs=1<<n;
	for(int i=1;i<=n;++i){
		for(int S=0;S<maxs;++S){
			int cnt=Q(S);
			if(S&(S<<1))continue;
			for(int s=0;s<maxs;++s){//s是上一行的状态枚举,与内层的k的枚举可以互换,但这样更省时间,因为特判会省去一些冗余循环
				if(S&s||(S<<1)&s||(S>>1)&s)continue;
				for(int k=cnt;k<=m;++k) f[i][S][k]+=f[i-1][s][k-cnt];
			}
		}
	}
	ll ans=0;
	for(int S=0;S<maxs;++S)ans+=f[n][S][m];//最后结果是最后一行放k个国王的各种状态所对应的方案数之和
	printf("%lld\n",ans);
	return 0;
}

炮兵阵地

这个题目和上个差不多,除了不能相互攻击外,还有些地方不能放,最后问你的是最多的个数而不是方案数了。
\(f[i][S][s]\)是前\(i\)行中,第\(i\)行状态是\(S\)\(i-1\)行状态是\(s\)时的个数。
转移是枚举本行状态\(S\),算出该状态下本行的个数\(cnt\),用上一行的\(f[i-1][s][ss]\)再加上\(cnt\),得到\(i\)行状态为\(S\),上一行状态为\(s\)时的最大个数。
即:\(f[[i][S][s]=max(f[i][S][s],f[i-1][s][ss]+cnt)\).
其中\(ss\)是&i-2&行的状态,也需要枚举。另外本题有个优化,因为炮兵左右攻击范围挺大(2格),所以,尽管一行有M<=10个格子,但真正合法的状态数不超过60个,我们可以提前将1行的合法的状态编上号预处理出来,时间/空间复杂度都可减少,空间可减少大概八九十倍的样子。

#include <cstdio>
#include <algorithm>
using namespace std;
int n,m,tot;
const int maxn=1<<9;
int a[101],st[70],f[101][70][70];
char s[15];
bool J(int x){
	if(x&(x<<1)||x&(x<<2))return 0;
	return 1;
}
int Q(int x){
	int cnt=0;
	for(int i=st[x];i;i-=(i&-i))cnt++;
	return cnt;
}
int main(){
	scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++){
        scanf(" %s",s);
        for(int j=0; j<m; j++){
            if(s[j] == 'H') a[i]|=(1<<j);
        }
    }
    int maxs=1<<m;
	for(int i=0;i<maxs;++i)if(J(i))st[++tot]=i;
	for(int i=1;i<=tot;++i)if(!(st[i]&a[1]))f[1][i][1]=Q(i);
	for(int i=2;i<=n;++i){
		for(int S=1;S<=tot;++S){
			if(st[S]&a[i])continue;
			int cnt=Q(S);
			for(int s=1;s<=tot;++s){
				if((st[S]&st[s])||(st[s]&a[i-1]))continue;
				for(int ss=1;ss<=tot;++ss){
					if((st[ss]&a[i-2])||(st[ss]&st[s])||(st[S]&st[ss]))continue;
					f[i][S][s]=max(f[i][S][s],f[i-1][s][ss]+cnt);
				}
			}
		}
	}
	int Max=0;
	for(int i=1;i<=tot;++i)
	for(int j=1;j<=tot;++j){
		if((st[i]&a[n])||(st[j]&a[n-1])||(st[i]&st[j]))continue;
		Max=max(Max,f[n][i][j]);
	}
	printf("%d\n",Max);
	return 0;
}

旅游景点 Tourist Attractions

愤怒的小鸟

动物园

待更新吧......

posted @ 2020-06-24 20:24  liuzhaoxu  阅读(179)  评论(0编辑  收藏  举报