学校作业-Usaco DP水题

好吧,因为USACO挂掉了,所以我写的所有代码都不保证正确性【好的,这么简单的题,再不写对,你就可以滚粗了!

第一题是USACO 2.2.2

★Subset Sums 集合 
对于从 1 到 N 的连续整集合合,能划分成两个子集合,且保证每个集合的数字和是相等的. 
举个例子,如果 N=3,对于{1,2,3}能划分成两个子集合,他们每个的所有数字和是相等的: 
{3} and {1,2}  26
这是唯一一种分发(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数) 
如果 N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分发的子集合各数字和是相等的: 
{1,6,7} and {2,3,4,5} {注 1+6+7=2+3+4+5} 
{2,5,7} and {1,3,4,6} 
{3,4,7} and {1,2,5,6} 
{1,2,4,7} and {3,5,6} 
给出 N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出 0.程序不能预存结果
直接输出. 
PROGRAM NAME: subset 
INPUT FORMAT
输入文件只有一行,且只有一个整数 N 
SAMPLE INPUT (file subset.in) 

OUTPUT FORMAT
输出划分方案总数,如果不存在则输出 0. 
SAMPLE OUTPUT (file subset.out) 

——分割线——

这道题其实就是一个背包,就是求用1~n组合成(n+1)*n/4的方案数。我们记f[x]表示组成和为x我们的方案数,我们尝试每个数k,从(n+1)*n/4开始到k即为p寻找一旦p-k这个值可以通过1~k-1这些数组成,即f[p-k]!=0。那么,我们就使f[p]+=f[p-k],即每个可以组成p-k的方案在加上一个k就可以组成p的方案。

/*Author:WNJXYK*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;

#define LL long long

inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;}
inline void swap(LL &x,LL &y){LL tmp=x;x=y;y=tmp;}
inline int remin(int a,int b){if (a<b) return a;return b;}
inline int remax(int a,int b){if (a>b) return a;return b;}
inline LL remin(LL a,LL b){if (a<b) return a;return b;}
inline LL remax(LL a,LL b){if (a>b) return a;return b;}

const int Maxn=2000;
int n;
int Sum=0;
int f[Maxn+10];

int main(){
	scanf("%d",&n);
	Sum=(n+1)*n/2/2;
	f[0]=1;
	for (int i=n;i>=1;i--){
		for (int j=Sum;j>=i;j--){
			f[j]=f[j]+f[j-i];
		}
	}
	printf("%d
",f[Sum]/2);
	return 0;
}

第二题是USACO 2.3.1

★Longest Prefix 最长前缀 
在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的.生物学家对于把长的序列 28
分解成较短的(称之为元素的)序列很感兴趣. 
如果一个集合 P 中的元素可以通过串联(允许重复;串联,相当于 Pascal 中的 “+” 运算符)
组成一个序列 S ,那么我们认为序列 S 可以分解为 P 中的元素.并不是所有的元素都必须出现.
举个例子,序列 ABABACABAAB 可以分解为下面集合中的元素: 
 {A, AB, BA, CA, BBC} 
序列 S 的前面 K 个字符称作 S 中长度为 K 的前缀.设计一个程序,输入一个元素集合以及一个
大写字母序列,计算这个序列最长的前缀的长度. 
PROGRAM NAME: prefix 
INPUT FORMAT
输入数据的开头包括 1..200 个元素(长度为 1..10 )组成的集合,用连续的以空格分开的字符串
表示.字母全部是大写,数据可能不止一行.元素集合结束的标志是一个只包含一个 “.” 的行.集
合中的元素没有重复.接着是大写字母序列 S ,长度为 1..200,000 ,用一行或者多行的字符串来表
示,每行不超过 76 个字符.换行符并不是序列 S 的一部分. 
SAMPLE INPUT (file prefix.in) 
A AB BA CA BBC 
ABABACABAABC 
OUTPUT FORMAT
只有一行,输出一个整数,表示 S 能够分解成 P 中元素的最长前缀的长度. 
SAMPLE OUTPUT (file prefix.out) 
11 

——分割线——

这道题我也没仔细想,脑不了个方法写出啦就好了、、就是即f[k]表示母串到第k位是否能被集合串组成,这样一旦母串从第1~p位的子串的后缀等于一集合串s,且p-s.length()的可以被组成的话,那么母串到第p为也可以被组成,即f[p]=true。

/*Author:WNJXYK*/
#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;

#define LL long long

inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;}
inline void swap(LL &x,LL &y){LL tmp=x;x=y;y=tmp;}
inline int remin(int a,int b){if (a<b) return a;return b;}
inline int remax(int a,int b){if (a>b) return a;return b;}
inline LL remin(LL a,LL b){if (a<b) return a;return b;}
inline LL remax(LL a,LL b){if (a>b) return a;return b;}

const int Maxn=200;
string lset[Maxn+10];
int ls=0;
string str;
int lr;
bool f[2000001];

bool cmpstr(int index,int local){
	for (int i=local-lset[index].length(),j=0;j<lset[index].length();i++,j++){
		if (str[i]!=lset[index][j]){
			return false;
		}
	}
	return true;
}

int main(){
	string temp;
	while(cin>>temp,temp!=".") lset[++ls]=temp;
	cin>>str;lr=str.length(); 
	f[0]=true;
	for (int i=1;i<=lr;i++){
		for (int j=1;j<=ls;j++){
			if (lset[j].length()>i) continue;
			if (f[i-lset[j].length()] && cmpstr(j,i)){
				f[i]=true;
				break;
			}
		}
	}
	int ans=lr;
	for (;ans>=1;ans--) if (f[ans])break;
	printf("%d
",ans);
	return 0;
}

第三题是USACO 2.3.4

★Money Systems 货币系统 
母牛们不但创建了他们自己的政府而且选择了建立了自己的货币系统. 
[In their own rebellious way],,他们对货币的数值感到好奇. 
传统地,一个货币系统是由 1,5,10,20 或 25,50, 和 100 的单位面值组成的. 
母牛想知道有多少种不同的方法来用货币系统中的货币来构造一个确定的数值. 
举例来说, 使用一个货币系统 {1,2,5,10,...}产生 18 单位面值的一些可能的方法是:18x1, 9x2, 
8x2+2x1, 3x5+2+1,等等其它. 
写一个程序来计算有多少种方法用给定的货币系统来构造一定数量的面值. 
保证总数将会适合 long long (C/C++) 和 Int64 (Free Pascal). 
PROGRAM NAME: money

INPUT FORMAT
货币系统中货币的种类数目是 V . (1<= V<=25) 
要构造的数量钱是 N . (1<= N<=10,000) 
第 1 行: 二整数, V 和 N 
第 2 ..V+1 行: 可用的货币 V 个整数 (每行一个 每行没有其它的数). 
SAMPLE INPUT (file money.in) 
3 10 
1 2 5 
OUTPUT FORMAT
单独的一行包含那个可能的构造的方案数. 
SAMPLE OUTPUT (file money.out) 
10 

——分割线——

这一题和第一题实际上是一样的,只不过这次每个元素可以重复使用,即无限背包。状态也是f[k]表示组成k的方案数,转移和第一题一样,唯一的区别是这次我们转移要从面值小的到面值大的转移,这样可以保证每个元素可以被多次使用。

代码:

/*Author:WNJXYK*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;

#define LL long long

inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;}
inline void swap(LL &x,LL &y){LL tmp=x;x=y;y=tmp;}
inline int remin(int a,int b){if (a<b) return a;return b;}
inline int remax(int a,int b){if (a>b) return a;return b;}
inline LL remin(LL a,LL b){if (a<b) return a;return b;}
inline LL remax(LL a,LL b){if (a>b) return a;return b;}

const int Maxm=10000;
const int Maxn=25;
int V[Maxn+10];
int N;
int n;
LL f[Maxm+10];

int main(){
	scanf("%d%d",&n,&N);
	for (int i=1;i<=n;i++) scanf("%I64d",&V[i]);
	f[0]=1;
	for (int i=1;i<=n;i++){
		for (int j=V[i];j<=N;j++){
			f[j]+=f[j-V[i]];	
		}
	}
	printf("%I64d",f[N]);
	return 0;
}

第四题是USACO 3.1.2
★Score Inflation 总分 
学生在我们 USACO 的竞赛中的得分越多我们越高兴.我们试着设计我们的竞赛以便人们能尽可能的
多得分,这需要你的帮助.我们可以从几个种类中选取竞赛的题目,这里的一个"种类"是指一个竞赛
题目的集合,解决集合中的题目需要相同多的时间并且能得到相同的分数. 
你的任务是写一个程序来告诉 USACO 的职员,应该从每一个种类中选取多少题目,使得解决题目的
总耗时在竞赛规定的时间里并且总分最大. 
输入包括竞赛的时间,M(1 <= M <= 10,000)(不要担心,你要到了训练营中才会有长时间的比赛)和
N,"种类"的数目 1 <= N <= 10,000. 
后面的每一行将包括两个整数来描述一个"种类": 
第一个整数说明解决这种题目能得的分数(1 <= points <= 10000),第二整数说明解决这种题目所

需的时间(1 <= minutes <= 10000). 
你的程序应该确定我们应该从每个"种类"中选多少道题目使得能在竞赛的时间中得到最大的分数.
来自任意的"种类"的题目数目可能任何非负数(0 或更多).计算可能得到的最大分数. 
PROGRAM NAME: inflate 
INPUT FORMAT
第 1 行: M, N--竞赛的时间和题目"种类"的数目. 
第 2-N+1 行: 两个整数:每个"种类"题目的分数和耗时. 
SAMPLE INPUT (file inflate.in) 
300 4 
100 60 
250 120 
120 100 
35 20 
OUTPUT FORMAT
单独的一行包括那个在给定的限制里可能得到的最大的分数. 
SAMPLE OUTPUT (file inflate.out) 
605 
{从第 2 个"种类"中选两题第 4 个"种类"中选三题} 

——分割线——

好吧,这道题就是一个无限背包,和上题是一样的,只不过这次F[k]表示的是用k的时间能达到的最大收益。F[j]=max(F[j],F[j-T[i]]+V[i]),因为是无限背包所以从小时间到大时间依次转移就好了。P.S.深受学校毒害,我还以为竞赛只能每门弄一次呢!竟然Wa了一次样例QAQ

代码:

/*Author:WNJXYK*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;

#define LL long long

inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;}
inline void swap(LL &x,LL &y){LL tmp=x;x=y;y=tmp;}
inline int remin(int a,int b){if (a<b) return a;return b;}
inline int remax(int a,int b){if (a>b) return a;return b;}
inline LL remin(LL a,LL b){if (a<b) return a;return b;}
inline LL remax(LL a,LL b){if (a>b) return a;return b;}

const int Maxn=10000;
const int Maxm=10000;
int T[Maxn+10];
int V[Maxn+10];
int F[Maxm+10];
int n,m;

int main(){
	scanf("%d%d",&m,&n);
	for (int i=1;i<=n;i++) scanf("%d%d",&V[i],&T[i]);
	for (int i=1;i<=n;i++){
		for (int j=T[i];j<=m;j++){
			F[j]=remax(F[j],F[j-T[i]]+V[i]);
		}
	}
	int Ans=0;
	for (int i=1;i<=m;i++) Ans=remax(Ans,F[i]);
	printf("%d
",Ans);
	return 0;
}

第五题是USACO 3.1.6

★Stamps 邮票 
已知一个 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮
票.计算从 1 到 M 的最大连续可贴出的邮资. 
例如,假设有 1 分和 3 分的邮票;你最多可以贴 5 张邮票.很容易贴出 1 到 5 分的邮资(用 1 
分邮票贴就行了),接下来的邮资也不难: 
6 = 3 + 3 
7 = 3 + 3 + 1 
8 = 3 + 3 + 1 + 1 
9 = 3 + 3 + 3 
10 = 3 + 3 + 3 + 1 
11 = 3 + 3 + 3 + 1 + 1 
12 = 3 + 3 + 3 + 3 
13 = 3 + 3 + 3 + 3 + 1. 
然而,使用 5 枚 1 分或者 3 分的邮票根本不可能贴出 14 分的邮资.因此,对于这两种邮票的集
合和上限 K=5,答案是 M=13. 
PROGRAM NAME: stamps 
INPUT FORMAT
第 1 行: 两个整数,K 和 N.K(1 <= K <= 200)是可用的邮票总数.N(1 <= N <= 50)是邮票面
值的数量. 
第 2 行 .. 文件末: N 个整数,每行 15 个,列出所有的 N 个邮票的面值,面值不超过 10000. 
SAMPLE INPUT (file stamps.in) 
5 2 
1 3 
OUTPUT FORMAT
第 1 行: 一个整数,从 1 分开始连续的可用集合中不多于 K 张邮票贴出的邮资数. 
SAMPLE OUTPUT (file stamps.out) 
13 

——分割线——

好吧,这道题目本质上和上面的题目是一样的,只不过要注意分层的问题,就是邮票使用的数量每次都要控制,这样DP要多出一维来记录使用邮票数f[k][m]表示k张邮票是否能组成m面值。f[k][m]=所有f[k-1][m-V[i]]的或值【i属于n】滚动数组可优化!

代码:

/*Author:WNJXYK*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;

#define LL long long

inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;}
inline void swap(LL &x,LL &y){LL tmp=x;x=y;y=tmp;}
inline int remin(int a,int b){if (a<b) return a;return b;}
inline int remax(int a,int b){if (a>b) return a;return b;}
inline LL remin(LL a,LL b){if (a<b) return a;return b;}
inline LL remax(LL a,LL b){if (a>b) return a;return b;}

const int Maxm=500000;
const int Maxn=50;
int v[Maxn+10];
int n,k;
int maxm=0;
bool f[205][Maxm+10];

int main(){
	scanf("%d%d",&k,&n);
	for (int i=1;i<=n;i++) scanf("%d",&v[i]);
	memset(f,false,sizeof(f));
	for (int i=1;i<=n;i++) {f[1][v[i]]=true;maxm=remax(maxm,v[i]);}
	for (int RunTimes=2;RunTimes<=k;RunTimes++){
		for (int i=1;i<=n;i++){
			for (int j=maxm;j>=0;j--){
				if (f[RunTimes-1][j]&&!f[RunTimes][j+v[i]]){
					f[RunTimes][j+v[i]]=true;
					maxm=remax(maxm,j+v[i]);
				}
			}
		}
	}
	int Ans=1;
	for (;;Ans++){
		bool flag=false;
		for (int i=1;i<=k;i++) if (f[i][Ans]){flag=true;break;}
		if (!flag) break;
	}
	printf("%Id
",Ans-1);
	return 0;
}

第五题是USACO 5.1.3

★Musical Themes 乐曲主题 
我们用 N(1 <= N <=5000)个音符的序列来表示一首乐曲,每个音符都是 1..88 范围内的整数,每个
数表示钢琴上的一个键.很不幸这种表示旋律的方法忽略了音符的时值,但这项编程任务是关于音
高的,与时值无关. 
许多作曲家围绕一个重复出现的“主题”来构建乐曲.在我们的乐曲表示法中,“主题”是整个音
符序列的一个子序列,它需要满足如下条件: 
长度至少为 5 个音符 
在乐曲中重复出现(可能经过转调,见下) 
重复出现的同一主题不能重叠 
“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值. 
给定一段乐曲,计算其中最长主题的长度(即音符数). 
本题时限为 1 秒钟! 
PROGRAM NAME: theme 
INPUT FORMAT
输出文件的第一行包含整数 N.下面的每一行(最后一行可能除外)包含 20 个整数,表示音符序列.最
后一行可能少于 20 个音符. 
SAMPLE INPUT (file theme.in) 
30 
25 27 30 34 39 45 52 60 69 79 69 60 52 45 39 34 30 26 22 18 
82 78 74 70 66 67 64 60 65 80 
OUTPUT FORMAT
输出文件应只含一个整数,即最长主题的长度.如果乐曲中没有主题,那么输出 0. 
SAMPLE OUTPUT (file theme.out) 

(这个长度为 5 的主题是输入文件中第一行的最后 5 个音符和第二行开头 5 个音符) 

——分割线——

F[I][J]为第I个与第J个匹配时的最大值

方程:F[I][J]=MAX(F[I][J],F[I-1][J-1]+1) (A[I]-A[J]==A[I-1]-A[J-1])

答案就是所有F[I][J]的最大值,最大值在求的时候顺便算出就行了。滚动数组可优化。

代码:

/*Author:WNJXYK*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;

#define LL long long

inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;}
inline void swap(LL &x,LL &y){LL tmp=x;x=y;y=tmp;}
inline int remin(int a,int b){if (a<b) return a;return b;}
inline int remax(int a,int b){if (a>b) return a;return b;}
inline LL remin(LL a,LL b){if (a<b) return a;return b;}
inline LL remax(LL a,LL b){if (a>b) return a;return b;}

const int Maxn=50003;
int n=0,a[Maxn],l;
int f[Maxn],f1[Maxn],ans=0;

int main(){
	scanf("%d",&n);
	l=n/2;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);a[0]=0x7FFFFFFF;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i-1;j++)
			if(a[i]-a[j]==a[i-1]-a[j-1]&&i-f1[j-1]-1>j){
				f[j]=remax(f[j],f1[j-1]+1);
				ans=remax(ans,f[j]);
			}
		memcpy(f1,f,sizeof f1);
		memset(f,0,sizeof f);
	}
	if(ans+1>=5)
		printf("%d
",ans+1);
	else
		printf("0
");
	return 0;
}

QAQ收工睡觉!

原文地址:https://www.cnblogs.com/WNJXYK/p/4063934.html