1. 程式人生 > 實用技巧 >8月1號考試

8月1號考試

T1 火苗

題目描述

這是n堆燃燒的火焰,QXH想將它們全部熄滅。現在有m個不同的罐子,罐子裡裝著神奇的液體,可能會使火焰點

燃或熄滅或者沒有影響,而且每個罐子對不同火焰的影響是不同的,可他並不知道每一罐液體對不同的火焰是什麼

效果,於是當他每拿起一個罐子時,便會將它用在每一堆火焰上,但是你知道,於是他來問你,最少使用多少次罐

子裡的液體可以使火焰全部熄滅。罐子是可以重複使用的。

輸入格式

前兩行兩個數,n m

接下來m行,每行n個數,aij表示第i罐液體對第j堆火焰的效果,-1表示讓火焰點燃,1表示讓火焰熄滅,0表示無影

響。

輸出格式

一個整數,表示最少次數。如果沒有任何辦法使其全部熄滅,輸出-1

樣例輸入

32
1 0 1
-1 1 0

樣例輸出

2

資料範圍

30%的資料,n<=5 ,m <= 10
70%的資料,n<=10,m<=20
100%的資料,n<=10,m <= 100

看到n的資料範圍很小,我們可以嘗試一下搜尋。

在經過我無數次的死迴圈後,發現一個問題,那就是陣列開不下。

那怎麼辦呢???

我們可以像八數碼難題那樣,把狀態用二進位制表示出來,並用map儲存。

然後直接爆搜就可以了。

程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#define LL long long
using namespace std;
int n,m,tmp,a[110][15],pre[1225];
bool vis[20];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
	return s * w;
}
map<LL,LL> d,in;
void bfs()
{
	queue<int> q;
	q.push(tmp); in[tmp] = 1;//把起始狀態放進去
	while(!q.empty())
	{
		int t = q.front(); q.pop();
		int k = t;
		if(t == 0) break;
		for(int i = n; i >= 1; i--)//把二進位制轉為陣列
		{
			pre[i] = k%10;
			k /= 10;
		}
		for(int i = 1; i <= m; i++)
		{
			int u = 0;
			for(int j = 1; j <= n; j++)//改變狀態
			{
				if(a[i][j] == 0) vis[j] = pre[j];
				if(a[i][j] == -1) vis[j] = 1;
				if(a[i][j] == 1) vis[j] = 0; 
			}
			for(int j = 1; j <= n; j++)
			{
				u = u * 10 + vis[j];
				vis[j] = pre[j];//回溯
			}
		    if(!in.count(u)){//更新
		    	    in[u] = 1;
			    q.push(u);
			    d[u] = d[t] + 1;
			}
		}
	}
}
int main()
{
//	freopen("flame.in","r",stdin);
//	freopen("flame.out","w",stdout);
	n = read(); m = read();
	for(int i = 1; i <= m; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			a[i][j] = read();
		}
	}
	for(int i = 1; i <= n; i++) tmp = tmp * 10 + 1;
	for(int i = 1; i <= n; i++) vis[i] = 1;//一開始所有的火苗都在燃燒,設為1.
        bfs();
	if(d[0] == 0) cout<<-1;
	else printf("%d\n",d[0]);
	fclose(stdin); fclose(stdout);
	return 0;
}

T2 拍照

題目描述

QXH召集了n個小夥伴一起來拍照。他們分別有自己的身高Hi和寬度Wi

為了放下這個照片並且每個小夥伴都完整的露出來,必須需要一個寬度為\(\sum_{i = 1}^{n} w_i\),長度為max{Hi}的相框。(因為不

能疊羅漢)。

QXH為了節省相框的空間,它有了絕妙的idea,讓部分人躺著!一個人躺著相當於是身高變成了Wi,寬度變成了

Hi。但是很多人躺著不好看,於是QXH規定最多隻有n/2個人躺著。(也就是說當n=3時最多隻有1個人躺著,當

n=4時最多隻有2個人躺著)

