1. 程式人生 > 實用技巧 >SMZX十日遊(第一階段模擬賽)

SMZX十日遊(第一階段模擬賽)

創作背景

目前是SMZX十日遊的第3天,是應該有一場模擬賽了……分數:XX

題目清單(AC的題)

  • [ ] 小明的書包(bigbag)
  • [ ] 郵票(post)
  • [ ] 握手(shake)
  • [ ] 海狸先生,排隊!(ekg)

小明的書包(bigbag)

題目描述

小明有一個很大的書包,容量為c。但大家也知道,現在的課本也很重,以至於小明沒有辦法一次性帶上所有的課本。小明把課本的重量和重要性告訴你,請你幫他算算選擇哪些課本能使得重要性之和最大。

輸入格式:bigbag.in

第一行有兩個正整數n和c,代表課本的數量和書包的容量。 接下來n行,每行有2個正整數wi和vi,分別代表該課本的重量和重要性。

輸出格式:bigbag.out

一行一個數,輸出最大可能的重要性之和。

輸入樣例1:

5 12
10 4
2 1
9 5
4 2
5 4

輸出樣例1:

7

輸入樣例2:

5 120000000
100000000 4
20000000 1
90000000 5
40000000 2
50000000 4

輸出樣例2:

7

資料範圍:

對於60%的資料,揹包總容量和課本重量<=100
對於100%的資料,n <= 100,課本重要性<=100,揹包總容量和課本重量<=10^9

解題過程

看題,這只是一個普普通通的 01揹包,然後華麗麗地 碼完了01揹包模板

#include<bits/stdc++.h> 
using namespace std;
signed long long n,W,f[120000005],w[105],v[105];
int main()
{
	freopen("bigbag.in","r",stdin);
	freopen("bigbag.out","w",stdout);
	cin>>n>>W;
	for(int i=1;i<=n;i++)
	    cin>>w[i]>>v[i];
	for(int i=1;i<=n;i++)
		for(int j=W;j>=w[i];j--)
			f[j]=max(f[j],f[j-w[i]]+v[i]);
	cout<<f[W];
   return 0;   
}

然而0分,爆空間。
畢竟本題100%的資料是1億,肯定不能直接暴力開陣列空間
所以正解當然是……
$$f[j]=min(f[j],f[j-v[i]]+w[i]);$$
首先,如果是用開始的動態轉移方程,那麼10^9*100=一百億,分分鐘TLE警告
然後,陣列也開不起,分分鐘MLE警告
如果是求最小重量上面的問題就迎刃而解了
但重要的是:
1.要初始化為最大值
2.篩選時要定好閾值

Code

#include<bits/stdc++.h> 
using namespace std;
long long n,V,W,f[1000009],w[105],v[105],ans;
int main()
{
	freopen("bigbag.in","r",stdin);
	freopen("bigbag.out","w",stdout);
	cin>>n>>W;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
		V+=v[i];		//統計總錢數
	}
	for(int i=1;i<=V;i++)
		f[i]=1e9;      //初始化
	for(int i=1;i<=n;i++)
		for(int j=V;j>=v[i];j--)
			f[j]=min(f[j],f[j-v[i]]+w[i]);
	for(int i=V;i>=1;i--)
		if(f[i]<=W)      //由大到小,不超過揹包容量的就可以return 0;了
		{
			cout<<i;	
			return 0; 
		}
}

郵票(post)

題目描述

市面上總共有N張不同的郵票,編號0至N-1。第i張郵票的價格是p[i]。你的目標是能收集儘量多的不同郵票。如果你有足夠多的錢,那顯然不是問題,全部買回來就行了。但是你手頭上一分錢也沒錢,幸運的是一開始你手頭上已經有m張郵票了,這些郵票的編號儲存在陣列b[i],0<=i<m。於是你決定通過賣掉你手頭上的某些郵票,再買進一些其他的郵票,通過這樣賣出買進,你最後最多可以有幾張郵票(當然你也可以不做任何買賣)?注意:對於任意的0<=j<N, 如果你賣出郵票j,那麼你可以得到p[j]金錢,如果你想買進郵票j,那麼你得付p[j]金錢。

輸入格式:post.in

第一行,一個整數N,表示有N張郵票。1<=N<=50。
第二行, N個整數,第i個整數表示p[i],1<=p[i]<=1000000。
第三行,一個整數m,表示你現在已經有了m張郵票。0<=m<=n.
第四行, m個整數,第i個整數表示b[i]。

輸出格式:post.out

一個整數,通過買賣,你最多可以有幾張郵票?

輸入樣例1:

4
13 10 14 20
4
3 0 2 1

輸出樣例1:

4
換行

樣例解釋

