其他分享
首页 > 其他分享> > 9.6快乐dp(初级1)

9.6快乐dp(初级1)

作者:互联网

题目传送:http://118.31.67.228/status.php?problem_id=&user_id=2021xiefuquan&cid=1013&language=-1&jresult=-1&csrf=0TJ798Za35gk5HIdMPl37YnoGUDub30n

预期实际

差值

a10083-17
b1001000
c1001000
d1001000
e000
f1001000
g10067-33(样例数字间无空格)
h1000-100
i000
j10000
总和800400-400

a.石子合并

由题意易得dp转移方程dp\left [ i,j \right ]=min\left \{ dp[i,k]+dp[k,j]+sum[j]-sum[i-1] \right \}最大值同理

sum为前缀和,如果不是环,就是n^2效率

法一:枚举断点

从一点断开变成一个链来做 效率n^3方,本题可过

法二:倍长环

从任意一点断开然后复制接到末尾可n^2

#include<bits/stdc++.h>
using namespace std;
int f[205][205],a[205],sum[205];//记得开两倍
int g[205][205];
int an1=0x7f7f7f7f,an2;
int n;
int main(){
	scanf("%d",&n);
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		a[i+n]=a[i];
	}
	for(int i=1;i<=n*2;i++){
		sum[i]=sum[i-1]+a[i];
		f[i][i]=0;
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<n*2;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
				g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+sum[j]-sum[i-1]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		an1=min(an1,f[i][i+n-1]);
		an2=max(an2,g[i][i+n-1]);
	}
	printf("%d\n%d",an1,an2);
} 
/*
6 6 5 4 2 3 4

*/ 

b.对抗赛

枚举每一个数的情况,选或不选,但2^{50}要炸,就考虑对半深搜,具体来说枚举前一半的可能情况,用f(sum)记录情况和为sum的个数,在枚举后一半,将ans加上f(half-sum),half为

总和的一半,由每一种情况的另一半也记录了,答案要除2;

#include<bits/stdc++.h>
using namespace std;
int f[50000],a[55];
int n,sum,ans,half;
inline void dfs(int x,int s){
	if(s>sum) return ;
	if(x>half){
		f[s]++;
		return ;
	}
	dfs(x+1,s);
	dfs(x+1,s+a[x]);
}
inline void dfs2(int x,int s){
	if(s>sum) return ;
	if(x>n){
		ans+=f[sum-s];
		return ;
	}
	dfs2(x+1,s);
	dfs2(x+1,s+a[x]);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		sum+=a[i];
	}
	if(sum&1){
		cout<<0;
		return 0;
	}
	sum/=2;
	half=n/2;
	dfs(1,0);
	dfs2(half+1,0);
	cout<<ans/2;
}
/*
7
1 2 3 4 5 6 7

*/

c.演讲大厅安排

啥也不说先排序,以左端点为第一关键字,右端点为第二关键字

然后考虑转移方程,dp[i]表示以 第i场为结尾,即第i场必须演讲,可得dp[i]=max\left \{ dp[j]+rime[i] \right \}(right(j)<=left(i))

然后就n^2过了

#include<bits/stdc++.h>
using namespace std;
struct node{
	int l,r;
}p[5050];
bool operator < (node x,node y){
	return x.l<y.l || x.l==y.l && x.r<y.r; 
}
int n,ans;
int f[5050];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&p[i].l,&p[i].r);
	sort(p+1,p+n+1);
	for(int i=1;i<=n;i++)
		for(int k=0;k<i;k++)
			if(p[k].r<=p[i].l)
				f[i]=max(f[i],f[k]+p[i].r-p[i].l);
	for(int i=1;i<=n;i++)//枚举寻找答案
		ans=max(ans,f[i]);
	cout<<ans;
}

d.传纸条

枚举每一点可能的转移情况,注意当前点和上一点的两条路线均不能重合