QXH現在想問你,當其中部分人躺著後,相框的面積最少是多少。

輸入格式

第一行一個數n。

接下來n行,每行兩個數分別是Wi,Hi。

輸出格式

你需要輸出這個相框的面積最少是多少。

樣例輸入

3
3 1
2 2
4 3

樣例輸出

21

樣例解釋

如果沒人躺過來,需要27的面積。

我們只要讓第1個人躺過來,就只需要21的面積!

資料範圍

對於30%的資料n<=10。
對於60%的資料n<=1000,Wi,Hi<=10。
對於100%的資料1<=n,Wi,Hi<=1000。

這道題第一眼就覺得是貪心(但沒想到lpj大佬用dp切了%%%)

我們可以列舉一個最高高度 H,在列舉每個人怎麼站,同時更新一下寬度d。

最終的答案就是所有的 H * d 的最小值。

在我們列舉每個人時,會遇到以下幾種情況。

1.h[i] > H and w[i] > H 這種情況無論怎麼翻轉都裝不下,說明我們高度列舉小了,直接continue就行

2.h[i] > H and w[i] < h 這時我們可以讓他躺下,這樣就可以裝下了,同時寬度要加上 h[i]

3.h[i] <= H and w[i] > H 這時我們讓他躺下時裝不下的,所以只能站著,那麼寬度要加上 w[i]

  1. h[i] <= H and w[i] <= H and h[i] >= w[i] 這時我們可以讓他躺下,也可以站著,但躺下比站著增加的寬度要多,所以我們直接讓他站著就行

  2. h[i] <= H and w[i] <= H and h[i] < w[i] 這時他既可以躺著,也可以站著,但躺下要比站著所增加的寬度要少,所以我們讓他躺下。

但可能前面必須躺的已經躺下,剩下能躺的人數不足以讓他躺下,我們設還可以躺的人為k,我們可以先加上 w[i],再對 w[i] - h[i] 從大到小

排個序。取前k個最大的數,在用總寬度減去這k個數之和,就是所有人都站好的最終寬度。

這道題就是分類討論麻煩了許多,但理解了思想就很好寫出來了。

程式碼(我又拉行了霧)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,cnt,k,ans = 233333333;
int h[1010],w[1010],e[1010];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+(ch -'0'); ch = getchar();}
	return s * w;
}
bool comp(int a,int b)
{
	return a > b;
}
int main()
{
//	freopen("photo.in","r",stdin);
//	freopen("photo.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; i++)
	{
		w[i] = read(); h[i] = read(); 
	}
	for(int H = 1; H <= 1000; H++)//列舉最大的高度
	{
		int d = 0, k = 0, cnt = 0; bool flag = 0;//每次列舉都要清空
		for(int i = 1; i <= n; i++)
		{
			if(w[i] > H && h[i] > H)//第一種情況
			{
				flag = 1; break;
			}
		        if(h[i] > H)
		        {
		    	    if(w[i] <= H) //第二種情況
		    	    {
		    		cnt++;
		    		d += h[i];
		    	    }
		        }
		       if(h[i] <= H)
		       {
		    	    if(w[i] > H)//第三種情況
		    	    {
		    		d += w[i];
		    	    }
		    	    if(w[i] <= H)
		    	    {
		    		if(h[i] >= w[i])//第四種情況
		    		{
		    			d += w[i];
		    		}
		    		if(h[i] < w[i])//第五種情況
		    		{
		    			k++;
		    			d += w[i];
		    			e[k] = w[i] - h[i];
		    		}
		    	    }
		       }
		}
		if(flag) continue;
		if(cnt > n/2) continue;//如果必須躺的人數都大於n/2說明此高度不符合情況
		sort(e+1,e+k+1,comp); 
		for(int i = 1; i <= min(n/2-cnt , k); i++) d -= e[i];
		ans = min(ans,H * d);
	}

	printf("%d",ans);	
	fclose(stdin); fclose(stdout);
	return 0;
}