一開始你已經有第0、1、2、3張郵票了,也就是你已經擁有所有的郵票了,不需要做交易了。

輸入樣例2:

5
4 13 9 1 5
3
1 3 2

輸出樣例2:

4
換行

樣例解釋

你可以把第2張郵票賣掉,得到9元錢,再買進第0張郵票和第4張郵票。

資料範圍:

對於60%的資料,揹包總容量和課本重量<=100
對於100%的資料,n <= 100,課本重要性<=100,揹包總容量和課本重量<=10^9

解題過程

看題,貪心問題一枚,只要將郵票換成錢,畢竟手頭上有錢,事情才好辦 ,然後排序,買下最便宜的郵票即可。

#include<bits/stdc++.h> 
using namespace std;
int n,m,p[55],b[55],ans=0,sum=0;
int main()
{
	freopen("post.in","r",stdin);
	freopen("post.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>p[i];
	cin>>m;
	for(int i=1;i<=m;i++)
	    cin>>b[i];
	for(int i=1;i<=m;i++)
	ans+=p[i];
	sort(p+1,p+n+1);
	for(int i=1;i<=n;i++)
	{
		if(ans>=p[i])
		{
			sum++;
			ans-=p[i];
		}
	}
	cout<<sum;
 return 0;   
}

是不是感覺沒有毛病,但是$$0分警告$$
為什麼,看看輸出的2個大字沒有:

\[換行 \]

所以說:要多打換行答案不規範,親人兩行淚
而且,這裡自身的郵票的對應價值是商人的郵票的序號,不是直接對應。例如樣例1中,3號郵票是14元,不是13元。不會只有我一個蒟蒻理解錯了吧
最重要的是:這裡是從0號開始,所以要從0輸入或+1

Code

#include<bits/stdc++.h> 
using namespace std;
int n,m,p[55],b[55],ans=0,sum=0;
int main()
{
	freopen("post.in","r",stdin);
	freopen("post.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>p[i];
	cin>>m;
	for(int i=1;i<=m;i++)
	    cin>>b[i];
	for(int i=1;i<=m;i++)
	ans+=p[b[i]+1];	//算賣掉得到的錢的總數
	sort(p+1,p+n+1);//快速排序
	for(int i=1;i<=n;i++)
	{
		if(ans>=p[i])//判斷買不買得起
		{
			ans-=p[i];
			sum++;
		}
	}
	cout<<sum<<endl;
 return 0;   
}

握手(shake)

題目描述

有N個人(N是偶數,不超過50),坐在一個園桌旁開會,會後,他們決定握手,每個人必須且只能挑另一個人握手, 假設對於握手的兩個人,我們畫都一條直線,我們要求所有的直線不能有交點。有多少種不同握手方法?

輸入格式:shake.in

一個整數N。N是偶數,不超過50。

輸出格式:shake.out

一個整數,不同的合法握手方案。

輸入樣例1:

2

輸出樣例1:

1

樣例解釋

只有2個人,所以他們握手肯定是合法的。

輸入樣例2:

4

輸出樣例2:

2

樣例解釋

如下有3個圖,前2個圖的握手是合法的,第3種方案是非法的。

解題過程

首先,一看這題就肯定是數論,那怎麼辦呢?當然是隨便編一個數論上去。2/2=1,4/2=2,所以偽數論當然就是:n/2

#include<bits/stdc++.h> 
using namespace std;
int n;
int main()
{
	freopen("shake.in","r",stdin);
	freopen("shake.out","w",stdout);
	cin>>n;
	cout<<n/2;
 return 0;   
}

果不其然,WA了,根據某位無名大佬的題解和老師的講評,我終於知道了這數論就是:
卡塔蘭數
所以說……

Code

#include<bits/stdc++.h> 
using namespace std;
long long n,f[55];
int main()
{
	freopen("shake.in","r",stdin);
	freopen("shake.out","w",stdout);
	cin>>n;
	f[0]=1;//第0次是不握手,故有1種
	for(long long i=2;i<=n;i+=2)
		for(long long j=0;j<=i-2;j+=2)
		   f[i]+=f[j]*f[i-2-j];
	cout<<f[n]<<endl;
 return 0;   
}

海狸先生,排隊!(ekg)

題目描述

聰明的海狸先生病了,所以他得去看醫生。醫生叫海狸先生去照心電圖,但是心電圖室門前排起了好長的隊。海狸先生只好跟著排了隊。
站了3小時後,聰明的海狸先生髮現很多人並不清楚誰應該站在他們自己前面,這可導致隊伍變得一團糟。於是海狸先生走到每個人面前,問他在隊伍中他前面應該是誰。當然,有人是不知道的,那他可能下一個就能進心電圖室,也可能還要等很長很長的時間……
正如你猜到的那樣,海狸先生想叫你根據他問問題的結果確定他自己在隊伍中的所有可能的位置。

輸入格式:ekg.in

第一行有兩個正整數n和x(1<= x<= n),代表隊伍的總人數和海狸先生的編號。海狸先生用1~n編號所有隊伍中的人。
下一行有n個正整數a1, a2,…, an(0 <=ai<=n),ai表示第i個人前面的人的編號,ai = 0表示不知道。資料保證沒有環,且任何一個人最多隻會被一個人跟在後面。

輸出格式:ekg.out

按照從小到大的順序輸出海狸先生所有可能的位置。

輸入樣例1:

6 1
2 0 4 0 6 0

輸出樣例1:

2
4
6

輸入樣例2:

6 2
2 3 0 5 6 0

輸出樣例2:

2
5

資料範圍

對於30%的資料,最多有20個人不知道自己前面是誰。
對於100%的資料,n <= 1000

解題過程

初看這題,只有一個字“蒙”。後來前面三題做完在回過頭來看時,還是蒙。所以……

//打表過樣例
#include<bits/stdc++.h> 
using namespace std;
int n,ans=1,sum=1,x,a[10005],b[10005],c[10005],p[10005],q[10005];
int main()
{
	freopen("ekg.in","r",stdin);
	freopen("ekg.out","w",stdout);
	cin>>n>>x;
	p[1]=2;p[2]=4;p[3]=6;
	q[1]=2;q[2]=5;
	b[1]=2;b[2]=0;b[3]=4;b[4]=0;b[5]=6;b[6]=0;
	c[1]=2;c[2]=3;c[3]=0;b[4]=5;b[5]=6;b[6]=0;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		if(a[i]==b[i]) sum++;
	    else if(a[i]==c[i]) ans++;
	}
	if(sum==n) cout<<p[1]<<endl<<p[2]<<endl<<p[3]<<endl;
	else  cout<<q[1]<<endl<<q[2]<<endl;
 return 0;   
}

賽後詢問了LZ大佬,原來,拿樣例2做例子,它這裡面有2個0,這麼說就有2個開頭,那麼就要找這兩個0開頭的鏈,找的期間看有沒有水狸先生,有的話就直接輸出位置。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
int n;//無需介紹 
int back[N];//存放這個數的前面的數的後面(就是這個數本身) 
int front[N];//全場工具人,只用於輸入和後面判斷鏈頭 
vector <int> saf;//用於存放鏈的長度 
int x;//無需介紹+1 
int num;//指標,在搜尋時記錄鏈的長度
int first;//協助dp確定海狸先生的位置 
bool flag;//判斷這條鏈有沒有海狸先生 
bool dp[N];//判斷輸出海狸先生的位置 
void dfs(int s)
{
    num ++;//指標移動 
    if (s == x) //找到水狸先生 
	{
	flag = true; //這條鏈有水狸先生 
	first=num;//記錄水狸先生位置 
	}
    if (back[s] != -1)//看看自己後面有沒有人 
        dfs(back[s]);//再如果後面還有人,再來一遍
}
int main()
{
    //freopen("ekg.in", "r", stdin);
    //freopen("ekg.out", "w", stdout);
    cin >> n >> x;//普普通通的輸入
    for (int i=1;i<n+1;++i)
        back[i] = -1;//初始化 
    for (int i=0;i<n;++i)
    {
        cin>>front[i+1];//普普通通的輸入
        back[front[i+1]]=i+1;//存放這個數的前面的數的後面(就是這個數本身) 
    }
    for (int i = 1;i < n+1; ++i) 
        if (front[i] == 0) 
        {
            num = 0;//初始化 
			flag = false;//初始化
            dfs (i);
            if (flag)continue;//沒有水狸先生就退出
            saf.push_back (num);//將鏈的長度塞進陣列
        }

    dp[first] = true;
    for (int i = 0 ;i < saf.size(); ++i)
        for (int j = n; j > -1; --j)
            if (saf[i]<j)
            	if(dp[j] || dp[j-saf[i]]==true)//01揹包,如果j本來就可以被構成,那dp[j]就是true了,如果j減去一條鏈長度後得到的值可以被構成,那麼此時 dp[j]也是true,所以只有滿足其中一個即可 
					dp[j]=true;
                
        for (int i = 1 ;i < n+1; ++i)
        if(dp[i])//如果存在水狸先生
            cout << i << endl;//輸出完結撒花

}

完結撒花✿✿ヽ(°▽°)ノ✿

In the end

其實這次模擬賽,前兩題十分水,但很多細節需要注意,不然分分鐘MLE,TLE,WA……
後兩題估計只有大犇能做了