1. 程式人生 > 實用技巧 >SMZX夏令營10日遊(第一階段DP):石子歸併,小A點菜,All in All,瘋狂的採藥程式碼詳解

SMZX夏令營10日遊(第一階段DP):石子歸併,小A點菜,All in All,瘋狂的採藥程式碼詳解

石子歸併 經典的區間DP

題目描述

在一個圓形操場的四周擺放 \(\rm N\) 堆石子,現要將石子有次序地合併成一堆.規定每次只能選相鄰的2堆合併成新的一堆,並將新的一堆的石子數,記為該次合併的得分。
試設計出一個演算法,計算出將 \(\rm N\) 堆石子合併成 \(1\) 堆的最小得分和最大得分。

輸入格式

資料的第\(1\)行是正整數 \(\rm N\),表示有 \(\rm N\) 堆石子。
\(2\)行有\(\rm N\)個整數,第\(\rm i\)個整數\(a_i\)表示第\(i\rm\)堆石子的個數。

輸出格式

輸出共 \(2\) 行,第 \(1\) 行為最小得分,第 \(2\)

行為最大得分。

輸入輸出樣例

輸入 #1

\(4\)
\(4\) \(5\) \(9\) \(4\)

輸出 #1

\(43\)
\(54\)

說明/提示

\(1\) \(≤\) \(\rm N\) \(≤\) \(100\)\(0\) \(≤\) \(a_i\) \(≤\) \(20\)

Code

#include<bits/stdc++.h> //萬能頭
using namespace std; 
int n,f2[1006][1006],f1[1006][1006],num[1006],s[1006],maxx,minn;//定義
int sum(int i,int j)//計算i~j石子的數量
{
	return s[j]-s[i-1];
}
int main()  
{ 
cin>>n;//普普通通的輸入
for(int i=1;i<=2*n;i++)//2倍拆環。注:測試樣例最好用freopen或洛谷的線上IDE,否則手動輸入樣例你會發現不得了的東西……
{
	cin>>num[i];//普普通通的輸入+1
	num[i+n]=num[i];//多copy一個環
	s[i]=s[i-1]+num[i];//字首和
}
for(int p=1;p<n;p++)//防止越界 
{
    for(int i=1,j=p+i;(j<2*n) && (i<2*n);i++,j=i+p)//設定i~jDP範圍,i是左邊,就是右邊
    {
    	f1[i][j]=-1;//求最大值設最小值
		f2[i][j]=1e9;//同上,求最小值設最大值
    	for(int k=i;k<j;k++)//列舉分割點
    	{
    		f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+sum(i,j));//動態轉移方程,f1[i][j]為不選,f1[i][k]為i~k的合併得到的得分,f1[k+1][j]同上,k+1~j的合併得到的得分
    		f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+sum(i,j));//同上,不在囉嗦
    	}
    }
}
maxx=-1;//求最大值設最小值
minn=1e9;//同上,求最小值設最大值
for(int i=1;i<=n;i++) maxx=max(maxx,f1[i][i+n-1]);//在f1這裡找最大,本處用的是打擂臺
for(int i=1;i<=n;i++) minn=min(minn,f2[i][i+n-1]);//同上,在f2裡找最小
cout<<minn<<endl<<maxx;//輸出
    return 0;  //完結撒花
}

小A點菜 非常簡單的01揹包

題目背景

\(\rm uim\)神犇拿到了\(\rm uoi\)\(\rm ra\)(鐳牌)後,立刻拉著基友小\(\rm A\)到了一家餐館,很低端的那種。
\(\rm uim\)指著牆上的價目表(太低階了沒有選單),說:“隨便點”。

題目描述

不過\(\rm uim\)由於買了一些輔\(\rm (e)\)\(\rm (ro)\)書,口袋裡只剩\(\rm M\)元(M≤10000)。
餐館雖低端,但是菜品種類不少,有\(\rm N\)種(N≤100),第\(\rm i\)種賣\(\rm a_i\)\(\rm (a_i​ ≤1000)\)
由於是很低端的餐館,所以每種菜只有一份。
\(\rm A\)

奉行“不把錢吃光不罷休”,所以他點單一定剛好把\(\rm uim\)身上所有錢花完。他想知道有多少種點菜方法。
由於小\(\rm A\)肚子太餓,所以最多隻能等待\(1\)秒。

輸入格式

第一行是兩個數字,表示\(\rm N\)\(\rm M\)
第二行起\(\rm N\)個正數\(a_i\)
(可以有相同的數字,每個數字均在\(1000\)以內)。

輸出格式

