zoukankan      html  css  js  c++  java
  • 动态规划 之 区间DP练习

    前言

    (Loj) 放上了那么多《信息学奥赛一本通》上的题(虽然我并没有这本书),我要给它点一个大大的赞 _
    以后分类刷题不愁啦!


    正文

    那就一道道说吧。

    石子合并

    (n) 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
    (n−1) 次合并后得分总和的最大值和最小值。
    (n leq 200)

    首先注意到“绕圆形排放”,那么有一个经典的技巧就是在 (1 sim n) 后再接一个 (1 sim n) ,相当于把圆拆成一条直线
    接着就是经典的区间 (DP) ,方程 (f[i][j]=mathop{max}limits_{k=i}^{j-1} {f[i][k]+f[k+1][j]+sum[i][j] }),最小值亦然
    (sum[i][j])为第 (i) 堆到第 (j) 堆石子数之和,可以用前缀和算出来。
    注意转移的顺序为区间大小从小至大。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 405;
    
    int n;
    int f[N][N],g[N][N],s[N];
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) 
    		scanf("%d",&f[i][i]),f[i+n][i+n]=f[i][i];
    	for(int i=1;i<=n*2;i++) 
    		s[i]=s[i-1]+f[i][i],f[i][i]=0;
    	
    	for(int i=2;i<=n;i++)
    		for(int j=1;j+i-1<=n*2;j++){
    			g[j][i+j-1]=2*1e9;
    			for(int k=j;k<j+i-1;k++){
    				f[j][i+j-1]=max(f[j][i+j-1],f[j][k]+f[k+1][i+j-1]+s[i+j-1]-s[j-1]);
    				g[j][i+j-1]=min(g[j][i+j-1],g[j][k]+g[k+1][i+j-1]+s[i+j-1]-s[j-1]);
    			}
    		}
    	
    	int Max=0,Min=2*1e9;
    	for(int i=1;i<=n;i++)
    		Max=max(Max,f[i][i+n-1]),Min=min(Min,g[i][i+n-1]);
    	printf("%d
    %d",Min,Max);
    	
    	return 0;
    }
    

    能量项链

    在项链上有 (N) 颗能量珠。能量珠是一颗有头标记和尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记必定等于后一颗珠子的头标记。如果一颗能量珠头标记为 (m),尾标记为 (r),后一颗能量珠头标记为 (r),尾标记为 (n),则聚合后释放出 (m imes r imes n) 单位的能量,新珠子头标记为 (m),尾标记为 (n)
    项链上有 (n) 颗珠子,相邻两颗珠子可以合并成一个,合并同时会放出一定的能量,不同珠子合并放出能量不相同,请问按怎样的次序合并才能使得释放的能量最多?
    (n leq 100)

    与第一题套路差不多,这个题所有能量珠也是围成一个圈的(吐槽一下,这个事情原题中说的很不清楚)
    所以也是 (1sim n) 后再接 (1 sim n)
    转移方程也很套路,(f[i][j]=mathop{max}limits_{k=i}^{j-1} { f[i][k]+f[k+1][j]+w[i] imes w[k] imes w[j] })

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 205;
    typedef long long ll;
    
    int n;
    ll w[N],f[N][N];
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%lld",&w[i]),w[i+n]=w[i];
    	
    	for(int i=3;i<=n+1;i++)
    		for(int j=1;j+i-1<=2*n;j++)
    			for(int k=j+1;k<j+i-1;k++)
    				f[j][j+i-1]=max(f[j][j+i-1],f[j][k]+f[k][j+i-1]+w[j]*w[j+i-1]*w[k]);
    	
    	ll ans=0;
    	for(int i=1;i<=n;i++) ans=max(ans,f[i][i+n]);
    	printf("%lld
    ",ans);
    	
    	return 0;
    }
    

    凸多边形的划分

    给定一个具有 (N) 个顶点的凸多边形,将顶点从 (1)(N) 标号,每个顶点的权值都是一个正整数。将这个凸多边形划分成 (N−2) 个互不相交的三角形,试求这些三角形顶点的权值乘积和至少为多少。
    (N leq 50) ,每个点权值 (leq 10^9)

    这是一道好题!!(因为我居然想了好半天没想出来,最后看的题解【捂脸】)
    之前没想到是因为不知道怎么让区间连续起来,有些三角形三个顶点都不连续……
    后来才意识到,当前多边形每一条边都一定在一个三角形中,故只需要拿住一条边,再选一个点作为三角形另一个顶点,然后就会把多边形分为两个部分,而这两个部分的多边形顶点是连续的。好巧好巧!
    多边形定点是连成一个圈的,故也要在 (1 sim n) 后接 (1 sim n)
    方程 (f[i][j]=mathop{max}limits_{k=i+1}^{j-1} { f[i][k]+f[k][j]+w[i] imes w[k] imes w[j] })
    坑人的是要写高精度 (qwq)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    
    using namespace std;
    
    const int N = 55*2;
    const int SZ =1000;
    
    struct Bign{
    	int s[15],len;
    	Bign() { len=0; memset(s,0,sizeof(s)); }
    	void print(){
    		printf("%d",s[len-1]);
    		for(int i=len-2;i>=0;i--) {
    			if(s[i]<10) printf("0");
    			if(s[i]<100) printf("0");
    			printf("%d",s[i]);
    		}
    		printf("
    ");
    	}
    	Bign operator = (int x) {
    		if(x==0) s[len++]=0;
    		while(x!=0) s[len++]=x%SZ,x/=SZ;
    		return *this;
    	}
    	Bign operator + (const Bign &b) const {
    		Bign c;
    		for(int i=0,g=0;;i++){
    			if(i>=len && i>=b.len && g==0) break;
    			c.s[c.len++]=(s[i]+b.s[i]+g)%SZ;
    			g=(s[i]+b.s[i]+g)/SZ;
    		}
    		return c;
    	}
    	Bign operator * (const Bign &b) const {
    		Bign c;
    		for(int i=0;i<len;i++)
    			for(int j=0;j<b.len;j++) c.s[i+j]+=s[i]*b.s[j];
    		c.len=len+b.len-1; /**/
    		for(int i=0,g=0;;i++){
    			if(i>=c.len && c.s[i]==0 && g==0) { c.len=i; break; }
    			g=c.s[i]+g;
    			c.s[i]=g%SZ;
    			g/=SZ;
    		}
    		return c;
    	}
    	bool operator > (const Bign &b) const {
    		if(len!=b.len) return len>b.len;
    		for(int i=len-1;i>=0;i--)
    			if(s[i]!=b.s[i]) return s[i]>b.s[i];
    		return false;
    	}
    }w[N],f[N][N],MAX; 
    int n;
    
    int main()
    {
    	int x;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&x),w[i]=x,w[i+n]=x;
    	
    	MAX.len=15; MAX.s[14]=1;
    	for(int i=1;i<n*2;i++) f[i][i+1]=(x=0);
    	for(int i=3;i<=n;i++)
    		for(int j=1;j+i-1<=n*2;j++){
    			f[j][i+j-1]=MAX;
    			for(int k=j+1;k<j+i-1;k++)
    				if(f[j][i+j-1]>f[j][k]+f[k][i+j-1]+w[j]*w[k]*w[i+j-1]) 
    					f[j][i+j-1]=f[j][k]+f[k][i+j-1]+w[j]*w[k]*w[i+j-1];
    		}
    	
    	Bign m=MAX;
    	for(int i=1;i<=n;i++) if(m>f[i][i+n-1]) m=f[i][i+n-1];
    	m.print();
    	
    	return 0;
    }
    

    括号配对

    (BE) 中有一类被称为 (GBE)
    以下是 (GBE) 的定义:
    1.空表达式是 (GBE)
    2.如果表达式 (A)(GBE),则 ([A])((A)) 都是 (GBE)
    3.如果 (A)(B) 都是 (GBE),那么 (AB)(GBE)
    给定 (BE) ,问最少添加多少字符可将其变为 (GBE)
    字符串长度小于100

    这个就是很常规的区间(DP),感觉没什么好说的
    对于 (f[i][j]) ,先讨论 (s[i])(s[j]) 是否可凑成一对中括号或小括号,如果可以的话 (f[i][j]=f[i+1][j-1])
    接着 (f[i][j]=mathop{max}limits_{k=i}^{j-1} { f[i][k]+f[k+1][j]})

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    
    #define INF 100007
    
    using namespace std;
    
    const int N = 105;
    
    int n;
    int f[N][N];
    char s[N];
    
    int main()
    {
    	scanf("%s",s);
    	n=strlen(s);
    	
    	for(int i=0;i<n;i++) 
    		if(s[i]=='(' || s[i]==')' || s[i]=='[' || s[i]==']') f[i][i]=1;
    	for(int i=2;i<=n;i++)
    		for(int j=0;j+i-1<n;j++){
    			f[j][i+j-1]=INF;
    			if(s[j]=='(' && s[i+j-1]==')') f[j][i+j-1]=min(f[j][i+j-1],f[j+1][i+j-2]);
    			if(s[j]=='[' && s[i+j-1]==']') f[j][i+j-1]=min(f[j][i+j-1],f[j+1][i+j-2]);
    			for(int k=j;k<i+j-1;k++)
    				f[j][i+j-1]=min(f[j][i+j-1],f[j][k]+f[k+1][i+j-1]);
    		}
    	printf("%d
    ",f[0][n-1]);
    	
    	return 0;
    }
    

    分离与合体

    原题太长了我要吐槽!!!
    杜神牛造了 (n) 个区域,他们紧邻着排成一行,编号 (1…n) 。在每个区域里都放着一把 (OI) 界的金钥匙,每一把都有一定的价值。
    一开始 (LYD) 可以选择 (1…n−1) 中的任何一个区域进入,我们不妨把这个区域记为 (k)。进入后 (LYD) 会在 (k) 区域发生分离,从而分离成两个小 (LYD)。分离完成的同时会有一面墙在 (k) 区域和 (k+1) 区域间升起,从而把 (1…k)(k+1…n) 阻断成两个独立的区间,并在各自区间内任选除区间末尾之外的任意一个区域再次发生分离,这样就有了四个小小 (LYD)……重复以上所叙述的分离,直到每个小 (LYD) 发现自己所在的区间只剩下了一个区域,那么他们就可以抱起自己梦寐以求的 (OI) 金钥匙。
    但是 (LYD) 不能就分成这么多个个体存在于世界上,这些小 $ LYD$ 还会再合体,合体的小 $ LYD$ 所在区间中间的墙会消失。合体会获得 (合并后所在区间左右端区域里金钥匙价值之和)( imes) (之前分离的时候所在区域的金钥匙价值)。
    (LYD) 请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
    (n leq 300)

    感觉这题难点在于耐下心把题看完……
    方程 (f[i][j]=mathop{max}limits_{k=i}^{j-1} { f[i][k]+f[k+1][j]+(a[i]+a[j]) imes a[k] })
    因为最后要输出方案,所以每个区域要记录下使 (f[i][j]) 取到 (max)(k)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 305;
    typedef long long ll;
    typedef pair<int,int> P;
    
    int n;
    ll a[N],f[N][N];
    int g[N][N];
    
    int head,tail;
    P que[N*N];
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    	
    	for(int i=2;i<=n;i++)
    		for(int j=1;j+i-1<=n;j++)
    			for(int k=j;k<j+i-1;k++)
    				if(f[j][i+j-1]<f[j][k]+f[k+1][i+j-1]+(a[j]+a[i+j-1])*a[k])
    					f[j][i+j-1]=f[j][k]+f[k+1][i+j-1]+(a[j]+a[i+j-1])*a[k],g[j][i+j-1]=k;
    	
    	printf("%lld
    ",f[1][n]);
    	que[tail++]=P(1,n);
    	while(head<tail){
    		int x=que[head].first,y=que[head++].second;
    		if(x==y) continue;
    		if(x!=1 || y!=n) printf(" %d",g[x][y]);
    		else printf("%d",g[x][y]);
    		que[tail++]=P(x,g[x][y]);
    		que[tail++]=P(g[x][y]+1,y);
    	}
    	
    	return 0;
    }
    

    矩阵取数游戏

    对于给定的 (n imes m) 的矩阵,矩阵中每个元素 (a_{i,j}) 均为非负整数。游戏规则如下:
    1.每次取数时必须从每行各取走一个元素,共 (n) 个,(m) 次取完所有元素。
    2.每次取走的各个元素只能是该元素所在行行首或行尾。
    3.每次取数都有一个的分值,为每行取数得分之和,每行取数得分=被取走元素值 ( imes 2^i),其中 (i) 表示第 (i) 次取数,从 1 开始计数。
    4.游戏结束时,总得分为 (m) 次取数得分之和。
    求取数后的最大得分。(n,m leq 80, a_{i,j} leq 10^3)

    这个也没啥难的,每行一次区间(dp)
    方程 (f[i][j]=max { f[i][j-1] +a[j] imes 2^{m-(j-i+1)+1} , f[i+1][j] +a[i] imes 2^{m-(j-i+1)+1} })
    恶心的是又要加高精度。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    
    using namespace std;
    
    const int N = 85;
    const int SZ = 10000;
    
    struct Bign{
    	int s[10],len;
    	Bign() { len=0; memset(s,0,sizeof(s)); }
    	Bign operator = (int x){
    		len=0; memset(s,0,sizeof(s));
    		if(x==0) s[len++]=0;
    		while(x!=0) s[len++]=x%SZ,x/=SZ;
    		return *this;
    	}
    	Bign operator + (const Bign &b) const{
    		Bign c;
    		for(int i=0,g=0;;i++){
    			if(i>=len && i>=b.len && g==0) break;
    			c.s[c.len++]=(s[i]+b.s[i]+g)%SZ;
    			g=(s[i]+b.s[i]+g)/SZ;
    		}
    		return c;
    	}
    	Bign operator * (const int &x) const{
    		Bign c;
    		for(int i=0,g=0;;i++){
    			if(i>=len && g==0) { c.len=i; break; }
    			g=g+x*s[i];
    			c.s[i]=g%SZ;
    			g/=SZ;
    		}
    		return c;
    	}
    	bool operator < (const Bign &b) const{
    		if(len!=b.len) return len<b.len;
    		for(int i=len-1;i>=0;i--)
    			if(s[i]!=b.s[i]) return s[i]<b.s[i];
    		return false;
    	}
    	void print(){
    		printf("%d",s[len-1]);
    		for(int i=len-2;i>=0;i--){
    			if(s[i]<10) printf("0");
    			if(s[i]<100) printf("0");
    			if(s[i]<1000) printf("0");
    			printf("%d",s[i]);
    		}
    		printf("
    ");
    	}
    }mod[N],f[N][N],ans;
    
    int n,m;
    int a[N];
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	
    	mod[0]=1;
    	for(int i=1;i<=m;i++) mod[i]=mod[i-1]*2;
    	
    	ans=0;
    	for(int x=1;x<=n;x++){
    		for(int y=1;y<=m;y++) 
    			scanf("%d",&a[y]),f[y][y]=mod[m]*a[y];
    		
    		for(int i=2;i<=m;i++)
    			for(int j=1;j+i-1<=m;j++){
    				f[j][i+j-1]=0;
    				if(f[j][i+j-1]<f[j][i+j-2]+mod[m-i+1]*a[i+j-1])
    					f[j][i+j-1]=f[j][i+j-2]+mod[m-i+1]*a[i+j-1];
    				if(f[j][i+j-1]<f[j+1][i+j-1]+mod[m-i+1]*a[j])
    					f[j][i+j-1]=f[j+1][i+j-1]+mod[m-i+1]*a[j];
    			}
    		ans=ans+f[1][m];
    	}
    	ans.print();
    	
    	return 0;
    }
    
    既然选择了远方,便只顾风雨兼程
  • 相关阅读:
    小记:利用递归调用循环寻找MP3文件的方法。
    模拟QQ侧滑控件 实现三种界面切换效果(知识点:回调机制,解析网络json数据,fragment用法等)。
    解析网络json数据,模拟美团界面显示。
    解析Json的谷歌官方方法Gson和阿里巴巴的fastJson方法。
    Json文件放入Assets文件,读取解析并且放入listview中显示。
    Android Fragment完全解析,关于碎片你所需知道的一切 (转)。
    安卓Json介绍(转)。
    Java中a+=b和a=a+b的区别
    线程安全的单例模式
    java中伪共享问题
  • 原文地址:https://www.cnblogs.com/lindalee/p/9770687.html
Copyright © 2011-2022 走看看