DP专题测试题解报告20190504

写在前面

都是些练动规的好题啊

T1 麦香牛块Beef McNuggets

题目传送门:luogu2737 Beef McNuggets

题目

麦香牛块(nuggets.cpp/in/out)

题目描述
农夫布朗的奶牛们正在进行斗争,因为它们听说麦当劳正在考虑引进一种新产品:麦香牛块。奶牛们 正在想尽一切办法让这种可怕的设想泡汤。奶牛们进行斗争的策略之一是“劣质的包装”。“看,”, 奶牛们说,“如果你用只有一次能装 3 块、6 块或 10 块的三种包装盒装麦香牛块,你就不可能满足 想要一次只想买 1、2、4、5、7、8、11、14 或 17 块麦香牛块的顾客了。劣质的包装意味着劣质的 产品。”

你的任务是帮助这些奶牛。给出包装盒的种类数 N(1<=N<=10)和 N 个代表不同种类包装盒容纳麦香 牛块个数的正整数(1<=i<=256),输出顾客不能用上述包装盒(每种盒子数量无限)买到麦香牛块的 最大块数。如果在限定范围内所有购买方案都能得到满足,则输出 0。 范围限制是所有不超过 2,000,000,000 的正整数。

输入格式:
第 1 行: 包装盒的种类数 N
第 2 行到 N+1 行: 每个种类包装盒容纳麦香牛块的个数

输出格式:
输出文件只有一行数字:顾客不能用包装盒买到麦香牛块的最大块数或 0(如果在限定范围内所有 购买方案都能得到满足)。

样例输入(nuggets.in)
3 3 6 10
样例输出:(nuggets.out)
17

分析

题目有误,应该是:

如果所有购买方案都能得到满足或者不存在不能买到块数的上限,则输出0。

还记得小凯的疑惑吗?这道题的证明也差不多了,推荐这篇博客的讲解

如果找不到两个互质的数,那么就没有最大不可表数;
如果有两个互质的数,那么这大于两个数的积的数就一定可以表示出来
这题也就变成了一个完全背包

所以代码:

代码

/**********************
User:Mandy.H.Y
Language:c++
Problem:T1 nuggets
Algorithm:
**********************/
//小凯的疑惑…… 
#include<bits/stdc++.h>

using namespace std;

const int maxn=15;

int n;
int a[maxn];
int dp[1000007];

template<class T>inline void read(T &x){
	x=0;bool flag=0;char ch=getchar();
	while(!isdigit(ch)) flag|=ch=='-',ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if(flag)x=-x;
}

template<class T>void putch(const T x){
	if(x>9) putch(x/10);
	putchar(x%10|48);
}