#include<bits/stdc++.h>
using namespace std;
const int N=56;
int n,m,a[N][N],f[N<<1][N][N];
int main(){
	int n,m;
	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=0;i<n+m-3;i++)
		for(int j=1;j<=n && j<=i+1;j++)
			for(int k=1;k<=n && k<=i+1;k++){
				if(j!=k){
					f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+a[j][i+3-j]+a[k][i+3-k]);
					f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+a[j+1][i+2-j]+a[k+1][i+2-k]);
				}
				if(j+1!=k) f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+a[j+1][i+2-j]+a[k][i+3-k]);
				if(k+1!=j) f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+a[j][i+3-j]+a[k+1][i+2-k]);
			}
	cout<<f[n+m-3][n-1][n]<<endl;
	return 0;
}
/*
3 3
0 3 9
2 8 5
5 7 0

*/

e.守望者的逃离

直接讲代码

#include<bits/stdc++.h>
using namespace std;
int m,s,t,now=0;
int main() {
	cin>>m>>s>>t;
	int s1=0,s2=0;
	for(int i=1; i<=t; i++) {
		s1+=17;//看当前是走路优还是闪现优
		if(m>=10) {//有蓝就闪
			s2+=60;
			m-=10;
		}
		else m+=4;//没蓝就回蓝
		if(s2>s1) s1=s2;//如果闪现优,那前面走路的时间不如全用来闪现
		if(s1>s) {
			cout<<"Yes"<<endl<<i<<endl;
			return 0;
		}
	}
	cout<<"No"<<endl<<s1<<endl;
}

f.矩阵取数游戏

明显每一行不会影响其他行,那对于每一行来说设dp\left [ i,j \right ]表示当前还剩ij的数没取走

可得dp\left [ i,j \right ]=max\left \{ dp\left [ i-1,j \right ] +a[i-1]*2^{m-j-i+1},dp\left [ i,j+1 \right ] +a[j+1]*2^{m-j-i+1}\right \}

#include<bits/stdc++.h>
#define N 40
using namespace std;
const int mm=10; 
struct A {
	int e[N];
	A(int x=0):e() {
		for (int i=x; i; i/=10) e[++e[0]]=i%10;
		if (!e[0]) e[0]++;
	}
	void read() {
		e[0]=0;
		char c=getchar();
		while (c<=32) c=getchar();
		while (c>32) {
			e[++e[0]]=c-'0';
			c=getchar();
		}
		reverse(e+1,e+e[0]+1);
	}
	void print() {
		for (int i=e[0]; i>=1; i--) printf("%d",e[i]);
		printf("\n");
	}
};
A operator + (const A &a,const A &b) {
	A c;
	c.e[0]=max(a.e[0],b.e[0]);
	for (int i=1; i<=c.e[0]; i++) {
		c.e[i]+=a.e[i]+b.e[i];
		c.e[i+1]+=c.e[i]/mm;
		c.e[i]%=mm;
	}
	if (c.e[c.e[0]+1]) c.e[0]++;
	return c;
}
A operator * (const A &a,const A &b) {
	A c;
	c.e[0]=a.e[0]+b.e[0];
	for (int i=1; i<=a.e[0]; i++)
		for (int j=1; j<=b.e[0]; j++) {
			c.e[i+j-1]+=a.e[i]*b.e[j];
			c.e[i+j]+=c.e[i+j-1]/mm;
			c.e[i+j-1]%=mm;
		}
	while (c.e[0]>1&&!c.e[c.e[0]]) c.e[0]--;
	return c;
}
bool operator < (const A &a,const A &b) {
	if (a.e[0]!=b.e[0]) return a.e[0]<b.e[0];
	for (int i=a.e[0]; i>=1; i--)
		if (a.e[i]!=b.e[i]) return a.e[i]<b.e[i];
	return 0;
}
bool operator > (const A &a,const A &b) {
	return b<a;
}

