1. 程式人生 > >『NOIP普及組訓練題:動態規劃專練』

『NOIP普及組訓練題:動態規劃專練』

NOIP普及組訓練題:動態規劃專練

T1蛋糕塔

題目描述

Hl高中要舉行一場蛋糕塔比賽。注意,不是蛋糕比賽,而是蛋糕塔比賽。

學校會提供N種不同型別的蛋糕,第i種蛋糕的高度為Hi(5 <= H_i <= T),營養價值為Vi(1 <= Vi<= 1,000,000),並且保證所有蛋糕的高度為5的整數倍,每種型別的蛋糕沒有數量限制。

蛋糕塔比賽的規則就是要求按照提供的蛋糕,壘成一個高度不超過T(1 <= T <= 1,000)的蛋糕塔,並且要求這個蛋糕塔所有蛋糕的營養價值累加和最高。

因為蛋糕不是很結實,參加比賽的小x發現一個現象,如果某塊蛋糕的高度超過K,那麼這塊蛋糕下面的所有蛋糕的高度都將被壓縮為自己高度的4/5,但是營養價值不會丟失。發現這個情況後的小x很興奮,現在他想知道,如何安排自己的蛋糕塔,能讓營養價值最高。 輸入格式

第一行三個整數:N,T和K;

接下來N行,每行兩個整數:Vi 和 Hi。 輸出格式

一行,一個整數,表示答案。 樣例資料

input

3 53 25 100 25 20 5 40 10

output

240

有3種蛋糕,蛋糕塔的高度限制為53,高度必須超過25的蛋糕才能將其下面的蛋糕高度壓縮。我們按照下面的方法來壘蛋糕:

        個數 高度    價值
 高   -> [1]   25    100
         [2]    4     20  
         [3]    8     40  
         [3]    8     40  
底    -> [3]    8     40

最上的蛋糕將下方的蛋糕都壓縮為4/5.最大價值為240 資料規模與約定

40% 資料保證 N<=20 100% 資料保證 N<=100 其他資料看題目描述

時間限制:1s1s

空間限制:256MB

解析

如果沒有大於k的蛋糕,很簡單,這就是一道完全揹包問題。那麼如果有大於k的蛋糕呢?有一個貪心策略就是放一個大蛋糕再最頂端。如果不放在最頂端,顯然把他移到最頂端更優。那麼我們對沒有大於k的蛋糕的情況做一次完全揹包,得到一個最優解ans1,再對所有物品只用4/5的高度做一次完全揹包,一重迴圈列舉大蛋糕放在最上面的情況,找到一個最優解ans2。最後輸出max(ans1,ans2)即可。

#include<bits/stdc++.h>
using namespace std; int n,height[180]={},value[180]={},v,k,ans=0,f[1080]={}; inline void input() { scanf("%d%d%d",&n,&v,&k); for(int i=1;i<=n;i++) { scanf("%d%d",&value[i],&height[i]); } } inline void dp(void) { for(int i=1;i<=n;i++) { for(int j=height[i];j<=v;j++) { if(height[i]<k)f[j]=max(f[j],f[j-height[i]]+value[i]); } } ans=f[v]; memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) { for(int j=height[i]*4/5;j<=v;j++) { f[j]=max(f[j],f[j-height[i]*4/5]+value[i]); } } for(int i=1;i<=n;i++)if(height[i]>=k)ans=max(ans,f[v-height[i]]+value[i]); } int main() { freopen("cheese..in","r",stdin); freopen("cheese..out","w",stdout); input(); dp(); printf("%d\n",ans); return 0; }

T2硬幣遊戲

題目描述

農夫約翰的奶牛喜歡玩硬幣遊戲,因此他發明了一種稱為“Xoinc”的兩人硬幣遊戲。

初始時,一個有N(5 <= N <= 2,000)枚硬幣的堆疊放在地上,從堆頂數起的第I枚硬幣的幣值為C_i (1 <= C_i <= 100,000)。

