zoukankan      html  css  js  c++  java
  • 与高精死杠的几天——记两道简单的高精dp

    (同样也是noip往年的题

    1​.矩阵取数游戏

    题目链接[Luogu P1005 矩阵取数游戏]

    (mathcal{SOLUTION}:)

    通过对题目条件的分析,我们可以发现,每一行取数对答案的影响是互相独立,互不影响的。所以我们可以从一个1*m的矩阵开始研究:

    因为每次取数只能取最左边或最右边,这又简化了我们的思路:

    我们设(dp[i][j][k])表示第i轮取数,还没有取的区间范围为[j,k],形象一点的话:k-j+1(区间长度)=m-i;

    考虑转移:对于(dp[i][j][k]),我们要考虑,第i次取数取的哪个数:

    显然取完后的区间变为[j,k],那么因为只能取最左边或最右边,所以第i次取走的数,可能是在j再左边一个,也就是j-1的位置,也可能是k右边的位置,即k+1的位置。因此我们基本的转移方程就出来了:

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

    k的位置不需要for枚举,每次取数时,区间长度是固定的,因此可以只枚举i,j,k=j+m-i-1;

    当k>m时,break;

    如果取到第m轮的话,我们的区间变成了[a,a-1]的形式,这显然是不行的,因此我们需要在第m-1轮时,顺带处理第m轮的情况(反正取到m-1轮以后只剩一个数了嘿,于是:

    (This=max(This,dp[m-1][j][j]+a[j]*2^m))

    找到This的最大值。

    对每一行都dp一遍,然后将最大值加和,即是最终答案。

    但是如果你只是这样的话,只能得到60pts的好成绩;

    m<=80,于是我们单单是(2^{80}),就狠狠地爆掉了long long,所以这道题很烦人的要写高精:

    它们分别是:

    (mathbb{A}.)高精加高精

    (mathbb{B}.)高精乘单精

    (mathbb{C}.)高精MAX

    可以提前预处理出(2^m)内的所有2的整数次方,避免重复计算。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    inline int read() {
    	int ans=0;
    	char last=' ',ch=getchar();
    	while(ch>'9'||ch<'0') last=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    	if(last=='-') ans=-ans;
    	return ans; 
    }
    
    int n,m;
    int A[100][100];
    
    struct GJ{
    	int C[50],len;
    	GJ() {
            memset(C,0,sizeof(C));
            len=0;
        }
    	//n->1 max->min
    };
    GJ M[81];
    
    
    GJ Mul(GJ a,int b) { 
    	int lena=a.len;
    	GJ c;
    	for(int i=1;i<=lena;i++)
    		c.C[i]=a.C[i]*b;
    	for(int i=1;i<=lena;i++)
    		if(c.C[i]>=10) {
    			c.C[i+1]+=c.C[i]/10;
    			c.C[i]%=10;
    		}
    	int lenc=lena;
    	while(c.C[lenc+1]>0) {
    		lenc++;
    		c.C[lenc+1]+=c.C[lenc]/10;
    		c.C[lenc]%=10;
    	}
    	c.len=lenc;
    	return c;
    }
    
    GJ Max(GJ a,GJ b) {//gaojing max
    	if(a.len<b.len)
    		return b;
    	if(b.len>a.len)
    		return a;
    	for(int i=a.len;i>=1;i--) {
    		if(a.C[i]>b.C[i]) 
    			return a;
    		if(b.C[i]>a.C[i]) 
    			return b;
    	}
    	return a;
    }
    
    GJ Sum(GJ a,GJ b) {
    	int len = max(a.len,b.len); 
    	GJ c;
    	for(int i=1;i<=len;i++)
    		c.C[i]=a.C[i]+b.C[i];
    	for(int i=1;i<=len;i++) {
    		if(c.C[i]>=10) {
    			c.C[i+1]+=c.C[i]/10;
    			c.C[i]%=10; 
    		}
    	}
    	if(c.C[len+1]>0) len++;
    	c.len=len;
    	return c;
    }
    void ych() {
    	GJ E,AA;
    	E.len=1;
    	E.C[1]=1;
    	int k=1;
    	while(k<=m) {
    		E=Mul(E,2);
    		M[k]=E;
    		k++;
    	}
    	
    }
    GJ dp[82][82][82];
    GJ ans,This;
    void clear() {
    	memset(dp,0,sizeof(dp));
    	memset(This.C,0,sizeof(This.C));
    }
    //2+8+24+4+12+32
    int main() {
    	n=read();
    	m=read();
    	ych();
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++) 
    			A[i][j]=read();
    	
    	for(int line = 1;line <= n; line ++ ) {
    		clear();
    		for(int i=1;i<m;i++) {//i 轮 
    			for(int j=1;j<=m;j++) {//区间[j,j+m-i-1] 
    				if(j+m-i-1>m) break;
    				dp[i][j][j+m-i-1]=Max(Sum(dp[i-1][j-1][j+m-i-1],
    					Mul(M[i],A[line][j-1])),
    					Sum(dp[i-1][j][j+m-i],Mul(M[i],A[line][j+m-i]))); 
    				if(i==m-1) 
    					This=Max(This,Sum(dp[i][j][j],Mul(M[m],A[line][j])));
    			}
    		}
    		ans=Sum(ans,This);
    	}
    	if(ans.len==0) 
    		puts("0");
    	for(int i=ans.len;i>=1;i--)
    		printf("%d",ans.C[i]);
    	return 0;
    }
    

    但是这样还是很难过,你会发现,你在luogu上TLE了两个点。发现dp数组中,第一维i是没有用的,所以我们可以把它删去,这样dp每一行前,清空的时间就会减少(这个减少真的是革命性的1.2s->216ms)

    (mathcal{CODE}:)

    #include<bits/stdc++.h>
    
    using namespace std;
    
    inline int read() {
    	int ans=0;
    	char last=' ',ch=getchar();
    	while(ch>'9'||ch<'0') last=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    	if(last=='-') ans=-ans;
    	return ans; 
    }
    
    int n,m;
    int A[100][100];
    
    struct GJ{
    	int C[50],len;
    	GJ() {
            memset(C,0,sizeof(C));
            len=0;
        }
    	//n->1 max->min
    };
    GJ M[81];
    
    
    GJ Mul(GJ a,int b) { 
    	int lena=a.len;
    	GJ c;
    	for(int i=1;i<=lena;i++) {
    		c.C[i]+=a.C[i]*b;
    		if(c.C[i]>=10) {
    			c.C[i+1]+=c.C[i]/10;
    			c.C[i]%=10;
    		}
    	}
    	int lenc=lena;
    	while(c.C[lenc+1]>0) {
    		lenc++;
    		c.C[lenc+1]+=c.C[lenc]/10;
    		c.C[lenc]%=10;
    	}
    	c.len=lenc;
    	return c;
    }
    
    GJ Max(GJ a,GJ b) {
    	if(a.len<b.len)
    		return b;
    	if(b.len>a.len)
    		return a;
    	for(int i=a.len;i>=1;i--) {
    		if(a.C[i]>b.C[i]) 
    			return a;
    		if(b.C[i]>a.C[i]) 
    			return b;
    	}
    	return a;
    }
    
    GJ Sum(GJ a,GJ b) {
    	int len = max(a.len,b.len); 
    	GJ c;
    	for(int i=1;i<=len;i++) {
    		c.C[i]+=a.C[i]+b.C[i];
    		if(c.C[i]>=10) {
    			c.C[i+1]+=c.C[i]/10;
    			c.C[i]%=10; 
    		}
    	}
    	if(c.C[len+1]>0) len++;
    	c.len=len;
    	return c;
    }
    void ych() {
    	GJ E;
    	E.len=1; E.C[1]=1;
    	int k=1;
    	while(k<=m) {
    		E=Mul(E,2);
    		M[k]=E;
    		k++;
    	}
    	
    }
    GJ dp[82][82];
    GJ ans,This;
    void clear() {
    	memset(dp,0,sizeof(dp));
    	This.len=-1; 
    }
    
    int main() {
    	n=read();
    	m=read();
    	ych();
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++) 
    			A[i][j]=read();
    	
    	for(int line = 1;line <= n; line ++ ) {
    		clear();
    		for(int i=1;i<m;i++) {//i 轮 
    			for(int j=1;j<=m;j++) {//区间[j,j+m-i-1] 
    				if(j+m-i-1>m) break;
    				dp[j][j+m-i-1]=Max(Sum(dp[j-1][j+m-i-1],
    					Mul(M[i],A[line][j-1])),
    					Sum(dp[j][j+m-i],Mul(M[i],A[line][j+m-i]))); 
    				if(i==m-1) 
    					This=Max(This,Sum(dp[j][j],Mul(M[m],A[line][j])));
    			}
    		}
    		ans=Sum(ans,This);
    	}
    	if(ans.len==0) 
    		puts("0");
    	for(int i=ans.len;i>=1;i--)
    		printf("%d",ans.C[i]);
    	return 0;
    }
    

    2.乘积最大:

    题目链接[Luogu P1018 乘积最大]

    (mathcal{SOLUTION}:)

    (dp[i][j])表示前i位,j个乘号的最大乘积是多少

    (dp[i][j]),考虑枚举上一个乘号放在了哪里。假设放在了K,那么此时到i的乘积就为:(dp[K][j-1]*num[K+1][i])(其中(num[K+1][i])表示从第K+1位到第i位的数字),那么从1~i-1枚举,求一个最大乘积,即:

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

    初始状态时:(dp[i][0]=num[1][i])

    于是写个高精就可以了

    (mathcal{CODE}:)

    #include<bits/stdc++.h>
    #define ll long long
    
    using namespace std;
    
    int n,K;
    char N[50];
    struct GJ {
    	int C[50],len;
    	GJ() {
    		memset(C,0,sizeof(C));
    		len=0;
    	}
    	//max->min n->1
    };
    GJ num[41][41];
    
    void pre() {
    	int len=strlen(N+1);
    	for(int i=1;i<=len;i++) {
    		for(int j=i;j<=len;j++) {
    			int cnt=0;
    			for(int k=j;k>=i;k--) {
    				num[i][j].C[++cnt]=N[k]-'0';
    			}
    			while(num[i][j].C[cnt]==0&&cnt!=1) cnt--;
    			num[i][j].len=cnt;
    		}
    	}
    }
    
    GJ Mul(GJ a,GJ b) {
    	int lena=a.len,lenb=b.len;
    	GJ c;
    	for(int i=1;i<=lena;i++) 
    		for(int j=1;j<=lenb;j++) 
    			c.C[i+j-1]+=a.C[i]*b.C[j];
    	for(int i=1;i<lena+lenb-1;i++) 
    		if(c.C[i]>=10) {
    			c.C[i+1]+=c.C[i]/10;
    			c.C[i]%=10;
    		}
    	int len=lena+lenb-1;
    	while(c.C[len]>=10) {
    		c.C[len+1]+=c.C[len]/10;
    		c.C[len]%=10;
    		len++;
    	}
    	c.len=len;
    	return c;
    }
    
    GJ Max(GJ a,GJ b) {
    	if(a.len<b.len)
    		return b;
    	if(b.len>a.len)
    		return a;
    	for(int i=a.len;i>=1;i--) {
    		if(a.C[i]>b.C[i]) 
    			return a;
    		if(b.C[i]>a.C[i]) 
    			return b;
    	}
    	return a;
    }
    
    GJ Sum(GJ a,GJ b) {
    	int len = max(a.len,b.len); 
    	GJ c;
    	for(int i=1;i<=len;i++) {
    		c.C[i]+=a.C[i]+b.C[i];
    		if(c.C[i]>=10) {
    			c.C[i+1]+=c.C[i]/10;
    			c.C[i]%=10; 
    		}
    	}
    	if(c.C[len+1]>0) len++;
    	c.len=len;
    	return c;
    }
    
    GJ dp[41][10];
    
    int main() {
    	scanf("%d%d",&n,&K);
    	scanf("%s",N+1);
    	pre();
    	for(int i=1;i<=n;i++)
    		dp[i][0]=num[1][i];
    	for(int i=1;i<=n;i++) {
    		for(int j=1;j<=K;j++) {
    			for(int k=1;k<i;k++) {
    				dp[i][j]=Max(dp[i][j],Mul(dp[k][j-1],num[k+1][i]));
    			}
    		}
    	}
    	for(int i=dp[n][K].len;i>=1;i--)
    		printf("%d",dp[n][K].C[i]);
    	return 0;
    }
    

    (color{OrangeRed}{2}color{Orange}{0}color{Yellow}{1}color{LimeGreen}{9}color{RoyalBlue}{是}color{Turquoise}{彩}color{Plum}{色}color{Gold}{的})

    (玩梗态)

  • 相关阅读:
    dedecms 权重排序问题
    HTML5学习笔记简明版(1):HTML5介绍与语法
    通过jquery 获取文本框的聚焦和失焦方法
    14种网页图片和文字特效的jQuery插件代码
    input文本框实现宽度自适应代码实例,input文本框
    html5,html5教程
    css的#和.的区别
    css style与class之间的区别,cssclass
    网页点击按钮弹出遮罩层,拖动和关闭效果
    基于CSS+dIV的网页层,点击后隐藏或显示
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/12004756.html
Copyright © 2011-2022 走看看