A f[90][90];
A p[81];
A a[81][81];
int n,m;
A ans;
int main() {
	p[1].e[1]=2;
	p[1].e[0]=1;
	A zero;zero.e[0]=1,zero.e[1]=0;
	ans=zero;
	for(int i=1;i<=80;i++)	p[i+1]=p[i]*p[1];
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][j].read();
	for(int i=1;i<=n;i++){
		for(int len=m;len;len--){
			for(int l=1;l+len-1<=m;l++){
				int r=l+len-1;
				f[l][r]=zero;
				if(l>1){
					A q=f[l-1][r]+a[i][l-1]*p[m-len];
					if(f[l][r]<q)
						f[l][r]=q;
				}
				if(r<m){
					A q=f[l][r+1]+a[i][r+1]*p[m-len];
					if(f[l][r]<q)
						f[l][r]=q;
				}
			}
		}
		A maxx=zero;
		for(int j=1;j<=m;j++){
			A q=f[j][j]+a[i][j]*p[m];
			if(q>maxx)
				maxx=q;
		}
		ans=maxx+ans;
	}
	ans.print();
	return 0;
}

g.城市交通

逆向思考:现在想要从城市1到达城市11,则只能从城市8、9或10中转过去,如果知道了从城市1到城市8、9和10的最短距离,那么只要把这3个最短距离分别加上这3个城市与城市11之间的距离,再从中取一个最小值即可。这样一来,问题就变成了求城市1到城市8、9、10的最短距离,而这3个子问题与原问题是完全-致的,只是问题的规模缩小了一点。如何求城市1到城市8的最短距离呢?想法与刚才一样,如果知道了城市1到城市4和5的最短距离,那么到城市8的最短距离就是到城市4的最短距离加上5以及到城市5的最短距离加 上5当中较小的那个值。而如何求城市4和5的最短距离呢? ...如此下去,直到求城市1到城市2和3的最短距离,而这个值是已知的(因为城市1和2.3之间有直接的边相连)

#include<bits/stdc++.h>
using namespace std;
int f[111];
int a[111][111];
int n;
char c;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
		}
		f[i]=0x3f3f3f3f;
	}
	f[n]=0;
	for(int i=n-1;i;i--)
		for(int j=i+1;j<=n;j++)
			if(a[i][j] && f[j]!=0x3f3f3f3f)
				f[i]=min(f[i],a[i][j]+f[j]);
	cout<<f[1];
	
}
/*
11
0 5 3 0 0 0 0 0 0 0 0
5 0 0 1 6 3 0 0 0 0 0
3 0 0 0 8 0 4 0 0 0 0
0 1 0 0 0 0 0 5 6 0 0
0 6 8 0 0 0 0 5 0 0 0
0 3 0 0 0 0 0 0 0 8 0
0 0 4 0 0 0 0 0 0 3 0 
0 0 0 5 5 0 0 0 0 0 3
0 0 0 6 0 0 0 0 0 0 4
0 0 0 0 0 8 3 0 0 0 3
0 0 0 0 0 0 0 3 4 3 0

*/

h.最长公共子序列

终极板题

定义状态f[i][j],表示序列X的前i个元素和序列Y的前j个元素的最长公共子序列长度。状态转移方程为


    以上算法时间复杂度为0(len1 xlen2),已经比较优了。空间复杂度可不可以优化呢?可以采用类似于杨辉三角形的做法,采用“滚动”数组,利用两个-维数组分别存放当前行和上一行,进行迭代存储。

 

#include<bits/stdc++.h>
using namespace std;
int f[2][5050];
int main(){
	char a[5050],b[5050];
	scanf("%s",a);
	scanf("%s",b);
	int la=strlen(a),lb=strlen(b);
	for(int i=1;i<=la;i++){
		for(int j=1;j<=lb;j++){
			if(a[i-1]==b[j-1]) f[i&1][j]=f[(i-1)&1][j-1]+1;
			else f[i&1][j]=max(f[i&1][j-1],f[(i-1)&1][j]);
		}
	}
	cout<<f[la&1][lb];
}

