zoukankan      html  css  js  c++  java
  • dp练习

    P1018 乘积最大

    P1018 乘积最大
    一串数字,加一些乘号使其乘积最大
    很显然是裸的区间dp,然后60分。。。。。因为没有高精

    int a[100];
    int dp[100][100];
    int getnum(int x,int y)
    {
    	int ans=0;
    	for(int i=x;i<=y;i++)
    	{
    		ans=ans*10+a[i];
    	}
    	return ans;
    }
    main(void)
    {
    	int n,k;
    	cin>>n>>k;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%1d",&a[i]);
    	}
    	for(int i=1;i<=n;i++)dp[i][0]=getnum(1,i);
    	for(int i=1;i<=n;i++)
    	for(int j=k;j>=1;j--)
    	for(int p=i-1;p>=1;p--)
    	{
    		dp[i][j]=max(dp[i][j],dp[i-p][j-1]*getnum(i-p+1,i));
    	}
    
    	cout<<dp[n][k];
    }
    
    
    
    

    P1057 传球游戏

    P1057 传球游戏
    两个属性,传球号码,传球次数,找出前后的关系即可

    int n,M;
    int dp[35][35]; 
    main(void)
    {
    	cin>>n>>M;
    	dp[0][0]=1;
    	for(int m=1;m<=M;m++)
    	for(int i=0;i<n;i++)
    	{
    		dp[i][m]=dp[(i-1+n)%n][m-1]+dp[(i+1+n)%n][m-1];
    		//printf("i:%d m:%d=%d
    ",i,m,dp[i][m]);
    	}
    	cout<<dp[0][M];
    }
    
    
    
    
    

    CF414B Mashmokh and ACM

    CF414B Mashmokh and ACM
    如果一个数列中,后一个数都能被前面一个数整除,那么就叫这个数列为好数列。输入n,k,求数列中最大元素为n,数列长度为k的好数列的种数(对1000000007取模)
    找前面的整除比较难找,因此转化为递推后边的乘法

    const int p=1000000007;
    int dp[2005][2005];
    main(void)
    {
    	int n,k;
    	cin>>n>>k;
    	for(int i=1;i<=n;i++)dp[i][1]=1;
    	for(int i=1;i<=n;i++)//最后一个数
    	for(int j=1;j<=k;j++)//数列的个数
    	{
    		for(int ch=1;ch<=n/i;ch++)//枚举可以乘的数,后边的++
    		{
    			dp[i*ch][j+1]+=dp[i][j]%p;
    			dp[i*ch][j+1]%=p;
    		}
    	}
    	int ans=0;
    	for(int i=1;i<=n;i++)//把以所有i结尾的长度为k的加起来
    	{
    		ans+=dp[i][k]%p;
    	}
    	cout<<ans%p;
    }
    
    
    
    
    

    P1077 摆花(***)

    P1077 摆花
    题目描述
    小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第ii种花不能超过a[i]盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
    试编程计算,一共有多少种不同的摆花方案。

    隐藏的背包问题,需要自己找到价值和容量。
    定义物品的种类和数列为i,a[i],每一个的价值为1,容量为m,求的的最大价值的方案数就是容量为m的方案数

    
    int n,m;
    const int p=1000007;
    int a[200];
    int aa[100000];
    int cnt=0;
    int dp[200][200];
    main(void)
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		dp[i][0]=1;
    	}
    	dp[0][0]=1;
    	//前i盆,一共摆了j盆 
    	for(int i=1;i<=n;i++)
    	for(int j=0;j<=m;j++)
    	{
    		for(int k=0;k<=min(j,a[i]);k++)
    		dp[i][j]+=dp[i-1][j-k]%p;
    		dp[i][j]%=p;
    //		printf("i:%d-j:%d-dp:%d
    ",i,j,dp[i][j]);
    	}
    	cout<<dp[n][m]%p;
    }
    
    
    
    
    

    P1586 四方定理(***)

    P1586 四方定理

    在这里插入图片描述
    与上一题类似,也可以转化为背包问题,但是多重背包,可定义每个平方数的价值为i,两个容量4,和要求的n。这样通过定义价值,把价值和满容量联系起来,可以方便的求得满容量(即最大价值)的方案数,就是要求的结果。

    int a[200];
    int dp[32769][5];
    int f[32769];
    main(void)
    {
    	int t;
    	cin>>t;
    	int n=0;
    	for(int i=1;i<=t;i++)
    	{
    		cin>>a[i];
    		n=max(a[i],n);
    	}
    	dp[0][0]=1;
    	for(int i=1;i<=181;i++)
    	for(int j=i*i;j<=n;j++)
    	for(int k=1;k<=4;k++)
    	{
    		dp[j][k]+=dp[j-i*i][k-1];
    	}		 	 
    	for(int i=1;i<=t;i++)
    	{
    		int ans=0;
    		for(int j=1;j<=4;j++)
    		{
    			ans+=dp[a[i]][j];
    		}
    		printf("%d
    ",ans);
    	}
    }
    
    
    
    
    

    矩阵取数

    P1005 矩阵取数游戏
    在这里插入图片描述
    思路:每一行都是独立的,可以分别处理。
    dp[i][j]为剩下[i,j]段时的价值,然后处理每一个剩下一个数的价值

    int dp[100][100];
    int a[100][100];
    main(void)
    {
    	int n,m;
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++)
    	{
    		cin>>a[i][j];
    	}
    	int anss=0;
    	for(int k=1;k<=n;k++)
    	{
    		memset(dp,0,sizeof(dp));
    	
    		for(int i=1;i<=m;i++)
    		for(int j=m;j>=i;j--)
    		{
    			if(i==1&&j==m)continue;
    			if(i-1==0)dp[i][j]=dp[i][j+1]+a[k][j+1]*(1<<(m-j+i-1));
    			else if(j+1==m+1)dp[i][j]=dp[i-1][j]+a[k][i-1]*(1<<(m-j+i-1));
    			else dp[i][j]=max(dp[i-1][j]+a[k][i-1]*(1<<(m-j+i-1)),dp[i][j+1]+a[k][j+1]*(1<<(m-j+i-1)));
    		//	printf("%d %d %d
    ",i,j,dp[i][j]);
    		}
    		int ans=0;
    		for(int i=1;i<=m;i++)
    		{
    			ans=max(dp[i][i]+a[k][i]*(1<<(m)),ans);
    		}
    		anss+=ans;
    	}
    	cout<<anss;
    }
    
    
    
    
    

    删数

    P2426 删数
    在这里插入图片描述
    与上一个题目类似,但是这次不是一个个取得,因此要把dp[i][j]定义为(i,j)段的最大价值
    这两个题目都不是直接求得了答案,而是通过区间的压缩到达一个方便判断的状态。

    
    int n,a[200],dp[200][200];
    main(void)
    {
    	int n;
    	cin>>n;
    	_1for(i,n)cin>>a[i];
    	for(int i=0;i<=n+1;i++)
    	for(int j=n+1;j>=i;j--)
    	{
    		if(i==0&&j==n+1)continue;
    		if(i==j)continue;
    		for(int k=1;k<=i;k++)//k----i
    		{
    			
    			int index=abs(a[k]-a[i])*(i-k+1);
    			if(k==i)index=a[k];
    			dp[i][j]=max(dp[i][j],dp[k-1][j]+index);
    		}
    		for(int k=n;k>=j;k--)//j----k
    		{
    			int index=abs(a[j]-a[k])*(k-j+1);
    			if(k==j)index=a[k];
    			dp[i][j]=max(dp[i][j],dp[i][k+1]+index);
    		}
    		//printf("%d-%d-%d
    ",i,j,dp[i][j]);
    	}
    	int ans=0;
    	for(int i=0;i<=n;i++)
    	{
    		ans=max(dp[i][i+1],ans);
    	}
    	cout<<ans;
    }
    
    
    
    
    

    加分二叉树

    P1040 加分二叉树
    在这里插入图片描述
    按照dp的思路,这是枚举中点,是一个裸的区间dp。按照dfs的思路,每次枚举中点分治

    dfs

    
    int root[500][500];
    int mid[1000];
    int m[500][500];
    int dfs(int l,int r)
    {
    	if(m[l][r])return m[l][r];
    	int ans=0;
    	if(l>r)return 1;
    	if(l==r)
    	{
    		root[l][r]=l;
    		m[l][r]=mid[l];
    		return mid[l];
    	}
    	for(int i=l;i<=r;i++)
    	{
    		int t=mid[i]+dfs(l,i-1)*dfs(i+1,r);
    		if(t>ans)
    		{
    			root[l][r]=i;	
    			ans=t;
    			m[l][r]=ans;
    		}
    	}
    	return ans;
    }
    void print(int l,int r)
    {
    	if(l>r)
    	return ;
    	printf("%d ",root[l][r]);
    	print(l,root[l][r]-1);
    	print(root[l][r]+1,r); 
    }
    main(void)
    {
    	int n;
    	cin>>n;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>mid[i];
    	}
    	cout<<dfs(1,n)<<endl;
    	print(1,n);
    }
    
    
    
    
    

    dp

    int n,v[500],f[500][500],root[500][500];
    void print(int l,int r)
    {
    	if(l>r)return ;
    	if(l==r)
    	{
    		printf("%d ",l);return ;
    	} 
    	printf("%d ",root[l][r]);
    	print(l,root[l][r]-1);
    	print(root[l][r]+1,r); 
    }
    main(void)
    {
    	cin>>n;
    	_1for(i,n)cin>>v[i];
    	for(int i=1;i<=n;i++)
    	{
    		f[i][i]=v[i];
    		f[i][i-1]=1;
    	}
    	for(int i=n;i>=1;i--)
    	{
    		for(int j=i+1;j<=n;j++)
    		{
    			for(int k=i;k<=j;k++)
    			{
    				if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k])
    				{
    					f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k];
    					root[i][j]=k;
    				}
    			}
    		}
    	}
    	cout<<f[1][n]<<endl;
    	print(1,n);
    }
    

    树形dp

    上一个题感觉像一个假的树形dp,接下来介绍几种常见的树形dp。参考

    1.最大独立子集
    最大独立子集的定义是,对于一个树形结构,所有的孩子和他们的父亲存在排斥,也就是如果选取了某个节点,那么会导致不能选取这个节点的所有孩子节点。

    没有上司的舞会

    
    int dp[6005][2];//dp[i][0]是不要i的最值,dp[i][1]是要i的最值
    vector<int >g[6005];
    int a[6005];
    int m[6005];
    int find(int now,int pre)
    {
    	int fa,son;
    	int len=g[now].size();
    	dp[now][1]=a[now];
    	dp[now][0]=0;
    	for(int i=0;i<len;i++)
    	{
    		if(g[now][i]==pre)continue;//避免重复
    		son=find(g[now][i],now);//得到儿子的dp值,儿子其实是g[now][i],没有return也可以
    		dp[now][1]+=dp[son][0];
    		dp[now][0]+=max(dp[son][0],dp[son][1]);
    	}	
    	return now;
    }
    main(void)
    {
    	int n;
    	cin>>n;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	int x,y;
    	for(int i=1;i<=n-1;i++)
    	{
    		cin>>y>>x;
    		g[x].push_back(y);//双向建边,但是要注意不能往复遍历,因此函数要记录上一个的遍历
    		g[y].push_back(x);
    	}
    	int f=find(1,0);//从1开始遍历
    	printf("%d",max(dp[f][0],dp[f][1]));//因为dp[f][]是最后遍历的(先是儿子),所以输出这个即可
    }
    

    拓扑排序,注意两点,从下属递推到上司,因为最后的上司只有一个。还有就是把握拓扑排序的思想,每次让入度为0的点入队。

    int n,r[6005],x,y,ind[6005];
    int dp[6005][2];
    vector<int>g[6005];
    main(void)
    {
    	cin>>n;
    	_1for(i,n)scanf("%d",&r[i]);
    	for(int i=1;i<=n-1;i++)
    	{
    		scanf("%d%d",&x,&y);
    		g[x].push_back(y);
    		ind[y]++;
    		dp[x][1]=r[x];
    		dp[y][1]=r[y];
    	}
    	int ans=0;
    	queue<int> q;
    	for(int i=1;i<=n;i++)
    	{
    		if(!ind[i])
    		{
    			q.push(i);
    			ans=max(ans,dp[i][1]);
    		}
    	}
    	while(!q.empty())
    	{
    		int index=q.front();
    		q.pop();
    		for(int i=0;i<g[index].size();i++)
    		{
    			int indexto=g[index][i];
    			ind[indexto]--;
    			dp[indexto][1]+=dp[index][0];
    			dp[indexto][0]+=max(dp[index][1],dp[index][0]);	
    			ans=max(ans,max(dp[indexto][1],	dp[indexto][0]));	
    			if(!ind[indexto])q.push(indexto);
    		}
    	}
    	cout<<ans; 
    }
    
    

    P1122 最大子树和

    与上一题类似,但是这次不要儿子,孙子也不能要了。
    这两者都可以用拓扑结构递推来做

    int dp[16001][2];
    int a[16001];
    vector<int >g[16001];
    void ss(int now,int pre)
    {
    	dp[now][1]+=a[now];
    	int len=g[now].size();
    	for(int i=0;i<len;i++)
    	{
    		int son=g[now][i];
    		if(son==pre)continue;
    		ss(son,now);
    		dp[now][1]+=max(dp[son][0],dp[son][1]);
    	}
    }
    main(void)
    {
    	int n,x,y;
    	cin>>n;
    	_1for(i,n)cin>>a[i];
    	_1for(i,n-1)
    	{
    		cin>>x>>y;
    		g[x].push_back(y);
    		g[y].push_back(x);
    	}
    	ss(1,0);
    	int ans=0;
    	for(int i=1;i<=n;i++)
    	{
    		ans=max(dp[i][1],ans);
    	}
    	cout<<ans;
    }
    
    

    拓扑,当n-1条边的最小生成树时,可以把任意节点当做根节点。

    int n,r[16005],x,y,ind[16005];
    int dp[16005];
    vector<int>g[16005];
    main(void)
    {
    	cin>>n;
    	_1for(i,n)scanf("%d",&r[i]);
    	for(int i=1;i<=n-1;i++)
    	{
    		scanf("%d%d",&x,&y);
    		g[x].push_back(y);
    		ind[y]++;
    		dp[x]=r[x];
    		dp[y]=r[y];
    	}
    	int ans=0;
    	queue<int> q;
    	for(int i=1;i<=n;i++)
    	{
    		if(!ind[i])
    		{
    			q.push(i);
    			ans=max(ans,dp[i]);
    		}
    	}
    	while(!q.empty())
    	{
    		int index=q.front();
    		q.pop();
    		for(int i=0;i<g[index].size();i++)
    		{
    			int indexto=g[index][i];
    			ind[indexto]--;
    			dp[indexto]+=max(0,dp[index]);	
    			ans=max(ans,dp[indexto]);	
    			if(!ind[indexto])q.push(indexto);
    		}
    	}
    	cout<<ans; 
    }
    
    

    dp的前缀和优化

    P2513 [HAOI2009]逆序对数列
    题解
    在这里插入图片描述
    我的思路是dp[i][j]插入一个数字以后dp[i+1][j+k]增加,t了,写出表达式可以发现有一个求和的过程,可以将缩短。

    [fleft[ i ight] left[ j ight] =sum_{k=0}^{min left( i-1,j ight)}{fleft[ i-1 ight] left[ j-k ight]} ]

    转化为

    [fleft[ i ight] left[ j ight] =sum_{k=max left( 0,j-i+1 ight)}^j{fleft[ i-1 ight] left[ k ight]} ]

    (sum)变量

    [sum=sum_{k=max left( 0,j-i+1 ight)}^j{fleft[ i-1 ight] left[ k ight]} ]

    const int p=10000;
    int dp[1000][100001];
    main(void)
    {
    	int n,k;
    	cin>>n>>k;
    	dp[1][0]=1;
    	for(int i=1;i<n;i++)
    	for(int j=0;j<=i*(i-1)/2;j++)
    	{
    		for(int k=0;k<=i;k++)
    		dp[i+1][j+k]+=dp[i][j]%p;	
    	}
    	cout<<dp[n][k]%p;
    }
    

    正解

    const int p=10000;
    int f[1001][1001];
    main(void)
    {
    	int n=read();
    	int k=read();
    	f[1][0]=1;//初始条件 
    	for(int i=2;i<=n;i++)
    	{
    		int sum=0;
    		for(int j=0;j<=k;j++)
    		{
    			(sum+=f[i-1][j])%=p;//因为求和到j,所以j从零开始累加到sum 
    			if(j>i-1)
    			{
    				(((sum-=f[i-1][j-i+1-1])%=p)+=p)%=p;//j==i-1是一个临界点,此时sum不必减,后面差值从1增加一个后需要不断地减。 
    			}//当j第一次大于i-1时减的是f[i-1][0]。 
    			f[i][j]=sum;
    		} 
    	}
    	cout<<f[n][k]%10000;
    }
    
    

    diversity

    题目

    题意:给你一棵树,每个点给一个区间,可以选区间里面任何一个数,然后问怎么安排得到最大边两点之间的差值和,求这个最大差值和

    思路:首先可以想到每个点肯定是选择区间端点值,也就是说每个点实际上只有两个值可以选,但是我们安排当前值求出最大边差值不一定最优,这个时候我们可以设立一个数组dp[n][2],代表选当前点的L能得到的最大值和当前选R能得到的最大值,这样最后递归到根节点1就能求出最大值是多少

    
    vector<int >g[100005];
    struct node{
    ll l,r;	
    }a[100005];
    ll dp[100005][2];
    void dfs(int u,int fa)
    {
    	for(int i=0;i<g[u].size();i++)
    	{
    		int v=g[u][i];
    		if(v!=fa)
    		{
    			dfs(v,u);
    			dp[u][0]+=max(abs(a[u].l-a[v].l)+dp[v][0],abs(a[u].l-a[v].r)+dp[v][1]);
    			dp[u][1]+=max(abs(a[u].r-a[v].l)+dp[v][0],abs(a[u].r-a[v].r)+dp[v][1]);
    		}
    	}
    }
    main(void)
    {
    	int t=read();
    	while(t--)
    	{
    		int n=read();
    		for(int i=1;i<=n;i++)
    		g[i].clear();
    		memset(dp,0,sizeof(dp));
    		for(int i=1;i<=n-1;i++)
    		{
    			int x=read();
    			int y=read();
    			g[x].push_back(y);
    			g[y].push_back(x);
    		}
    		for(int i=1;i<=n;i++)
    		{
    			a[i].l=read();
    			a[i].r=read();
    		}
    		dfs(1,1);
    		printf("%lld
    ",max(dp[1][0],dp[1][1]));
    	}
    }
    

    B. Array Walk

    这题的题意大概就是有1-n个点,每个点赋值为a[i],现在可以移动k次,不过只能向左移动z次,并且不能连续向左移动,问你最后走过所有点的和最大是多少
    这题很明显思路是一个dp,但是怎么进行状态转移是个问题,比赛的时候我想的是枚举每一个点,再枚举中间的点作为返回的点,后来发现代码并不好写

    其实我们可以这样考虑,因为题目中说了,不会连续向左移动,也就是说我们每次向左移动一次,下一步就必然要向右移动,然后向左向右移动都可以,所以我们可以枚举当前到达的点,和已经向左组走过的次数即可,然后根据当前位置和向左走的次数可以计算总共移动的次数,然后判断是否需要记录答案

    
    int dp[300010][10];//定义dp[i][q]是当前在索引i位置且往左走了q次的最大价值
    int a[300010];
    main(void)
    {
    	int t=read();
    	while(t--)
    	{
    		int ans=0;
    		int n=read();
    		int k=read();
    		int z=read();
    	   for(int i=0;i<=n;i++) memset(dp[i],0,sizeof(dp[i]));
    		for(int i=1;i<=n;i++)
    			a[i]=read();
    		dp[1][0]=a[1];
    		for(int i=2;i<=n;i++)
    		for(int j=0;j<=z;j++)
    		{
    			dp[i][j]=max(dp[i-1][j]+a[i],dp[i][j]);
    			if(i-1+j*2==k)ans=max(ans,dp[i][j]);
    			//回退
    			dp[i-1][j+1]=max(dp[i-1][j+1],dp[i][j]+a[i-1]); 
    			if(i-1-1+2*(j+1)==k&&j+1<=z)ans=max(ans,dp[i-1][j+1]);
    		}
    		printf("%lld
    ",ans);
    	}
    }
    
    
  • 相关阅读:
    vim的强大,vim设置和插件的使用,脱离windows才是王道
    [VS2013]如何闪开安装VS2013必须要有安装IE10的限制
    常用客户端实现逻辑
    开源控件ViewPagerIndicator学习
    常用设计模式
    主题演讲:未来新趋势电动车
    你最美好的年华
    一度总结
    android线程池ThreadPoolExecutor的理解
    Touch事件or手机卫士面试题整理回答(二)
  • 原文地址:https://www.cnblogs.com/wangqianyv/p/13359448.html
Copyright © 2011-2022 走看看