學校作業-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\n",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\n",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\n",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\n",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\n",ans+1);
	else
		printf("0\n");
	return 0;
}
QAQ收工睡覺!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章