写在前面
都是些练动规的好题啊
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;
}