• [NOIP 2014复习]第三章:动态规划——NOIP历届真题回想


    背包型动态规划

    1、Wikioi 1047 邮票面值设计

    题目描写叙述 Description

    给定一个信封,最多仅仅同意粘贴N张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定全部的邮票数量都足够),怎样设计邮票的面值。能得到最大值MAX。使在1~MAX之间的每个邮资值都能得到。

       

        比如。N=3,K=2,假设面值分别为1分、4分。则在1分~6分之间的每个邮资值都能得到(当然还有8分、9分和12分);假设面值分别为1分、3分,则在1分~7分之间的每个邮资值都能得到。能够验证当N=3。K=2时,7分就是能够得到的连续的邮资最大值。所以MAX=7,面值分别为1分、3分。

    N和K

    每种邮票的面值。连续最大能到的面值数。数据保证答案唯一。

    3 2

    1 3

    MAX=7

    非常好的一道题!实际上这个题是个DFS搜索题,通过DFS搜索下一个能够出现的邮票面值,可是得到邮票面值后须要求出能凑出的最大邮资。假设光用枚举,尽管数据范围不大,可是算上去也是个非常大的常数了,加上DFS搜索次数太多,这样做会超时。比較好的办法就是用全然背包f[v]表示凑出邮资v最少要多少张邮票,每次得到一个邮票面值。就记录下它,用“我为人人”型动规更新f数组,然后从小到大搜索一遍下一种邮票面值,直到全部邮票面值都枚举完,更新最大邮资

    另外须要注意的是搜索下一种邮票面值时。也不能乱枚举,这样会让搜索树中每一个节点的儿子太多。搜索树就会太大。一样会超时,那么枚举就得有上下界。


    下界的求法是,枚举的下一种邮票面值要保证比上一种大,这样邮票面值序列就会是单调递增的,非常明显。下界是之前求出的邮票面值中的最后一种面值x+1,简单证明:


    假设下一种邮票面值应该小于之前的邮票,因为邮票面值序列是单调递增的。那么它不应该在这个时候搜索。而应该早就被搜过了,不会轮到如今这么晚搜。


    而枚举面值的上界是当前全部邮票能凑出的连续最大邮资sum,简单证明:

    之前出现的邮资连续区间是[1,sum],而下一张邮票面值是sum+1,则加上下一张邮票面值后,连续区间为[1,sum]∩[sum+2,sum*2+1],而邮资sum+1取不到,实际上连续最大邮资还是sum没变,说明下一张邮票面值取得太大了
    若下一张邮票面值是sum,则加上下一张邮票面值后,连续区间为[1,2*sum]。答案变大了,邮票面值取得刚好。


    这样一来此题思路就清晰了。以下是代码

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAXV 1000
    #define MAXN 40
    #define INF 1000000
    
    int f[MAXV],stamp[MAXN],nowSol[MAXN],maxValue; //f[v]=凑出面额v至少要多少张邮票。stamp是保存面值的数组,nowSol是当前DFS搜出的邮票面值,max=当前最大总面值
    int n,k;
    
    void dfs(int cnt,int sum) //已经有了cnt种邮票(当前正在尝试第cnt+1种邮票),并且
    {
    	int copy[MAXV]; //拷贝数组f用
    	if(cnt==k) //全部邮票的面值都枚举完了
    	{
    		if(sum>maxValue) //假设当前的最大总面值超过之前的答案。那么这是新的最优解
    		{
    			maxValue=sum;
    			for(int i=1;i<=cnt;i++)
    				stamp[i]=nowSol[i];
    		}
    		return;
    	}
    	for(int i=0;i<MAXV;i++) copy[i]=f[i]; //后面的操作会改动f数组。所以先将f数组拷贝保存起来,回溯后再恢复回去
    	//全然背包求出使用该种邮票后能够获得的面值
    	for(int i=nowSol[cnt]+1;i<=sum;i++) //枚举下一个面额
    	{
    		for(int j=0;j<MAXV-i;j++)
    			if(f[j]+1<f[j+i])
    				f[j+i]=f[j]+1;
    		int nowMaxValue; //当前能凑出的最大邮资
    		for(nowMaxValue=sum+1;f[nowMaxValue]<=n;nowMaxValue++); //寻找能得到的最大面值
    		nowSol[cnt+1]=i; //添加新的邮票
    		dfs(cnt+1,nowMaxValue);
    		for(int j=0;j<MAXV;j++) f[j]=copy[j]; //把f数组拷贝回来
    	}
    }
    
    int main()
    {
    	int i;
    	scanf("%d%d",&n,&k);
    	nowSol[1]=1; //第一张邮票的面值一定为1(废话,不用1的话,一种邮票凑不出连续的邮资)
    	for(i=1;i<=n;i++) f[i]=i; //DP边界1:f[i]=i,i<=n,由于这时邮资小,仅仅用i张1元邮票就能凑起来
    	for(;i<MAXV;i++) f[i]=INF; //DP边界2:f[i]=+∞。由于这时邮资大了,邮票个数有限,光用1元邮票凑不齐。须要后面添加新面值以后,通过全然背包动规更新f数组
    	dfs(1,n);
    	for(i=1;i<=k;i++) printf("%d ",stamp[i]);
    	printf("
    MAX=%d
    ",maxValue);
    	system("pause");
    	return 0;
    }

    2、Wikioi 1155 金明的预算方案

    金明今天非常开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的非常宽敞的房间。

    更让他高兴的是,妈妈昨天对他说:“你的房间须要购买哪些物品,怎么布置。你说了算,仅仅要不超过N元钱即可”。今天一早金明就開始做预算了。他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的样例:

    主件

    附件

    电脑

    打印机,扫描仪

    书柜

    图书

    书桌

    台灯。文具

    工作椅

    假设要买归类为附件的物品。必须先买该附件所属的主件。每一个主件能够有0个、1个或2个附件。附件不再有从属于自己的附件。金明想买的东西非常多,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是10元的整数倍)。他希望在不超过N元(能够等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

    设第j件物品的价格为v[j]。重要度为w[j],共选中了k件物品,编号依次为j1j2,……,jk,则所求的总和为:

    v[j1]*w[j1]+v[j2]*w[j2]++v[jk]*w[jk]。(当中*为乘号)

    请你帮助金明设计一个满足要求的购物单。

    1行。为两个正整数,用一个空格隔开:

    N m

    (当中N<32000)表示总钱数,m<60)为希望购买物品的个数。)

    从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数

    v p q

    (当中v表示该物品的价格(v<10000)。p表示该物品的重要度(1~5),q表示该物品是主件还是附件。假设q=0。表示该物品为主件。假设q>0,表示该物品为附件。q是所属主件的编号)

    仅仅有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000

    1000 5

    800 2 0

    400 5 1

    300 5 1

    400 3 0

    500 2 0

    2200

    这个题WA得太奇妙了,数组开小了。fuck

    详细说这就是个01背包的题,仅仅只是决策时须要注意下,仅仅对主件进行决策,要么不取主件,要么仅仅取主件。要么取主件和1号附件,要么取主件和2号附件。要么主件和1、2号附件都取。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXM 70
    #define MAXN 30100
    
    int w[MAXM][4]; //w[i][0]=i的价格,w[i][1]=i的1号附件价格,w[i][2]=i的2号附件价格
    int v[MAXM][4]; //v[i][0]=i的价值,v[i][1]=i的1号附件价值,v[i][2]=i的2号附件价值
    int f[MAXM][MAXN]; //f[v]=使用掉的钱为v时获得的最大价值
    
    int max(int a,int b)
    {
    	if(a>b) return a;
    	return b;
    }
    
    int main()
    {
    	int n,m,maxAns=-1;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		int W,V,belong;
    		scanf("%d%d%d",&W,&V,&belong);
    		if(!belong) //是附件
    		{
    			w[i][0]=W;
    			v[i][0]=V;
    		}
    		else
    		{
    			if(!w[belong][1])
    			{
    				v[belong][1]=V; //记录下主件有附件1
    				w[belong][1]=W;
    			}
    			else
    			{
    				v[belong][2]=V; //记录下主件有附件1
    				w[belong][2]=W;
    			}
    		}
    	}
    	for(int i=1;i<=m;i++)
    			for(int j=1;j<=n;j++)
    			{
    				if(j>=w[i][0])
    				{
    					f[i][j]=max(f[i-1][j],f[i-1][j-w[i][0]]+v[i][0]*w[i][0]); //仅仅要主件
    					if(j-w[i][0]-w[i][1]>=0) f[i][j]=max(f[i][j],f[i-1][j-w[i][0]-w[i][1]]+v[i][0]*w[i][0]+v[i][1]*w[i][1]); //主件+1号附件
    					if(j-w[i][0]-w[i][2]>=0) f[i][j]=max(f[i][j],f[i-1][j-w[i][0]-w[i][2]]+v[i][0]*w[i][0]+v[i][2]*w[i][2]); //主件+2号附件
    					if(j-w[i][0]-w[i][1]-w[i][2]>=0) f[i][j]=max(f[i][j],f[i-1][j-w[i][0]-w[i][1]-w[i][2]]+v[i][0]*w[i][0]+v[i][1]*w[i][1]+v[i][2]*w[i][2]); //主件+2号附件
    				}
    				else f[i][j]=f[i-1][j];
    			}
    	printf("%d
    ",f[m][n]);
    	system("pause");
    	return 0;
    }
    


    棋盘型动态规划

    1、Wikioi 1043 方格取数

    设有N*N的方格图(N<=10,我们将当中的某些方格中填入正整数,而其它的方格中则放入数字0。

    例如以下图所看到的(见例子):

    某人从图的左上角的A 点出发。能够向下行走,也能够向右走,直到到达右下角的B点。在走过的路上,他能够取走方格中的数(取走后的方格中将变为数字0)。

    此人从A点到B 点共走两次,试找出2条这种路径,使得取得的数之和为最大。

    输入的第一行为一个整数N(表示N*N的方格图),接下来的每行有三个整数。前两个表示位置,第三个数为该位置上所放的数。

    一行单独的0表示输入结束。

        仅仅需输出一个整数。表示2条路径上取得的最大的和。

          8

          2  3  13

          2  6   6

          3  5   7

          4  4  14

          5  2  21

          5  6   4

          6 3  15

          7 2  14

          0 0  0

          67

    和之前的传纸条非常类似,思路基本同样,照搬就可以。这里不细说,但有个细节须要注意:这里两条路径是能够重合的。因为题目中有这个要求。所以动规时不能避开两个同样的点,如图

    若两条路径有交叉(DP中两个点同样)。那么就把反复算入的当前格子分数扣掉即可了。

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAXN 50
    
    long long int f[MAXN][MAXN][MAXN][MAXN];
    long long int n,map[MAXN][MAXN];
    
    long long int max(long long int a,long long int b)
    {
    	if(a>b) return a;
    	return b;
    }
    
    int main()
    {
    	scanf("%lld",&n);
    	int x,y,num;
    	while(1)
    	{
    		scanf("%d%d%d",&x,&y,&num);
    		if(!x&&!y&&!num) break;
    		map[x][y]=num;
    	}
    	for(int i=1;i<=n;i++) //第一条路径过点(i,j)
    		for(int j=1;j<=n;j++)
    			for(int k=1;k<=n;k++)
    				for(int h=1;h<=n;h++)
    				{
    					f[i][j][k][h]=max(f[i][j][k][h],f[i-1][j][k-1][h]);
    					f[i][j][k][h]=max(f[i][j][k][h],f[i-1][j][k][h-1]);
    					f[i][j][k][h]=max(f[i][j][k][h],f[i][j-1][k-1][h]);
    					f[i][j][k][h]=max(f[i][j][k][h],f[i][j-1][k][h-1]);
    					f[i][j][k][h]+=map[i][j]+map[k][h];
    					if(i==k&&j==h) f[i][j][k][h]-=map[i][j];
    				}
    	printf("%lld
    ",map[n][n]+max(f[n-1][n][n][n-1],f[n][n-1][n-1][n]));
    	return 0;
    }


     

    区间型动态规划

    1、Wikioi 1090 加分二叉树

    设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),当中数字1,2,3,…,n为节点编号。每一个节点都有一个分数(均为正整数),记第j个节点的分数为ditree及它的每一个子树都有一个加分,任一棵子树subtree(也包括tree本身)的加分计算方法例如以下:

    subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数

    若某个子树为主,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空

    子树。

    试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出。

    1tree的最高加分

    2tree的前序遍历

     

    如今。请你帮助你的好朋友XZ设计一个程序,求得正确的答案。

    1行:一个整数nn<=30)。为节点个数。

    2行:n个用空格隔开的整数,为每一个节点的分数(分数<=100

    1行:一个整数。为最高加分(结果不会超过4,000,000,000)。

    2行:n个用空格隔开的整数,为该树的前序遍历。

    5

    5 7 1 2 10

    145

    3 1 2 4 5

    nn<=30)

    分数<=100

    乍一看这个题是数据结构题。可是由于一个中序遍历相应多个二叉树。这个题没办法建树,好像做不出来,可是细想NOIP怎么可能会考这么裸的二叉树呢?所以这个题是个区间型动规题,为了写起来方便,最好是用记忆化DFS来写,也不easy错。

    能够用一个二元组(或者说区间左右端点)[L,R]表示当前递归层次的状态,dfs(L,R)=求区间[L,R]相应二叉树子树能获得的最大分数,为了能在线段区间中模拟树结构的递归深搜过程,能够进行下图的操作

    动规的思路也非常清晰,套用区间型动规的通用方程即可,f[L,R]=max{f[L,mid-1]*f[mid+1,R]+value[mid]},这里mid就是要取的子树根结点

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAXN 100
    
    int f[MAXN][MAXN]; //f[i][j]=中序遍历序列区间[1,j]获得分数的最大值
    int visit[MAXN][MAXN];
    int root[MAXN][MAXN];
    
    int max(int a,int b)
    {
    	if(a>b) return a;
    	return b;
    }
    
    int dp(int L,int R) //
    {
    	if(L==R) return f[L][R];
    	if(L>R) return 1;
    	if(visit[L][R]) return f[L][R];
    	visit[L][R]=1;
    	int maxAns=-1;
    	for(int mid=L;mid<=R;mid++)
    		if(maxAns<(dp(L,mid-1)*dp(mid+1,R)+f[mid][mid]))
    		{
    			root[L][R]=mid;
    			int Ltree=dp(L,mid-1);
    			int Rtree=dp(mid+1,R);
    			maxAns=(Ltree*Rtree+f[mid][mid]);
    		}
    	return f[L][R]=maxAns;
    }
    
    void firstPrint(int L,int R)
    {
    	if(L>R) return;
    	printf("%d ",root[L][R]);
    	firstPrint(L,root[L][R]-1);
    	firstPrint(root[L][R]+1,R);
    }
    
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&f[i][i]);
    		root[i][i]=i;
    	}
    	printf("%d
    ",dp(1,n));
    	firstPrint(1,n);
    	printf("
    ");
    	system("pause");
    	return 0;
    }


     

    序列型动态规划

    1、Wikioi 1058 合唱队形

    题目描写叙述 Description

        N位同学站成一排,音乐老师要请当中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

        合唱队形是指这种一种队形:设K位同学从左到右依次编号为1。2…。K。他们的身高分别为T1。T2,…。TK,  则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。

        你的任务是,已知全部N位同学的身高。计算最少须要几位同学出列。能够使得剩下的同学排成合唱队形。

        输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数。用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。

        输出文件chorus.out包括一行,这一行仅仅包括一个整数,就是最少须要几位同学出列。

    8
    186 186 150 200 160 130 197 220

    4

    对于50%的数据,保证有n<=20;
    对于所有的数据,保证有n<=100。

    此题能够用最长上升子序列和最长下降子序列做。只是有些细节须要处理。如图

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXN 300
    
    int high[MAXN]; //high[i]=第i个同学的身高
    int up[MAXN],dn[MAXN];
    
    int max(int a,int b)
    {
    	if(a>b) return a;
    	return b;
    }
    
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&high[i]);
    		up[i]=dn[i]=1;
    	}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=i;j++)
    			if(high[j]<high[i]) up[i]=max(up[i],up[j]+1);
    	for(int i=n;i>=1;i--)
    		for(int j=n;j>=i;j--)
    			if(high[j]<high[i]) dn[i]=max(dn[i],dn[j]+1);
    	int maxPeople=-1; //最多參与人数
    	for(int i=1;i<=n;i++)
    		maxPeople=max(maxPeople,dn[i]+up[i]-1);
    	printf("%d
    ",n-maxPeople);
    	system("pause");
    	return 0;
    }

     

    状态压缩型动态规划
    1、Wikioi 1105 过河

    在河上有一座独木桥。一仅仅青蛙想沿着独木桥从河的一側跳到还有一側。在桥上有一些石子,青蛙非常讨厌踩在这些石子上。

    因为桥的长度和青蛙一次跳过的距离都是正整数,我们能够把独木桥上青蛙可能到达的点看成数轴上的一串整点:01……L(当中L是桥的长度)。

    坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点開始,不停的向终点方向跳跃。

    一次跳跃的距离是ST之间的随意正整数(包含S,T)。当青蛙跳到或跳过坐标为L的点时。就算青蛙已经跳出了独木桥。
    题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少须要踩到的石子数。

    输入第一行有一个正整数L1<=L<=109),表示独木桥的长度。第二行有三个正整数STM,分别表示青蛙一次跳跃的最小距离。最大距离。及桥上石子的个数。当中1<=S<=T<=101<=M<=100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。全部相邻的整数之间用一个空格隔开。

    输出仅仅包含一个整数,表示青蛙过河最少须要踩到的石子数。

    10
    2 35
    2 3567

    2

    数据规模


    对于30%的数据。L<=10000

    对于所有的数据,L<=109

    这个题的DP方程比較好推出来,f[i]=青蛙到达点i至少要踩多少个石头,那么DP方程是

    f[i]=min{f[j]},i点无石头
    f[i]=min{f[j]}+1,i点有石头

    可是题目非常鬼畜,假设纯粹依照距离动规的话,总距离可能达到10^9左右(int的最大范围)。空间时间都吃不消,只是1<=M<=100,石头个数不多,这说明可能有两个石头之间的距离太大,青蛙跳只是去,能够採取压缩状态的方式。缩短总距离。就是说假设两个石头之间距离太大,青蛙跳只是去的话,把两个石头之间的距离改成T,能够想到这种距离青蛙也跳只是去。两者是等价的

    详细能够看我之前的一个代码http://blog.csdn.net/qpswwww/article/details/25742233

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <algorithm>
    
    #define MAXN 10020
    #define INF 0x3f3f3f3f
    
    using namespace std;
    
    int f[MAXN]; //f[i]=青蛙到达点i至少要踩的石子数
    int maxStoneDist=0; //当前最远的石头距离
    int river[MAXN]; //river[i]=1
    int stonePos[MAXN];
    
    int min(int a,int b)
    {
    	if(a<b) return a;
    	return b;
    }
    
    int main()
    {
    	int L,S,T,M;
    	memset(f,INF,sizeof(f));
    	scanf("%d%d%d%d",&L,&S,&T,&M);
    	river[0]=1;
    	f[0]=0;
    	int dist,lastDist=0;
    	for(int i=1;i<=M;i++) scanf("%d",&stonePos[i]);
    	sort(stonePos+1,stonePos+M+1); //防坑,按距离对石头位置升序排序
    	for(int i=1;i<=M;i++)
    	{
    		if(stonePos[i]-stonePos[i-1]>=T) //新的石头太远,青蛙一定跳只是去
    		{
    			maxStoneDist+=T;
    			river[maxStoneDist]=1; //则设新石头距离为T+1
    		}
    		else
    		{
    			maxStoneDist=maxStoneDist+stonePos[i]-stonePos[i-1];
    			river[maxStoneDist]=1; //否则新石头距离不变
    		}
    	}
    	for(int i=1;i<=maxStoneDist+T;i++) //我为人人型动规,跳跃起点为i
    	{
    		int minn=INF;
    		for(int j=i-T;j<=i-S;j++)
    			if(j>=0)
    				minn=min(minn,f[j]);
    		f[i]=minn+river[i];
    	}
    	int ans=INF; //答案。从maxStoneDist到maxStoneDist+T中找 
    	for(int i=maxStoneDist;i<=maxStoneDist+T;i++)
    		ans=min(ans,f[i]);
    	if(S==T) //最大跳跃距离等于最小跳跃距离,这样的情况要单独讨论
    	{
    		ans=0;
    		for(int i=1;i<=M;i++)
    			if(stonePos[i]%T==0)
    				ans++;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }


    其它类型的动态规划
    1、Wikioi 1135 选择客栈

    丽江河边有 n 家非常有特色的客栈,客栈依照其位置顺序从1 到n 编号。每家客栈都依照某一种色调进行装饰(总共k 种,用整数0 ~ k-1 表示)。且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。
    两位游客一起去丽江旅游,他们喜欢同样的色调。又想尝试两个不同的客栈,因此决定分别住在色调同样的两家客栈中。晚上,他们打算选择一家咖啡店喝咖啡,要求咖啡店位于两人住的两家客栈之间(包含他们住的客栈),且咖啡店的最低消费不超过p。


    他们想知道总共同拥有多少种选择住宿的方案,保证晚上能够找到一家最低消费不超过p元的咖啡店小聚。

    共n+1 行。
    第一行三个整数 n,k,p,每两个整数之间用一个空格隔开。分别表示客栈的个数,色调的数目和能接受的最低消费的最高值;
    接下来的 n 行。第i+1 行两个整数,之间用一个空格隔开,分别表示i 号客栈的装饰色调和i 号客栈的咖啡店的最低消费。

    输出仅仅有一行,一个整数,表示可选的住宿方案的总数。

    5 2 3
    0 5
    1 3
    0 2
    1 4
    1 5

    3

    【输入输出例子说明】

    客栈编号
    色调 
    最低消费  5

    2 人要住相同色调的客栈,全部可选的住宿方案包含:住客栈①③,②④,②⑤。④⑤。
    可是若选择住 4、5 号客栈的话。4、5 号客栈之间的咖啡店的最低消费是 4,而两人能承受
    的最低消费是 3 元,所以不满足要求。因此仅仅有前 3 种方案可选。

    【数据范围】
    对于 30%的数据,有n≤100;
    对于 50%的数据。有n≤1,000;
    对于 100%的数据,有2≤n≤200,000,0<k≤50,0≤p≤100, 0≤最低消费≤100。


    之前我曾写过一篇该题的解题报告,鉴于没有图加上数组变量太多。不便于理解,我又一次写一篇题解
    对于此题,能够用cheapMaxNum[i]表示1~i中编号最大的廉价客栈
     colorMaxNum[i]表示1~i-1中与i颜色同样的编号最大的客栈
     sameColorNum[i]表示1~i-1中和i颜色同样的客栈个数

    ans[i]表示1~i-1中与i颜色同样,且其到i之间有廉价客栈的个数

    colorNum[i]表示之前全部客栈中色调为i的个数

    maxNum[i]表示之前全部客栈中色调为i的最大客栈编号

    DP方程的推导能够參考下图
    所以第一个DP方程是:
    ans[i]=ans[colorMaxNum[i]],cheapMaxNum[i]<=colorMaxNum[i]
    ans[i]=sameColorNum[i],cheapMaxNum[i]>colorMaxNum[i]
    可是光这个方程还不够,cheapMaxNum数组也是须要推出来的。假设第i个客栈廉价。cheapMaxNum[i]=i,否则cheapMaxNum[i]=cheapMaxNum[i-1],这就是第二个方程
    cheapMaxNum[i]=i,price[i]<=p
    cheapMaxNum[i]=cheapMaxNum[i-1],price[i]>p
     
    至于DP过程中的其它參数,比較好递推出来,代码里看得比較清楚就不细说了
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAXN 200100
    
    int cheapMaxNum[MAXN],colorMaxNum[MAXN],sameColorNum[MAXN],ans[MAXN],colorNum[MAXN],maxNum[MAXN];
    
    /*
    	声明:廉价的客栈就是价格低于最高消费的客栈
    	cheapMaxNum[i]=1~i中编号最大的廉价客栈
    	colorMaxNum[i]=1~i-1中与i颜色同样的编号最大的客栈
    	sameColorNum[i]=1~i-1中和i颜色同样的客栈个数
    	ans[i]=1~i-1中与i颜色同样,且其到i之间有廉价客栈的个数
    	DP方程为:
    	ans[i]=ans[colorMaxNum[i]],cheapMaxNum[i]<=colorMaxNum[i]
    	ans[i]=sameColorNum[i],cheapMaxNum[i]>colorMaxNum[i]
    	colorNum[i]=之前全部客栈中色调为i的个数,maxNum[i]=之前全部客栈中色调为i的最大客栈编号
    */
    
    int main()
    {
    	int n,k,p,color,price;
    	scanf("%d%d%d",&n,&k,&p);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d%d",&color,&price);
    		colorMaxNum[i]=maxNum[color]; //前i-1个客栈中与i同样颜色的客栈最大编号就是之前color色调的最大客栈编号
    		sameColorNum[i]=colorNum[color]; //同理
    		if(price<=p) //i号客栈是廉价客栈
    			cheapMaxNum[i]=i; //前i个客栈中廉价客栈的最大编号就是i自己
    		else cheapMaxNum[i]=cheapMaxNum[i-1]; //否则前i个客栈中廉价客栈最大编号和前i-1个的同样
    		if(cheapMaxNum[i]<colorMaxNum[i])
    			ans[i]=ans[colorMaxNum[i]];
    		else
    			ans[i]=sameColorNum[i];
    		colorNum[color]++;
    		maxNum[color]=i;
    	}
    	int Ans=0;
    	for(int i=2;i<=n;i++) //找客栈尾巴,统计方案总个数
    			Ans+=ans[i];
    	printf("%d
    ",Ans);
    	system("pause");
    	return 0;
    }

    2、2k进制数


     题目描写叙述 Description

    r是个2k进制数。并满足下面条件:

    1r至少是个2位的2k进制数。

    2)作为2k进制数,除最后一位外,r的每一位严格小于它右边相邻的那一位。

    3)将r转换为2进制数q后。则q的总位数不超过w

    在这里。正整数k1k9)和wk<W< span>30000)是事先给定的。

    问:满足上述条件的不同的r共同拥有多少个?

    我们再从还有一角度作些解释:设S是长度为w01字符串(即字符串Sw个“0或“1组成),S相应于上述条件(3)中的q。将S从右起划分为若干个长度为k的段,每段相应一位2k进制的数,假设S至少可分成2段。则S所相应的二进制数又能够转换为上述的2k进制数r

    例:设k=3w=7。则r是个八进制数(23=8)。

    因为w=7。长度为701字符串按3位一段分,可分为3段(即133,左边第一段仅仅有一个二进制位),则满足条件的八进制数有:

    2位数:高位为16个(即121314151617)。高位为25个。…,高位为61个(即67)。

    6+5++1=21个。

    3位数:高位仅仅能是1,第2位为25个(即123124125126127)。第2位为34个,…,第2位为61个(即167)。共5+4++1=15个。

    所以,满足要求的r共同拥有36个。

    仅仅有1行,为两个正整数,用一个空格隔开:

    k W

    共1行。是一个正整数,为所求的计算结果,即满足条件的不同的r的个数(用十进制数表示),要求最高位不得为0,各数字之间不得插入数字以外的其它字符(比如空格、换行符、逗号等)。

    (提示:作为结果的正整数可能非常大。但不会超过200位)

    3 7

    36

     个人觉得是非常经典的一道题,刚開始我感觉做不出来,只是看神犇们的题解搞懂了,实际上2k进制数转换为二进制后。每k位相应于2k进制中的一位。题目就好做了
     
     
    而数字的位数能够选。所以假设最高位不填的话,方案数有ΣC(2^k-1,i),i=数字的位数。C(m,n)=从m个数里选n个数的方案总数
     
    假设最高位填的话,方案数有ΣC(2^k-i-1,w/k),i为最高位取的数。

    组合数学中C(m,n)=C(m-1,n)+C(m-1,n-1)。题目变成了一个递推(说动规应该也行)问题
    然后题目提示了终于结果的大小非常大,所以须要高精度加法。这个不难。
    #include <iostream>
    #include <string.h>
    #include <stdio.h>
    
    #define BASE 10000
    #define MAXN 512
    
    using namespace std;
    
    struct Hugeint
    {
        int c[100],len,sign;
        Hugeint(){memset(c,0,sizeof(c));len=1;sign=0;}
        void zero()
        {
            while(len>1&&c[len]==0) len--; //清除前面的0
            if(len==1&&c[len]==0) sign=0;
        }
        void writein(char *s) //将数字串s压4位存入高精数组中
        {
            int k=1,L=strlen(s); //L=s的长度
            for(int i=L-1;i>=0;i--)
            {
                c[len]+=(s[i]-'0')*k;
                k*=10;
                if(k==BASE) //这一位装不下了
                {
                    k=1;
                    len++; //换到下一位
                }
            }
        }
        void read() //读入高精度数
        {
            char s[300]={0};
            scanf("%s",s);
            writein(s);
        }
        void print()
        {
            if(sign) printf("-"); //负数
            printf("%d",c[len]);
            for(int i=len-1;i>=1;i--) printf("%04d",c[i]);
            printf("
    ");
        }
        Hugeint operator = (int a) //给高精度赋值
        {
            char s[300]={0};
            sprintf(s,"%d",a);
            writein(s);
            return *this; //this指针仅仅能用于成员函数中。表示当前对象的地址
        }
        Hugeint operator+(const Hugeint &b)
        {
            Hugeint r;
            r.len=max(len,b.len)+1;
            for(int i=1;i<=r.len;i++)
            {
                r.c[i]+=c[i]+b.c[i];
                r.c[i+1]+=r.c[i]/BASE; //算上进位
                r.c[i]%=BASE;
            }
            r.zero(); //清掉前面的零
            return r;
        }
        Hugeint operator+(const int &a) //高精加低精
        {
            Hugeint b;
            b=a;
            return *this+b;
        }
    }C[MAXN][MAXN],ans;
    
    int main()
    {
        int k,w,maxNum,firstNum;
        cin>>k>>w;
        maxNum=1<<k; //maxNum=2^k,每一位能够选择的数字为1~maxNum-1
        firstNum=1<<(w%k); //首段最大可取数字为firstNum-1
        C[0][0]=1;
        for(int i=1;i<maxNum;i++)
            for(int j=0;j<=i;j++) //在i个数里选j个数的方案个数
            {
                if(j==0) C[i][j]=1;
                else C[i][j]=C[i-1][j]+C[i-1][j-1];
            }
        for(int i=2;i<=w/k&&i<maxNum;i++) //i=2k进制数的位数
            ans=ans+C[maxNum-1][i];
        for(int i=1;i<firstNum&&w/k+i<maxNum;i++) //首位数字取i
            ans=ans+C[maxNum-i-1][w/k];
        ans.print();
        return 0;
    }
    



     

    
    
  • 相关阅读:
    ride.py 启动报错
    python中的__call__函数
    python-namedtuple命名元组
    python模块-操作excel(xlsxwriter)
    (转)python中@property详解
    python中使用logging将日志写入文件或输出到控制台
    “selenium.common.exceptions.SessionNotCreatedException: Message: Unable to find a matching set of capabilities“解决办法
    Fiddler对https抓包时,提示"HTTPS decryption is disabled."
    fiddler的使用
    常用软件激活/破解
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/5362526.html
Copyright © 2020-2023  润新知