開始玩遊戲時,第一個玩家可以從堆頂拿走一枚或兩枚硬幣。如果第一個玩家只拿走堆頂的一枚硬幣,那麼第二個玩家可以拿走隨後的一枚或兩枚硬幣。如果第一個玩家拿走兩枚硬幣,則第二個玩家可以拿走1,2,3,或4枚硬幣。在每一輪中,當前的玩家至少拿走一枚硬幣,至多拿走對手上一次所拿硬幣數量的兩倍。當沒有硬幣可拿時,遊戲結束。 兩個玩家都希望拿到最多錢數的硬幣。請問,當遊戲結束時,第一個玩家最多能拿多少錢呢? 輸入格式

第1行:1個整數N 第2…N+1行:第i+1行包含1個整數C_i 輸出格式

1個整數表示第1個玩家能拿走的最大錢數。

input

5 1 3 1 7 2

output

9

資料規模與約定

時間限制:1s

空間限制:256MB 樣例說明

第1個玩家先取走第1枚,第2個玩家取第2枚;第1個取走第3,4兩枚,第2個玩家取走最後1枚。

解析

觀察題目,顯然,有兩個資訊在動態規劃時是必要的,那就是上一次對手取的硬幣個數和對多少個硬幣進行遊戲。那麼,我們設f[i][j]f[i][j]代表堆底1~ i個硬幣(所以倒序讀入),上一次對手取了j的最優解。那麼f[i][j]f[i][j]怎麼轉移呢?我們還需要列舉這一次取的硬幣個數k,其實,f[i][j]f[i][j]就相當於1~ i個硬幣的所有價值和減去對手取出的最大價值,而對手取出的最大價值就可以用f[ik][k]f[i-k][k]表示,所以,我們處理一個字首和sum[i]sum[i]代表堆底1~i個硬幣的價值和,狀態轉移方程就是f[i][j]=max{sum[i]f[ik][k]}(1&lt;=k&lt;=min(2j,i))f[i][j]=max\{sum[i]-f[i-k][k]\}(1&lt;=k&lt;=min(2*j,i))。 這樣本題的大體框架就出來了。但是,這樣列舉時間複雜度是O(n3)O(n^3)的,考慮一個優化:外層迴圈i不變時,j每增加1,決策集合k的取值範圍只增加2,那就只要在每一次轉移是,取上一個最優值,並只更新兩個新值即可,這樣我們就能優化的一重迴圈。

#include<bits/stdc++.h> 
using namespace std;
int n,coin[2080]={},f[2080][2080]={},s[2080]={};
inline void input(void)
{
	scanf("%d",&n);
	for(int i=n;i>=1;i--)scanf("%d",&coin[i]);
	for(int i=1;i<=n;i++)s[i]=s[i-1]+coin[i];
}
inline void dp(void)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			int Max=f[i][j-1];
			if(i>=j*2-1)Max=max(Max,s[i]-f[i-(j*2-1)][j*2-1]);
			if(i>=j*2)Max=max(Max,s[i]-f[i-j*2][j*2]);
			f[i][j]=Max;
		}
	}
}
int main()
{
	freopen("xoinc..in","r",stdin);
	freopen("xoinc..out","w",stdout);
	input();
	dp();
	printf("%d\n",f[n][1]);
	return 0;
}

T3遊蕩的奶牛

題目描述

奶牛們在被劃分成N行M列(2 <= N <= 100; 2 <= M <= 100)的草地上游走,試圖找到整塊草地中最美味的牧草。Farmer John在某個時刻看見貝茜在位置(R1, C1),恰好T (0 < T <= 15)秒後,FJ又在位置(R2, C2)與貝茜撞了正著。FJ並不知道在這T秒內貝茜是否曾經到過(R2, C2),他能確定的只是,現在貝茜在那裡。

