zoukankan      html  css  js  c++  java
  • 区间类型DP

    一般是由长度小的子问题推到长度大的子问题,解法一般比较固定,先枚举长度再枚举左端点 最后枚举中间的分割点

    通过小区间的情况扩展到大区间

    1、合并石子1

    线性的,合并相邻的,通过计算前缀和减少了计算量,但也是O(N^3)

    cin>>n;
        for(int i=1;i<=n;i++) {
            cin>>a[i];
            s[i]=s[i-1]+a[i];
        }
        memset(f,127/3,sizeof(f));
        for(int i=1;i<=n;i++) f[i][i]=0;  //赋一个极大的值但是相加不会溢出
        for(int i=n-1;i>=1;i--){
            for(int j=i+1;j<=n;j++){
                for(int k=i;k<=j-1;k++){
                    if(f[i][j]>f[i][k]+f[k+1][j]+s[j]-s[i-1]) //把i--k和k+1--j这两堆合并起来,原本的得分加上现在的新增的得分
                    f[i][j]=f[i][k]+f[k+1][j]+s[j]-s[i-1];
                }
            }
        }
        cout<<f[1][n]<<endl;

    2、合并石子2

    题解:https://www.luogu.com.cn/problem/P1880

    在一个圆形操场的四周摆放 NN 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出一个算法,计算出将 NN 堆石子合并成 11 堆的最小得分和最大得分。

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    //区间DP
    //把环形化成线性 
    //合并石子这道题:很典型的区间dp,通过求解子区间,然后按照合并为大区间
    int w[221],dp1[220][220];  //存储区间i--j 
    int a[220]; 
    int n;
    int dp2[220][220];
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		cin>>a[i];
    		a[i+n]=a[i];  
    		w[i]=w[i-1]+a[i];  //前缀和减少计算 
    	} 
    	for(int i=n+1;i<=2*n;i++) w[i]=w[i-1]+a[i];
    	for(int len=1;len<n;len++){
    		for(int i=1,j=i+len;i<(n*2)&&j<(2*n);i++,j=i+len){
    			dp1[i][j]=INF;
    			dp2[i][j]=-1;
    			for(int k=i;k<j;k++){  //中间元素 
    				dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+w[j]-w[i]+a[i]);
    				dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+w[j]-w[i]+a[i]);
    			}
    		}
    	}
    	int maxx=-1,minn=INF;
    	for(int i=1;i<=n;i++){
    		maxx=max(maxx,dp2[i][i+n-1]);
    		minn=min(minn,dp1[i][i+n-1]);
    	}
    	cout<<minn<<endl<<maxx<<endl;
    return 0;
    }
    

    通过四边形不等式进行优化,时间复杂度下降至O(N^2),因为保存上一次的最佳分割点,证明自己找资料再看看:

    for(int k=rel[i][j-1];k<=rel[i+1][j];k++)

    以合并石子2为例

    //经过四边形不等式优化的合并石子
    int rel[maxn][maxn];
    int dp[maxn][maxn]; 
    int summ[maxn];
    int a[maxn];
    int n;
    int main(){
    	cin>>n;
    	memset(summ,0,sizeof(summ));
        memset(dp,0x3f,sizeof(dp));   //别忘了初始化 
    	for(int i=1;i<=n;i++){
    		cin>>a[i];
    		dp[i][i]=0;
    		rel[i][i]=i;
    		summ[i]=summ[i-1]+a[i];
    	}
    	for(int i=1;i<=n;i++){
    		summ[i+n]=summ[i+n-1]+a[i];
    		dp[i+n][i+n]=0;
    		rel[i+n][i+n]=i+n;
    	}
    	for(int len=1;len<=n;len++){
    		for(int i=1;i<=2*n-len;i++){
    			int j=i+len-1;
    			for(int k=rel[i][j-1];k<=rel[i+1][j];k++){
    				if(dp[i][j]>dp[i][k]+dp[k+1][j]+summ[j]-summ[i-1]) {
    					dp[i][j]=dp[i][k]+dp[k+1][j]+summ[j]-summ[i-1];
    					rel[i][j]=k; 
    				}
    				
    			}
    		}
    	}
    	int ans= 0xfffffff;
    	for(int i=1;i<=n;i++){
    		ans=min(ans,dp[i][i+n-1]);
    	}
    	cout<<ans<<endl;
    return 0;
    } 

    3、凸包的路径

    这道题将环形的信息转化为了长度为2倍的链,就变成了线性的问题

    而且这道题。。。我觉得我不是很懂

    CF838E Convex Countour

    按顺时针给出一个凸多边形,任意两点间有一条直线边,求每个点恰好一次的最长不自交(和之前线段相交)路径

    题解:https://www.luogu.com.cn/problemnew/solution/CF838E

    //https://www.luogu.com.cn/problemnew/solution/CF838E 
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    
    const int N=2510;
    struct Point {
        double x, y;
        Point(int x=0, int y=0):x(x), y(y){}
    } p[N];
    double dis(Point a, Point b) {
        return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    }
    double f[N][N][2];
    int n;
    
    int main() {
        scanf("%d", &n);
        for (int i=0; i<n; i++) {
            int x, y; scanf("%d%d", &x, &y);
            p[i]=Point(x, y);
        }
        for (int len=2; len<=n; len++)
            for (int l=0; l<n; l++) {
                int r=(l+len-1)%n;
                f[l][r][0]=max(f[(l+1)%n][r][0]+dis(p[l], p[(l+1)%n]), f[(l+1)%n][r][1]+dis(p[l], p[r]));
                f[l][r][1]=max(f[l][(r-1+n)%n][0]+dis(p[r], p[l]), f[l][(r-1+n)%n][1]+dis(p[r], p[(r-1+n)%n]));
            }
        double ans=0;
        for (int i=0; i<n; i++) ans=max(ans, max(f[i][(i+n-1)%n][0], f[i][(i+n-1)%n][1]));
        printf("%.10lf", ans);
        return 0;
    }
    

      

    4、矩阵相乘提高版,或者题目也可以说成是抽卡片,抽卡片更好理解

    给出n个数字,不能删除两边,删除i的代价是a[i-1]*a[i]*a[i+1]

    简单的区间DP,设f[i][j]为[i,j][i,j]为一个矩阵的最小代价

    f[i][j]=min(f[i][k]+f[k][j]+a[i]a[k]a[j],f[i][j])

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    long long a[110],dp[110][110];
    //区间dp
    //dp[i][j]=min(dp[i][k]+dp[k+1][j]+a[i-1][k][k+1] 
    int n; 
    
    int main(){
    	cin>>n;
    	int x;   
     	for(int i=0;i<=n;i++){
    		cin>>a[i];
    		dp[i][i]=0;
    		if(i!=0&&i!=n) cin>>x; //根据转移表达式,有一个不用输入 
     	}
    	for(int len=2;len<=n;len++){
    		for(int i=1;i<=n-len+1;i++){
    			int j=i+len-1;
    			long long temp=dp[i+1][j]+a[i-1]*a[i]*a[j];
    			for(int k=i+1;k<j;k++){
    				long long t=dp[i][k]+dp[k+1][j]+a[i-1]*a[k]*a[j];
    				temp=min(temp,t);
    			}
    			dp[i][j]=temp;
    		}
    	}
    	cout<<dp[1][n]<<endl;
    return 0;
    }
    

    5、添加括号

    【题目背景】
      给定一个正整数序列a1,a2,...,an,(1<=n<=20)
      不改变序列中每个元素在序列中的位置,把它们相加,并用括号记每次加法所得的和,称为中间和。
      例如:
      给出序列是4,1,2,3。
      第一种添括号方法:
        ((4+1)+(2+3))=((5)+(5))=(10)
      有三个中间和是5,5,10,它们之和为:5+5+10=20
      第二种添括号方法 
        (4+((1+2)+3))=(4+((3)+3))=(4+(6))=(10)
      中间和是3,6,10,它们之和为19。 
    【问题描述】 
      现在要添上n-1对括号,加法运算依括号顺序进行,得到n-1个中间和,求出使中间和之和最小的添括号方法。 

    const int maxn=1010;
    const int INF=0x3fffffff;
    //区间dp:但是涉及三个问题
    //第一个:找到最优值,这个就是转台转移表达式
    //首先是dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]) dp[i][j]+=sum(i,j)
    //然后是输出括号:记录每个数字前面有多少个左括号,后面有多少个右括号
    //最后是输出中间值
    //后面两个问题都是通过DFS来解决,当然是在第一个问题通过DP解决了,并且记录了最优值得基础上
    int a[21],summ[21][21]; //注意理解这个summ数组,是一个斜三角形,summ[i][j]就代表了从i到j得和,因为第i行就是从第i-1开始的 
    int dp[21][21]; //最小值
    int be[21],af[21]; // 记录每个数字前面有多少个左括号,后面有多少个右括号
    int n;
    int res[21][21];
    void dfs(int l,int r){
    	if(l==r) return ;
    	dfs(l,l+res[l][r]); //递归
    	dfs(l+res[l][r]+1,r);
    	be[l]++;
    	af[r]++ ;
    }
    void dfs2(int l,int r){
    	if(l==r) return;
    	dfs2(l,l+res[l][r]);
    	dfs2(l+res[l][r]+1,r);
    	cout<<summ[l][r]<<" "; //最后输出 
    }
    
    int main(){
    	cin>>n;
    	memset(dp,0x3f,sizeof(dp));
    	for(int i=1;i<=n;i++){
    		cin>>a[i];
    		dp[i][i]=0;
    	}
    	for(int i=1;i<=n;i++){
    		for(int j=i;j<=n;j++) summ[i][j]=summ[i][j-1]+a[j]; //初始summ数组 
    	}
    	for(int len=1;len<n;len++){
    		for(int i=1;i<=n-len;i++){
    			int j=i+len;
    			for(int step=0;step<len;step++){  //记录 
    				if(dp[i][j]>dp[i][i+step]+dp[i+step+1][j]){ 
    					dp[i][j]=dp[i][i+step]+dp[i+step+1][j];
    					res[i][j]=step;  //i和j之间的括号在那个位置 
    				}
    			}
    			dp[i][j]+=summ[i][j] ;// 还要加上和 
    		}
    	}
    	dfs(1,n);
    	//记录左右括号 
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=be[i];j++) cout<<"("; //前面有多少个左括号
    		cout<<a[i];
    		for(int j=1;j<=af[i];j++) cout<<")";
    		if(i!=n) cout<<"+"; 
    	} 
    	cout<<endl<<dp[1][n]<<endl;
    	dfs2(1,n);
    return 0;
    }
    

    6、最长括号匹配   Poj2955 括号匹配(一)

    dp[i][j]表示i~j个字符间的最长匹配

    if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2

    dp[i][j]=max{dp[i][j],dp[i][k]+dp[k+1][j]}

    int dp[105][105];
    int main()
    {
        char s[105];
        while(scanf("%s",s+1)!=EOF)
        {
            memset(dp,0,sizeof(dp));//dp初始化为0,因为一方面是找最大之,一方面初始匹配数为0
            int len = strlen(s+1);//dp[i][i]不用处理,因为自己和自己不匹配就是0
            if(s[1]=='e')break;
            for(int l = 1;l<=len;l++){
                for(int i = 1;i+l<=len+1;i++){
                    int j= i+l-1;
                    if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']')){//如果匹配,先更新
                        dp[i][j] = dp[i+1][j-1]+2;
                    }
                    for(int k = i;k<j;k++){//k<j
                        dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]);
                    }
                }
            }
            cout<<dp[1][len]<<endl;
        }
        return 0;
    }

    7、hdu 4632 Palindrome subsequence

     最多回文子串,要求不连续,位置不同就可以了

    先全部都处理:dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]   这个操作是并上,有容斥原理可以得到,而且这个是一个固定的状态,注意要取模,所以要先加模,再取模

    如果s[i]==s[j]的话,那么就可以分为dp[i][j]=dp[i][j]+dp[i+1][j-1]+1,

    //最多回文子串
    //先全部都处理:dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]
    //如果s[i]==s[j]的话,那么就可以分为dp[i][j]=dp[i][j]+dp[i+1][j-1]+1
    char a[maxn]; 
    int n;
    int dp[maxn][maxn];
    int main(){
    	scanf("%d",&n);
    	int index=1;
    	while(n--){
    		scanf("%s",a+1);
    
    		int nn=strlen(a+1);
    		memset(dp,0,sizeof(dp));
    		for(int i=1;i<=nn;i++) dp[i][i]=0;
    		for(int len=1;len<=nn;len++){
    			for(int i=1;i<=nn-len+1;i++){
    				int j=i+len-1;
    				dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+10007)%10007;
    				if(a[i]==a[j]) dp[i][j]=(dp[i][j]+dp[i+1][j-1]+1)%10007;
    			}
    		}
    		printf("Case %d: %d
    ",index++,dp[1][nn]);
    	}
    	
    	
    return 0;
    }
    

    8、整数划分

    给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积

    这里给的乘号是有限个,所以状态方程里必须包含使用乘号的个数,此外还要包含区间长度。所以怎么用二维dp实现包含m和n,我们可以用dp[i][j]表示在第1~i个字符里插入j个乘号的最大值。

    状态转移方程 dp[i][j]表示在第1i个字符里插入j个乘号的最大值;用num[i][j]表示第ij个字符表示的数字;

    dp[i][j] = max(dp[i][j],dp[k][j-1]*num[k+1][i])

    1570:【例 2】能量项链

    这个其实就是矩阵相乘,一样的转移表达式

    注意要拆分乘2*N,而且这个的循环一二维循环的是区间左右下标,注意转移表达式

    dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j]*a[k+1]*a[i+1]);

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //能量项链
    //矩阵相乘提高版,或者题目也可以说成是抽卡片,抽卡片更好理解
    //f[i][j]=min(f[i][k]+f[k][j]+a[i]?a[k]?a[j],f[i][j])
    int n;
    LL a[220];
    LL dp[220][220];
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		dp[i][i]=0;
    		a[i+n]=a[i];
    	}
    	LL ans=-1;
    	for(int i=2;i<n*2;i++){  //整个区间都需要更新 
    		for(int j=i-1;i-j<n&&j>=1;j--){  //从后往前推 
    			for(int k=j;k<i;k++){
    				dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j]*a[k+1]*a[i+1]);
    			//	dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j-1]*a[k]*a[i]);
    			}
    			ans=max(ans,dp[j][i]);
    		}
    	}
    	printf("%lld",ans);
    return 0;
    }

    1571:【例 3】凸多边形的划分

    和上一道题的转移式比较像,但是这道题数据大, 要写高精度

    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //	dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);
    //但是需要写高精度
    /* 
    int n;
    LL a[51];
    LL dp[51][51];
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    	}
    	LL ans=INF;
    	for(int len=1;len<=n;len++){
    		for(int i=1;i<=n-len+1;i++){
    			dp[i][i+len]=INF;
    			dp[i][i+1]=0;  //不能形成
    			int j=i+len-1;
    			for(int k=i;k<=i+len-1;k++){
    			dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);	
    			} 
    			if(len==n) ans=min(ans,dp[i][i+n-1]);
    		}
    		
    	}
    	printf("%lld",ans);
    return 0;
    }
    */
    inline void qread(int &x){
    	x=0;
    	int ch=getchar();
    	while(ch<'0'||ch>'9') ch=getchar();
    	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    }
    struct bignum{
    	int num[48];
    	void sset(int x){
    		memset(num,0,sizeof(num));
    		while(x){
    			num[++num[0]]=x%10;
    			x/=10;
    		}
    	}
    	void clea(){
    		memset(num,0,sizeof(num));
    	}
    	void INf(){
    		num[0]=30;
    		for(int i=1;i<=num[0];i++) num[i]=9;
    	}
    };
    bignum minn(const bignum &a,const bignum &b){
    	if(a.num[0]>b.num[0]) return b;
    	if(a.num[0]<b.num[0]) return a;
    	for(int i=a.num[0];i>=1;i--) {
    		if(a.num[i]>b.num[i]) return b;
    		if(a.num[i]<b.num[i]) return a;
    	}
    	return a;
    }
    bignum add(const bignum &a,const bignum &b){
    	bignum c;
    	c.clea();
    	c.num[0]=max(a.num[0],b.num[0]);
    	int jw=0;
    	for(int i=1;i<=c.num[0];i++){
    		c.num[i]=a.num[i]+b.num[i]+jw;
    		jw=c.num[i]/10;
    		c.num[i]%=10;
    	}
    	if(jw) c.num[++c.num[0]]=jw;
    	return c;
    }
    bignum mul(bignum a,bignum b){
    	bignum c;
    	c.clea();
    	c.num[0]=a.num[0]+b.num[0];  //相乘 
    	for(int i=1;i<=a.num[0];i++){
    		int jw=0;
    		for(int j=1;j<=b.num[0];j++){
    			c.num[i+j-1]+=a.num[i]*b.num[j]+jw;
    			jw=c.num[i+j-1]/10;
    			c.num[i+j-1]%=10;
    		}
    		c.num[i+b.num[0]]=jw;
    	}
    	while(c.num[c.num[0]]==0) --c.num[0];
    	if(!c.num[0]) c.num[0]=1;
    	return c;
    }
    void show(const bignum &a){
    	for(int i=a.num[0];i>=1;i--) putchar(a.num[i]+48);
    }
    int n;
    bignum data[120];
    bignum dp[120][120];
    int main(){
    	qread(n);
    	int x;
    	for(int i=1;i<=n;i++){
    		qread(x);
    		data[i].sset(x);
    		data[i+n]=data[i];
    		
    	}
    	data[n<<1|1]=data[1];
    	for(int len=2;len<n;len++){   //格式 
    		for(int i=1;i<=(n<<1)-len;i++){
    			int j=i+len;
    			dp[i][j].INf();
    			for(int k=i+1;k<j;k++)
    				dp[i][j]=minn(dp[i][j],add(dp[i][k],add(dp[k][j],mul(data[i],mul(data[k],data[j])))));
    		}
    		
    	}
    	bignum ans;
    		ans.INf();
    		for(int i=1;i<=n;i++) ans=minn(ans,dp[i][i+n-1]);  //最后取值 
    	show(ans);
    	cout<<endl;
    	return 0;
    } 

    1573:分离与合体

    表达转移式变化了,因为题目的要求,但是做法还是一样,而且这道题还需要输出对策,

    合并时获得的价值就是 (1 号金钥匙价值 +3 号金钥匙价值)×( 2 号金钥匙价值))。

    LYD 请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。

    例如先打印一分为二的区域,然后从左到右打印二分为四的分离区域,然后是四分为八的……

    /*
    大意就是选取区间一点区间划分为左右部分,合并时得分为划分前(左端点+右端点) * 选取点的权值。
    我们还是按照老办法,用跨度来 DP 就可以得到最大得分。
    关于路径输出,因为本题中区间划分是同时进行的,就是说如果存在多段区间都可划分,那么它们是可以同时划分的,而题意又要求按照划分的先后和区域的左右输出,
    也就是说要先判断该节点是属于第几次划分的,由于我们 path 数组存放了区间的划分点,那么我们不断递归,递归树第 k 层就对应着第 k 次划分,而左右顺序取决于我们的递归顺序;
    因此我们可以通过传入变量 step 代表当前递归树的层数,num 代表当前要输出的是第几步划分出的区域,当 step = num 就说明满足条件。
    */

    所以这个DFS(pint函数比较重要)

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=303;
    const int INF=0x3fffffff;
    typedef long long LL;
    //重点是理解题意 
    /*
    大意就是选取区间一点区间划分为左右部分,合并时得分为划分前(左端点+右端点) * 选取点的权值。
    我们还是按照老办法,用跨度来 DP 就可以得到最大得分。
    关于路径输出,因为本题中区间划分是同时进行的,就是说如果存在多段区间都可划分,那么它们是可以同时划分的,而题意又要求按照划分的先后和区域的左右输出,
    也就是说要先判断该节点是属于第几次划分的,由于我们 path 数组存放了区间的划分点,那么我们不断递归,递归树第 k 层就对应着第 k 次划分,而左右顺序取决于我们的递归顺序;
    因此我们可以通过传入变量 step 代表当前递归树的层数,num 代表当前要输出的是第几步划分出的区域,当 step = num 就说明满足条件。
    */
    int n;
    LL a[maxn],dp[maxn][maxn];
    int path[maxn][maxn];
    void prin(int i,int j,int step,int num){ //step 代表当前递归树的层数,num 代表当前要输出的是第几步划分出的区域 
    //num从1增加到n表示,从一分为二,二分为四这个顺序
    //step就是一直都是1,(调用时) 
    	if(i>=j) return;
    	if(step==num) cout<<path[i][j]<<" ";
    	prin(i,path[i][j],step+1,num);
    	prin(path[i][j]+1,j,step+1,num);
    }
    void solve(){
    	for(int len=1;len<n;len++){  //这里还是模板 
    		for(int i=1;i<=n-len;i++){
    			int j=i+len;
    			for(int k=i;k<j;k++){
    				if(dp[i][j]<dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k]){
    					dp[i][j]=dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k]; //k是分割点 
    					path[i][j]=k;
    				}
    			}
    		}
    	}
    	LL mx=dp[1][n];
    	cout<<mx<<endl;
    	for(int i=1;i<=n;i++) prin(1,n,1,i);
    }
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		cin>>a[i];
    	}
    	solve();
    return 0;
    }

    1574:矩阵取数游戏

    每一行是不会互相影响的
    所以只需要对每一行算出最优,然后相加就可以了
    记得要用高精,
    用M表示输入的矩阵的一行,M[i]表示该行第i个值
    dp[i][j]表示区间变为[i,j]时的最优解
    状态转移方程为:dp[i][j]=max(dp[i-1][j]+M[i-1]* 2^{m-j+i-1} ,dp[i][j+1]+M[j+1]*2^{m-j+i-1})
    从两边向中间靠拢
    终态是区间为空,dp[i][i]+M[i]*2^m(从1~n遍历,取最大)

    !!!这道题也需要写高精度,这个真的需要练习

    //怎么连最简单的一点都没想到呢,每一行是不会互相影响的
    //所以只需要对每一行算出最优,然后相加就可以了
    //记得要用高精,
    //用M表示输入的矩阵的一行,M[i]表示该行第i个值
    //dp[i][j]表示区间变为[i,j]时的最优解
    //状态转移方程为:dp[i][j]=max(dp[i-1][j]+M[i-1]* 2^{m-j+i-1} ,dp[i][j+1]+M[j+1]*2^{m-j+i-1})
    //从两边向中间靠拢 
    //终态是区间为空,dp[i][i]+M[i]*2^m
    /*
    int n,m,a[maxn];
    LL f[maxn][maxn];  //f[i][j]之间的最优值 
    void prin(LL x){
    	if(x>9) prin(x/10);
    	putchar(x%10+'0');
    } 
    LL solve(){
    	memset(f,0,sizeof(f));
    	for(int i=1;i<=m;i++){  //从两边开始 
    		for(int j=m;j>=i;j--){
    			LL b=(LL)1<<(m-j+i-1);
    			f[i][j]=max(f[i-1][j]+(LL)a[i-1]*b,f[i][j+1]+(LL)a[j+1]*b);
    		}
    	}
    	LL mx=-1;
    	//最后一步 
    	for(int i=1;i<=m;i++) mx=max(mx,f[i][i]+(LL)a[i]*((LL)1<<m)); //取最大 
    	return mx;
    }
    int main(){
    	scanf("%d %d",&n,&m);
    	LL ans=0;
    	while(n--){
    		for(int i=1;i<=m;i++) scanf("%d",&a[i]);
    		ans+=solve();
    	}
    	prin(ans);
    return 0;
    }
    */
    
    
    //老老实实写高精度TAT  4个点过不了
    #include <bits/stdc++.h>
    using namespace std;
    const int N=85;
    const int L=105,Power=4,Base=10000;
    int n,m,a[N];
    struct Bignum
    {
        int a[L];
        Bignum(){memset(a,0,sizeof a);}
        Bignum(int x)
        {
            memset(a,0,sizeof a);
            while(x) {a[++*a]=x%10; x/=10;}
            return;
        }
        inline void Print()
        {
            int i;
            printf("%d",a[*a]);
            for(i=*a-1;i>=1;i--)
            {
                if(a[i]<1000) putchar('0');
                if(a[i]<100) putchar('0');
                if(a[i]<10) putchar('0');
                printf("%d",a[i]);
            }
            puts("");
            return;
        }
        inline void Init()
        {
            memset(a,0,sizeof a);
        }
    }Bin[N],dp[N][N];
    inline bool operator<(const Bignum &p,const Bignum &q)
    {
        if(p.a[0]!=q.a[0]) return (p.a[0]<q.a[0])?1:0;
        int i;
        for(i=p.a[0];i>=1;i--) if(p.a[i]!=q.a[i])
        {
            return (p.a[i]<q.a[i])?1:0;
        }
        return 0;
    }
    inline Bignum max(Bignum p,Bignum q)
    {
        return (p<q)?(q):(p);
    }
    inline Bignum operator+(const Bignum &p,const Bignum &q)
    {
        int i;
        Bignum ans=p;
        for(i=1;i<=q.a[0];i++)
        {
            ans.a[i]+=q.a[i];
            if(ans.a[i]>=Base){ans.a[i+1]+=ans.a[i]/Base; ans.a[i]%=Base;}
        }
        while(ans.a[ans.a[0]+1]) ans.a[0]++;
        return ans;
    }
    inline Bignum operator*(const Bignum &p,const Bignum &q)
    {
        int i,j;
        Bignum ans;
        ans.a[0]=p.a[0]+q.a[0];
        for(i=1;i<=p.a[0];i++)
        {
            for(j=1;j<=q.a[0];j++)
            {
                ans.a[i+j-1]+=p.a[i]*q.a[j];
                if(ans.a[i+j-1]>Base)
                {
                    ans.a[i+j]+=ans.a[i+j-1]/Base;
                    ans.a[i+j-1]%=Base;
                }
            }
        }
        while(!ans.a[ans.a[0]]) ans.a[0]--;
        return ans;
    }
    inline Bignum operator*(const Bignum &p,const int &q)
    {
        int i;
        Bignum ans;
        ans.a[0]=p.a[0]+5;
        for(i=1;i<=p.a[0];i++)
        {
            ans.a[i]+=p.a[i]*q;
            if(ans.a[i]>Base)
            {
                ans.a[i+1]+=ans.a[i]/Base; ans.a[i]%=Base;
            }
        }
        while(!ans.a[ans.a[0]]) ans.a[0]--;
        return ans;
    }
    inline Bignum Solve()
    {
        int i,j;
        for(i=1;i<=m;i++)
        {
            for(j=1;j+i-1<=m;j++)
            {
                int l=j,r=j+i-1;
                dp[l][r]=max(dp[l][r-1]+Bin[m-i+1]*a[r],dp[l+1][r]+Bin[m-i+1]*a[l]);
            }
        }
    //    dp[1][m].Print();
        return dp[1][m];
    }
    int main()
    {
        int i,j;
        Bignum ans;
        scanf("%d%d",&n,&m);
        Bin[0]=Bignum(1);
        for(i=1;i<=m;i++) Bin[i]=Bin[i-1]*2;
    //    for(i=1;i<=m;i++)
    //    {
    //        Bin[i].Print();
    //    }
    //    puts("");
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++) scanf("%d",&a[j]);
            ans=ans+Solve();
        }
        ans.Print();
        return 0;
    }
    

      

  • 相关阅读:
    Mysql存储过程详解
    自动化测试——人人都可自制“呼死你”
    Apktool(1)——Apktool的安装
    Apktool(2)——使用前必须知道的apk知识
    写博是种心情
    webpack使用tree shaking的问题。及关于UglifyJs不支持ES6的解决方案。
    angular2 笔记
    angular2 content projection
    angular2aotwebpack 生产环境下编译angular2
    ionic2配置问题集
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12296267.html
Copyright © 2011-2022 走看看