一個正整數,表示點菜方案數,保證答案的範圍在int之內。

輸入輸出樣例

輸入 #1

\(4\) \(4\)
\(1\) \(1\) \(2\) \(2\)

輸出 #1

\(3\)

Code

//小A點菜(01揹包) (選與不選)
#include<bits/stdc++.h>//萬能頭 
using namespace std;
long long m,f[10000000],W,w[10000],v[10000];//定義 
int main()
{
	cin>>m>>W;//普普通通的輸入 
	for(int i=1;i<=m;i++)
	cin>>w[i];
	f[0]=1;//初始化(小心爆0,之所以設定1,是因為當沒有菜時,只能不選) 
	for(int i=1;i<=m;i++)
		for(int j=W;j>=w[i];j--)//01揹包標準模板 
	        f[j]+=f[j-w[i]];//(累加)
	 cout<<f[W];   
    return 0;//完結撒花
}

All in All (無DP版,指標版本)

題目描述

讀入兩個字串s和t,問是否能通過刪去串t中的某幾個字元得到串s,(大小寫區分),如果能則輸出 Yes,否則輸出 No。

輸入輸出樣例

輸入 #1

\(\rm sequence subsequence\)
\(\rm person compression\)
\(\rm VERDI vivaVittorioEmanueleReDiItalia\)
\(\rm caseDoesMatter CaseDoesMatter\)

輸出 #1

\(\rm Yes\)
\(\rm No\)
\(\rm Yes\)
\(\rm No\)

Code

#include<iostream>
#include<string>  
using namespace std;  
string s,st; 
int n,m,v,p,q,f[100000],zv[100000],zw[100000],fw[100000][5],fv[100000][5];
int main()  
{ 
    while(cin>>st>>s)//多組輸入
    {
    	int i=0,j=0,s_end=s.size(),st_end=st.size();//設定指標
    	while(1)//一直尋找,找到為止
    	{
    		if(st[i]==s[j])//匹配2個字母,如果匹配成功,則尋找下一個字母
    	  {
    		   i++;//移動
			   j++;
    		   if(i==st_end)//如果全部匹配成功,退出
    		 {
    			cout<<"Yes"<<endl;
    			break;
    		 }
    	  }
    	else
    	 {
    		j++;//如果2個字母無法成功匹配,那就在長串選下一個字母再找一次
    		if(j==s_end)//如果全部匹配失敗,退出
    		{
    			cout<<"No"<<endl;
    			break;
    		}
    	 }
    	}
		
    }
    return 0;  
}

瘋狂的採藥 經典完全揹包

題目背景

此題為紀念 \(\rm LiYuxiang\) 而生。

題目描述

\(\rm LiYuxiang\) 是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同種類的草藥,採每一種都需要一些時間,每一種也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”
如果你是\(\rm LiYuxiang\),你能完成這個任務嗎?
此題和原題的不同點:

  1. 每種草藥可以無限制地瘋狂採摘。
  2. 藥的種類眼花繚亂,採藥時間好長好長啊!師傅等得菊花都謝了!

輸入格式

輸入第一行有兩個整數,分別代表總共能夠用來採藥的時間 \(\rm t\) 和代表山洞裡的草藥的數目 \(\rm m\)
第2到第 \(\rm (m+1)\) 行,每行兩個整數,第\(\rm (i+1)\) 行的整數 \(\rm a_i\), \(\rm b_i\)分別表示採摘第 \(\rm i\) 種草藥的時間和該草藥的價值。

輸出格式

輸出一行,這一行只包含一個整數,表示在規定的時間內,可以採到的草藥的最大總價值。

輸入輸出樣例

輸入 #1

\(70\) \(3\)
\(71\) \(100\)
\(69\) \(1\)
\(1\) \(2\)

輸出 #1

\(140\)

說明/提示

資料規模與約定

  1. 對於 30% 的資料,保證\(m≤10^3\)

  2. 對於 100% 的資料,保證\(1≤m≤10^4\)\(1≤t≤10\),且 \(m×t\) < \(10^7\)\(1 ≤a_i\), \(b_i≤10^4≤a\)

Code

#include<bits/stdc++.h>
using namespace std;
long long m,f[10000000],W,w[10000],v[10000];
int main()
{
	cin>>W>>m;
	for(int i=1;i<=m;i++)
	cin>>w[i]>>v[i];//普普通通的輸入
	for(int i=1;i<=m;i++)
	    for(int j=w[i];j<=W;j++)//完全揹包基本模板
	        f[j]=max(f[j-w[i]]+v[i],f[j]);
	cout<<f[W];
    return 0;
}