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]
-
h[i] <= H and w[i] <= H and h[i] >= w[i] 這時我們可以讓他躺下,也可以站著,但躺下比站著增加的寬度要多,所以我們直接讓他站著就行
-
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;
}