i.玩具装箱

正解斜率优化,但n^2优化可过,转移方程如下

dp[i]=min\left (dp[j]+ \left ( sum[i]+i-sum[j]-j-1-L \right )^{2}\right)(j<i)

优化:记录每一次可将法dp(i)更新的最大j,对于dp(i+1)就从j开枚举断点;

#include<bits/stdc++.h>
using namespace std;
long long dp[50010],sum[50010],a[50010];
long long L;
int n;
int main(){
	scanf("%d%lld",&n,&L);
	for(int i=1;i<=n;i++){
		scanf("%lld",a+i);
		sum[i]=sum[i-1]+a[i];
	}
	for(int i=1,k=0;i<=n;i++){
		dp[i]=1e18;
		for(int j=k;j<i;j++){
			if(dp[j]+(sum[i]+i-sum[j]-j-L-1)*(sum[i]+i-sum[j]-j-L-1)<=dp[i]){
				dp[i]=dp[j]+(sum[i]+i-sum[j]-j-L-1)*(sum[i]+i-sum[j]-j-L-1);
				k=j;
			}
		}
	}
	cout<<dp[n];
}

能过的原因可能是因为本来每一次增多都至少增加二的单位长度,对于包含同一段物品的两盒东西差值至少也为二,又加上平方,即便在前某一点选择错了,但最后又通过后面的正确决策将其覆盖

当然还有可能这个做法确实正确,但我太弱无法证明……

斜率优化……没学啊!!!

j.最长公共上升子序列

两个板子的合体,但难度增加了许多

F[i,j]表示A_{1}\sim A_{i}B_{1}\sim B_{j}可以构成的以B_{j}为结尾的LCIS的长度。不妨假设A_{0}=B_{0}=-\infty

        当A_{i}\neq B_{j}时,有F[i,j]=F[i-1,j]

        当A_{i}= B_{j}时,有

F\left [ i,j \right ]-1=max\left \{ F\left [ i-1,k \right ] \right \}\left(0\leq k< j,B_{k}<B_{j} \right )=max\left \{ F\left [ i-1,k \right ] \right \}\left(0\leq k< j,B_{k}<A_{i} \right )

在转移过程中,将满足0\leq k< j,B_{k}<A_{i}的k构成的集合称为F[i,j]进行状态转移时的决策集合,记为S(i,j)。注意到,在第二层循环j从1增加到m时,第一层循环i是一个定值,这使得条件B_{k}<A_{i}

是固定的。因此当便变量j增加1时,k的取值范围从0\leq k<j变为0\leq k<j+1,即整数j可能会进入新的决策集合、也就是说,我们只需O(1)地检查条件B_{j}<A_{i}是否满足,已经在决策集合中的数则一定不会被去除:

                S\left(i,j+1 \right )=\left\{\begin{matrix} S\left(i,j \right ) & B_{j}\geq A_{i}\\ S\left(i,j \right )\bigcup \left \{ j \right \} & B_{j}< A_{i} \end{matrix}\right.

所以上面的转移方程只需要两层循环即可求解。最终的目标是\max_{1\leq j<m}F[n,j]

for (int i = 1; i <= n; i++) {
    // val是决策集合S(i,j)中f[i-1][k]的最大值
    int val = 0;
    // j=1时,0可以作为k的取值
    if (b[0] < a[i]) val = f[i - 1][0];
    for (int j = 1; j <= m; j++) {
        if (a[i] == b[j]) f[i][j] = val + 1;
        else f[i][j] = f[i - 1][j];
        // j即将增大为j+1,检查j能否进入新的决策集合
        if (b[j] < a[i]) val = max(val, f[i - 1][j]);
    }
}

总结:大部分都还是挺基础的但分差较大原因多是没注意范围

标签:std,return,int,namespace,初级,using,9.6,include,dp
来源: https://blog.csdn.net/int128maxn/article/details/120142395