設S為奶牛在T秒內從(R1, C1)走到(R2, C2)所能選擇的路徑總數,FJ希望有一個程式來幫他計算這個值。每一秒內,奶牛會水平或垂直地移動1單位距離(奶牛總是在移動,不會在某秒內停在它上一秒所在的點)。草地上的某些地方有樹,自然,奶牛不能走到樹所在的位置,也不會走出草地。

現在你拿到了一張整塊草地的地形圖,其中’.‘表示平坦的草地,’*'表示擋路的樹。你的任務是計算出,一頭在T秒內從(R1, C1)移動到(R2, C2)的奶牛可能經過的路徑有哪些。 輸入格式

第1行: 3個用空格隔開的整數:N,M,T 第2…N+1行: 第i+1行為M個連續的字元,描述了草地第i行各點的情況,保證字元是’.‘和’*'中的一個 第N+2行: 4個用空格隔開的整數:R1,C1,R2,以及C2 輸出格式

第1行: 輸出S,含義如題中所述

input

4 5 6 …. …. … … 1 3 1 5

輸入說明:

草地被劃分成4行5列,奶牛在6秒內從第1行第3列走到了第1行第5列。

output

1

輸出說明:

奶牛在6秒內從(1,3)走到(1,5)的方法只有一種(繞過她面前的樹)。

資料規模與約定

時間限制:1s

空間限制:256MB

解析

此處應該有一句dfs一定會超時,但是…,這道題搜尋能過。 當然,正規解法是動態規劃,保證不會超時。 這是一道最簡單的二維動態規劃,此類題目在之前的部落格中以及有所提及。我們設f[i][j][k],代表座標為i,j的點,在k時刻到達的方案數,那麼我們三重迴圈列舉列舉i,j,k,從四個可能的方向進行狀態轉移即可。

#include<bits/stdc++.h>
using namespace std;
int n,m,t,Map[300][300]={},beginx,beginy,endx,endy,ans=0;
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0},f[300][300][30]={};
inline void input(void)
{
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			char temp;cin>>temp;
			if(temp=='.')Map[i][j]=1;
			else Map[i][j]=0;
		}
	}
	scanf("%d%d%d%d",&beginx,&beginy,&endx,&endy);
}
inline void dp(void)
{
	f[beginx][beginy][0]=1;
	for(int k=1;k<=t;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				for(int l=0;l<4;l++)
				{
					int x=i+dx[l],y=j+dy[l];
					if(!(Map[x][y]))continue;
					f[i][j][k]+=f[x][y][k-1];
				}
			}
		}
	}
	printf("%d\n",f[endx][endy][t]);
}
int main(void)
{
	freopen("ctravel.in","r",stdin);
	freopen("ctravel.out","w",stdout);
	input();
	dp();
	return 0;
}


T4格鬥

題目描述

格鬥俱樂部是格鬥愛好者的一個組織,在這裡,格鬥者們能通過與別的成員進行格鬥來釋放自己的壓力與輕鬆自己的情緒。

最近俱樂部舉行了一場比賽,該比賽有N位選手參加,他們將圍成一個圓圈,每一場比賽圈內任意的兩位相鄰的選手均可進行相互的格鬥,勝利者將留在圈內進入下輪比賽而失敗者則直接被送往醫院(沒有平局)。比賽是殘酷的,最後圈內將只剩下一位選手,他將是總冠軍。

我們做個奇怪的假設,兩位選手進行格鬥,他們比賽的結果總是確定的。雖然俱樂部的成員們都很喜歡格鬥,但是他們仍然很希望能獲得總冠軍。

現在你通過統計已經知道了任意兩位選手格鬥的結果,你有責任告訴每位選手,如果賽程合適安排的話,他是否可能成為總冠軍。 輸入格式

資料第一行是一個整數N,(1<=N<=40),表示比賽的選手數量。