template<class T>void put(const T x){
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu(){
	freopen("nuggets.in","r",stdin);
	freopen("nuggets.out","w",stdout);
}

void readdata(){
	read(n);
	for(int i=1;i<=n;++i) read(a[i]);
	sort(a+1,a+n+1);
}

int gcd(int x,int y){
	return x%y?gcd(y,x%y):y;
}

void work(){
	if(a[1]==1) {put(0);return;}
	if(n==1){
		int y=2e9;int x=y%a[1];
		if(x) put(y);
		else put(y-1);
		return;
	}
	int MAXN=0;
	for(int i=1;i<=n;++i){//找互质的数
		for(int j=i+1;j<=n;++j)
			if(gcd(a[i],a[j])==1){MAXN=a[i]*a[j]-a[i]-a[j];break;}
		if(MAXN) break;
	}
	int ans=0;
	if(!MAXN) {put(0);return;}//luogu上的题目说没有上限也输出零 
	dp[0]=1;
	for(int i=1;i<=n;++i)//找出最大不可表数,完全背包
		for(int j=a[i];j<=MAXN;++j)
			dp[j]|=dp[j-a[i]];
	for(int i=MAXN;i>=1;--i)
		if(!dp[i]){ans=i;break;}
	put(ans);
}

int main(){
//	docu();
	readdata();
	work();
	return 0;
}

T2 奶牛家谱 Cow Pedigrees

题目传送门:luogu1472 Cow Pedigrees

题目

奶牛家谱
(nocows.cpp/in/out)

题目描述
农民约翰准备购买一群新奶牛。 在这个新的奶牛群中, 每一个母亲奶牛都生两个小奶牛。这些奶牛间的关系可以用二叉树来表示。这些二叉树总共有N个节点(3 <= N < 200)。这些二叉树有如下性质:
每一个节点的度是0或2。度是这个节点的孩子的数目。
树的高度等于K(1 < K < 100)。高度是从根到最远的那个叶子所需要经过的结点数; 叶子是指没有孩子的节点。
有多少不同的家谱结构? 如果一个家谱的树结构不同于另一个的, 那么这两个家谱就是不同的。输出可能的家谱树的个数除以9901的余数。

输入输出格式

输入格式:
两个空格分开的整数, N和K。
输出格式:
一个整数,表示可能的家谱树的个数除以9901的余数。

输入输出样例

输入样例:
5 3
输出样例:
2

在这里插入图片描述

分析

要求得出节点数为 N,高度为 K 的“家谱树”数目。

如果设这个数目为 f [ n ] [ k ] 的话,那么实在是难以利用子问题得出它的数值。

事实上这种设计状态的方法难以想出方程的原因可能就是因为“高度为 k”的条件太严格。

如果我们将 f [ n ] [ k ] 定义为“节点数为 n,高度不大于 k”的家谱树数目,那么我们可以得到如下方程:

f [ n ] [ k ] = sum { f [ l ] [ k - 1 ] * f [ n - 1 - l ] [ k - 1 ] }

最后的答案就是 f [ N ] [ K ] – f [ N ] [ K - 1 ] 。 不妨认为这是一种 “ 差分 ” 的思想。

代码

/**********************
User:Mandy.H.Y
Language:c++
Problem:T2 nocows
Algorithm:
**********************/

#include<bits/stdc++.h>

using namespace std;

const int maxn=205;
const int mod=9901;

int n,k;
int dp[maxn][maxn];
//dp[i][j]表示用i头牛建一棵高度不超过j的树的方案数
template<class T>inline void read(T &x){
	x=0;bool flag=0;char ch=getchar();
	while(!isdigit(ch)) flag|=ch=='-',ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if(flag)x=-x;
}

template<class T>void putch(const T x){
	if(x>9) putch(x/10);
	putchar(x%10|48);
}

template<class T>void put(const T x){
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu(){
	freopen("nocows.in","r",stdin);
	freopen("nocows.out","w",stdout);
}

void readdata(){
	read(n);read(k);
}

void work(){
	for(int i=1;i<=k;++i){
		dp[1][i]=1;
		for(int j=3;j<=n;j+=2){//儿子只能有0或2个,则节点数一定是奇数
			for(int l=1;l<=j-2;l+=2){
				dp[j][i]+=dp[l][i-1]*dp[j-1-l][i-1];
				dp[j][i]%=mod;
			}
		}
	}
	put((dp[n][k]-dp[n][k-1]+mod)%mod);
}

int main(){
//	docu();
	readdata();
	work();
	return 0;
}

T3 加拿大旅游Canada Tour

题目传送门:luogu2747 Canada Tour

题目

加拿大旅游
(tour.cpp/in/out)

题目描述
你赢得了一场航空公司举办的比赛,奖品是一张加拿大环游机票。旅行在这家航空公司开放的最西边的城市开始,然后一直自西向东旅行,直到你到达最东边的城市,再由东向西返回,直到你回到开始的城市。除了旅行开始的城市之外,每个城市只能访问一次,因为开始的城市必定要被访问两次(在旅行的开始和结束)。
当然不允许使用其他公司的航线或者用其他的交通工具。
给出这个航空公司开放的城市的列表,和两两城市之间的直达航线列表。找出能够访问尽可能多的城市的路线,这条路线必须满足上述条件,也就是从列表中的第一个城市开始旅行,访问到列表中最后一个城市之后再返回第一个城市。

输入输出格式

输入格式:
第 1 行: 航空公司开放的城市数 N 和将要列出的直达航线的数量 V。N 是一个不大于 100 的正整数。V 是任意的正整数。
第 2…N+1 行: 每行包括一个航空公司开放的城市名称。城市名称按照自西向东排列。不会出现两个城市在同一条经线上的情况。每个城市的名称都 是一个字符串,最多15字节,由拉丁字母表上的字母组成;城市名称中没有空格。
第 N+2…N+2+V-1 行: 每行包括两个城市名称(由上面列表中的城市名称组成),用一个空格分开。这样就表示两个城市之间的直达双程航线。
输出格式:
Line 1: 按照最佳路线访问的不同城市的数量 M。如果无法找到路线,输出 1。

输入输出样例

输入样例:
8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary

输出样例:
7

分析

写这道题时的思考方式是“如果我们知道了某个状态,那么我们可以用它来更新哪些状态的值”。

设 f [ i ] [ j ] 表示由起点到城市 i、j 走两条不相交路径时的最大城市总数。

我们如果知道了 f [ i ] [ j ],那么我们可以用它更新 f [ i ] [ k ] (如果 j、k 间有航线)和 f [ k ] [ j ] (如果 i、k 间有航线)的取值,其中 k>i, j。

最后的答案是 f [ N ] [ N ] – 1(两条路线交汇于最后一个城市)。

这是不同于“自顶向下”和“逐步递推”的第三种 DP 程序的实现方法,而且有时候更易于编程和思考。

——引自 《USACO心得

代码

/**********************
User:Mandy.H.Y
Language:c++
Problem:T3 tour
Algorithm:DP Floyed 
**********************/
//可以看成两个人分别从起点开始走
//a[第一个人的]位置[第二个人的位置]=最多城市数
#include<bits/stdc++.h>

using namespace std;

const int maxn=105;

int n,v;
bool g[maxn][maxn];
int a[maxn][maxn];
char s[maxn][20];

template<class T>inline void read(T &x){
	x=0;bool flag=0;char ch=getchar();
	while(!isdigit(ch)) flag|=ch=='-',ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if(flag)x=-x;
}

template<class T>void putch(const T x){
	if(x>9) putch(x/10);
	putchar(x%10|48);
}

template<class T>void put(const T x){
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu(){
	freopen("tour.in","r",stdin);
	freopen("tour.out","w",stdout);
}

int getid(char s1[]){
	int len1=strlen(s1);
	for(int i=1;i<=n;++i){
		int flag=0;int len2=strlen(s[i]);
		if(len1!=len2) continue;
		for(int j=0;j<len1;++j) if(s[i][j]!=s1[j]){flag=1;break;}
		if(!flag) return i;
	}
}

void readdata(){
	read(n);read(v);
	for(int i=1;i<=n;++i) scanf("%s",s[i]);
	for(int i=1;i<=v;++i){
		char x[20],y[20];
		scanf("%s",x);scanf("%s",y);
		int u1=getid(x);int v1=getid(y);
		g[u1][v1]=g[v1][u1]=1;		
	}
}

void work(){
	a[1][1]=1;
	for(int i=1;i<n;++i){
		for(int j=i+1;j<=n;++j)
			for(int k=1;k<j;++k){
				if(g[k][j] && a[i][k])//a[i][k]>0 即存在这种方案 且k,j联通
				//因为a[i][k]无重复,而又是直接从 k到j,故保证了a[i][j]无重复 
				a[i][j]=max(a[i][j],a[i][k]+1);
				a[j][i]=a[i][j];
			}
	}
	int ans=1;//初值1 
	for(int i=1;i<n;++i) if(g[i][n]) ans=max(ans,a[i][n]);
	put(ans);
}

int main(){
//	docu();
	readdata();
	work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值