T3 恰飯

題目描述

這個神祕的村莊裡有4家美食店。這四家店分別有A,B,C,D種不同的美食。QXH想在每一家店都吃其中一種美食。每

種美食需要吃的時間可能是不一樣的。

現在給定第1家店A種不同的美食所需要吃的時間a1,a2,…,aA。

給定第2家店B種不同的美食所需要吃的時間b1,b2,…,bB。

以及c和d。

QXH擁有n個時間,問它有幾種吃的方案。

輸入格式

第一行5個數分別表示n,A,B,C,D。

第二行A個數分別表示ai。

第三行B個數分別表示bi。

第四行C個數分別表示ci。

第五行D個數分別表示di。

輸出格式

一個數表示答案。

樣例輸入

11 3 1 1 1
4 5 6
321

樣例輸出

2

資料範圍

對於30%的資料A,B,C,D<=50
對於另外30%的資料n<=1000。
對於100%的資料1<=n<=100000000,1<=A,B,C,D<=5000,0<=ai,bi,ci,di<=100000000。

這道題,直接暴力滾粗,可以得到30分。

我們可以考慮優化一下,減少兩重迴圈。

對於每一個 a[i] + b[j],他所能匹配的的c[k] + d[u]也是可以確定的。

所以我們可以把 a[i] + b[j] 以及c[k] + d[u]都處理出來。

再從大到小排序,開兩個指標,維護一下就可以了。

然鵝,快排在這裡是會被卡的,所以我們桶排就可以了(第一次見1024MB 的題QWQ)

程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,A,B,C,D,cnt,tot,maxn;
int a[5010],b[5010],c[5010],d[5010],f[25000005],g[25000005],tong[100000010];
long long ans;
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
	return s * w;
}
int main()
{
//	freopen("eat.in","r",stdin);
//	freopen("eat.out","w",stdout);
	n = read(); A = read(); B = read(); C = read(); D = read();
	for(int i = 1; i <= A; i++) a[i] = read();//輸入
	for(int i = 1; i <= B; i++) b[i] = read();
	for(int i = 1; i <= C; i++) c[i] = read();
	for(int i = 1; i <= D; i++) d[i] = read();	
	for(int i = 1; i <= A; i++)
	{
		for(int j = 1; j <= B; j++)
		{
			if(a[i] + b[j] > n) continue;
                        tong[a[i] + b[j]]++;//開個桶存a[i] + b[j]出現的次數
			maxn = max(maxn,a[i] + b[j]);//a[i] + b[j]的最大值
		}
	}
       for(int i = 0; i <= maxn; i++)
       {
    	       while(tong[i])
    	       {
    		      tong[i]--;
    		      f[++cnt] = i;//處理每種結果
    	        }
        }
	maxn = 0;
	for(int i = 1; i <= C; i++)//在處理一下c[i] + d[j]
	{
		for(int j = 1; j <= D; j++)
		{
			if(c[i] + d[j] > n) continue;
			tong[c[i] + d[j]]++;
			maxn = max(maxn,c[i] + d[j]);
		}
	}
	for(int i = 0; i <= maxn; i++)
	{
		while(tong[i])
		{
			tong[i]--;
			g[++tot] = i;
		}
	}
	int last = 0;
	for(int i = tot; i >= 1; i--)//找與f[1] 能匹配的最大的序號
	{ 
	    if(g[i] + f[1] <= n)
	    {
	    	last = i;
	    	break;
	    }
	}
	for(int i = 1; i <= cnt; i++)
	{
		ans += last;//加上這一個的答案
		while(last && f[i+1] + g[last] > n) last--;//處理下一個的貢獻,只要last不為0,且f[i+1] + g[last] > n 說明不符合情況 last--
	}
	printf("%lld\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

ENDING