接下來給出一個N*N的“0”、“1”矩陣A(行內用空格隔開),第i行第j列為 1表示選手i能戰勝選手j,否則選手j能戰勝選手i。

你可以假定Aij與Aji(i≠j)均是不同的且Aii=0。比賽開始時所有選手按順時針方向由編號1到編號N站成一個圈,初始時編號1與編號N的選手是相鄰的。 輸出格式

輸出包含N行,每行為一個整數“0”或“1”,“1”表示第i號選手有可能成為冠軍,“0”表示不可能。 樣例資料

input

3 0 1 1 0 0 1 0 0 0

output

1 0 0

資料規模與約定

時間限制:1s1s

空間限制:256MB

解析

很明顯的區間動態規劃,我們設f[i][j][k]代表第i個人到第j個人間第k個人是否能夠獲勝,如果能f[i][j][k]=1,否則f[i][j][k]=0。由於資料量小,我們可以直接暴力轉移狀態:我們五重迴圈列舉列舉一個區間,一箇中間點,左端點及右端點和中間點構成的兩個區間裡,在分別列舉兩個左中間點,右中間點。 在這裡插入圖片描述 如果[left ~ mid]的獲勝者是lmid,[mid+1 ~ right]的獲勝者是rmid,那麼[left,right]的獲勝者就必然由lmid和rmid產生,我們直接比較他們即可,五重迴圈的狀態轉移思路就是這樣。 當然,這道題還要處理環的問題,這裡,我們用老方法,把序列複製一遍接在後面,最後再一重迴圈找到解即可。

#include<bits/stdc++.h>
using namespace std;
int n,victory[100][100]={},f[100][100][100]={},ans[100]={};
inline void input(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			scanf("%d",&victory[i][j]);
			victory[i+n][j]=victory[i][j+n]=victory[i+n][j+n]=victory[i][j];
		}
	}
} 
inline void dp(void)
{
	for(int i=1;i<=2*n;i++)f[i][i][i]=1;
	for(int len=2;len<=n;len++)
	{
		for(int left=1;left+len-1<=2*n;left++)
		{
			int right=left+len-1;
			for(int mid=left;mid<right;mid++)
			{
				for(int lmid=left;lmid<=mid;lmid++)
				{
					if(f[left][mid][lmid])
					{
						for(int rmid=mid+1;rmid<=right;rmid++)
						{
							if(f[mid+1][right][rmid])
							{
								int vic;
								if(victory[lmid][rmid])vic=lmid;else vic=rmid;
								f[left][right][vic]=1;
							}
						}
					}
				}
			}
		}
	}
}
inline void output(void)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			ans[j]|=f[i][i+n-1][j];
		}
	}
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
}
int main(void)
{
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	input();
	dp();
	output();
	return 0;
}

T5關燈問題

題目描述

在海亮中學的路上,新建了若干漂亮的路燈,這給同學們晚上的出行帶來很大的方便。但是,問題隨之出現了。

一天晚上,我們資訊學競賽班的 小x 同學正往校門外走,忽然眼前一片漆黑,於是直 接把眼鏡都摔掉了,再也找不到。後來 小x 同學從學校管理處瞭解到昨晚路燈突然熄滅是 因為電路不堪重負,導致空氣開關跳閘。善於思考的 SFJ 同學考慮將路燈進行改建,以避免再次出現類似的問題。

小x 同學仔細瞭解每盞路燈的耗電量 a[i]與照明度 z[i],已知共有 N 盞電燈,並且每盞電燈都可能有不同的耗電量與照明度,現在的問題是要把這 N 盞電燈分為 M 組,新分出的每組燈的耗電量(即是該組所有開啟電燈的耗電量之和)不能超過該組的電燈數目的 T倍,在滿足這樣的前提下使得照明度儘可能的大,最後算出 M 組的最大照明度的和。由於每組耗電量的限制,該組中的某些電燈可能不被使用,但是仍然應該算作該組燈的數