2019CSP-J普及組複賽題解

A.數字遊戲

題意描述
小 K 同學向小 P 同學發送了一個長度爲 8 的 01 字符串來玩數字遊戲,小 P 同學想 要知道字符串中究竟有多少個 1。 
 注意:01 字符串爲每一個字符是 0 或者 1 的字符串,如“101”(不含雙引號)爲一 個長度爲 3 的 01 字符串。

解題思路
簽到題。
代碼示例

#include<bits/stdc++.h>
using namespace std;
string str;
int cnt = 0;
int main(){
	cin >> str;
	for(int i = 0;str[i];i++) if(str[i] == '1') cnt++;
	cout << cnt << endl;
	return 0;
}

B.公車換乘

題意描述
著名旅遊城市 B 市爲了鼓勵大家採用公共交通方式出行,推出了一種地鐵換乘公交 車的優惠方案:在搭乘一次地鐵後可以獲得一張優惠票,有效期爲 45 分鐘,在有效期內可以 消耗這張優惠票,免費搭乘一次票價不超過地鐵票價的公交車。在有效期內指 開始乘公交車的時間與開始乘地鐵的時間之差小於等於 45 分鐘,即:tbustsubway45t_{bus} - t_{subway} ≤ 45
搭乘地鐵獲得的優惠票可以累積,即可以連續搭乘若干次地鐵後再連續使用優 惠票搭乘公交車。搭乘公交車時,如果可以使用優惠票一定會使用優惠票;如果有多張優惠票滿 足條件,則優先消耗獲得最早的優惠票。
現在你得到了小軒最近的公共交通出行記錄,你能幫他算算他的花費嗎?

解題思路
一道模擬題,顯而易見需要按照時間升序,因爲如果超過45分鐘就無效了。基於此應該想到可以利用優先隊列(或類似)的數據結構來維護,每次坐公交,在所有價值大於本次票價的優惠券中優先使用過期時間最早的(不用就過期了!),價值最低的優惠券。一旦票過期了,就刪掉,但是如果票只是價值不夠,那可能後面還會有用,所以還要壓回去。

代碼示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
struct Node{
	int ty;
	ll t,price;
	bool operator < (const Node& rhs) const{
		if(t == rhs.t) return price > rhs.price;
		return t > rhs.t;
	}
}rd[N];
int n;
priority_queue<Node> q;
stack<Node> s;
void solve(){
	sort(rd+1,rd+1+n);
	ll res = 0;
	for(int i = n;i >= 1;i--){
		if(!rd[i].ty){
			res += rd[i].price, q.push(rd[i]);
			continue; 
		}
		bool flag = true;
		while(q.size()){
			Node x = q.top(); q.pop();
			if(rd[i].t - x.t > 45) continue;
			if(x.price < rd[i].price){
				s.push(x); continue;
			}
			flag = false; break;
		}
		if(flag) res += rd[i].price;
		while(s.size()) q.push(s.top()), s.pop(); 
	}
	printf("%lld\n",res);
}
int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++)
		scanf("%d%d%d",&rd[i].ty,&rd[i].price,&rd[i].t);
	solve();
	return 0;
}

C.紀念品

題意描述
小偉突然獲得一種超能力,他知道未來 T 天 N 種紀念品每天的價格。某個紀念品 的價格是指購買一個該紀念品所需的金幣數量,以及賣出一個該紀念品換回的金幣數量。
 每天,小偉可以進行以下兩種交易無限次
任選一個紀念品,若手上有足夠金幣,以當日價格購買該紀念品;賣出持有的任意一個紀念品,以當日價格換回金幣。
 每天賣出紀念品換回的金幣可以立即用於購買紀念品,當日購買的紀念品也可以當日賣出換回金幣。當然,一直持有紀念品也是可以的。 
T 天之後,小偉的超能力消失。因此他一定會在第 T 天賣出所有紀念品換回金幣。 
小偉現在有 M 枚金幣,他想要在超能力消失後擁有儘可能多的金幣。

解題思路
本題思路可以根據數據範圍來想。這是一道動態規劃題。
剛開始想到的是建立一張全圖,但是後來發現節點數量不是 N 而是 NT ,並且總價值也不一定只能走一條路。
後來發現可以用動態規劃解決,首先設 f[x]: 第x天能夠賺到最多多少錢。那麼顯然f[1] = m,並且 f 是遞增的。重點在於狀態轉移,f[ i ] 肯定是由 f[ 1~i-1 ] 推出來的,因爲單獨一個 f[i-1] 無法表示所有狀態。
假設當前處於狀態 j (j < i),那麼將第 j 天物品的價格看作花費,第 i 天的物品價格看作價值,這就是一個完全揹包問題,揹包容量是當天的金幣數 f[j] 。
於是我們兩層循環O(T^2)來計算 f[i] ,而每次計算需要O(MN) 做完全揹包,但是實際測試效率還行,可能是數據太弱了。

代碼示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e2+10;
const int M = 1e4+10;
int t,n,m;
int val[N][N];
int f[N];	//第 x 天最多有多少錢
int tmp[M],mx;
void solve(){
	f[1] = m;
	for(int day = 2;day <= t;day++){
		for(int i = 1;i < day;i++){
			memset(tmp,0,sizeof tmp); mx = m;
			for(int j = 1;j <= n;j++){
				for(int v = val[i][j]; v <= f[i];v++){
					tmp[v] = max(tmp[v-val[i][j]]+val[day][j],tmp[v]);
					mx = max(mx,tmp[v]+f[i]-v);
				}
			}
			f[day] = max(f[day],mx);
		}
		f[day] = max(f[day],f[day-1]);
	}
	//for(int i = 1;i <= t;i++) printf("%d\n",f[i]);
	printf("%d\n",f[t]);
}
int main(){
	scanf("%d%d%d",&t,&n,&m);
	for(int i = 1;i <= t;i++)
		for(int j = 1;j <= n;j++) scanf("%d",&val[i][j]);
	solve();
	return 0;
}

D.加工零件

題意描述

凱凱的工廠正在有條不紊地生產一種神奇的零件,神奇的零件的生產過程自然也很 神奇。工廠裏有 𝑛 位工人,工人們從 1∼𝑛編號。某些工人之間存在雙向的零件傳送帶。保證每兩名工人之間最多隻存在一條傳送帶。        
如果 𝑥 號工人想生產一個被加工到第 𝐿(𝐿>1) 階段的零件,則所有與 𝑥 號工人 有傳送帶直接相連的工人,都需要生產一個被加工到第 𝐿 −1 階段的零件(但 𝑥 號工 人自己無需生產第 𝐿 −1 階段的零件)。        
如果 𝑥 號工人想生產一個被加工到第 1 階段的零件,則所有與 𝑥 號工人有傳送 帶直接相連的工人,都需要爲 𝑥 號工人提供一個原材料。        
軒軒是 1 號工人。現在給出 𝑞 張工單,第 𝑖 張工單表示編號爲 𝑎𝑖的工人想生產 一個第 𝐿𝑖階段的零件。軒軒想知道對於每張工單,他是否需要給別人提供原材料。他知道聰明的你一定可以幫他計算出來!

解題思路
經過推導可以發現,如果 a 號節點到 1 號節點(以下只用編號簡寫)之間存在一條小於 L 的路徑,且它們的差爲偶數(包括0),那麼 1 就要提供原材料。
但是問題在於 1 到 a 之間可能不止一條路徑,而 L 也不一定是奇或者偶,於是我們需要保存 a 到 1 的最短奇路徑最短偶路徑
在圖論中,如果邊權爲1,那麼求奇偶最短路徑的好方法就是用分層圖來做,具體做法是建立雙層圖,如果從起點不在同一層,則路徑一定是奇,否則一定爲偶;這樣做的好處是不用額外的輔助代碼,只要在建圖時多加兩條邊就行了。

代碼示例

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
const int M = 2*N;
typedef long long ll;
int n,m,q;
int head[N],edge[M],ver[M],nex[M] ,tot = 1;
void addEdge(int x,int y,int z){
	ver[++tot] = y; edge[tot] = z;
	nex[tot] = head[x]; head[x] = tot;
}
int getInt(){
	int res = 0;
	bool neg = false;
	char c = getchar();
	while(c != '-' && (c < '0' || c > '9')) c = getchar();
	if(c == '-') neg = true, c = getchar();
	while(c >= '0' && c <= '9') res = res*10+c-'0',c = getchar();
	return neg?-res:res;
}
queue<int> que;
int dis[N],vis[N];
bool solve(int a,int l){
	if(dis[a] <= l && !((dis[a]&1) ^ (l&1))) return true;
	if(dis[a+n] <= l && !((dis[a+n]&1) ^ (l&1))) return true;
	return false;
}
void SPFA(int s){
	que.push(s);
	memset(dis,0x3f,sizeof dis);
	dis[s] = 0;
	while(que.size()){
		int x = que.front(); que.pop();
		vis[x] = false;
		for(int i = head[x];i ;i = nex[i]){
			int y = ver[i], z = edge[i];
			if(dis[y] > dis[x]+z){
				dis[y] = dis[x]+z;
				if(!vis[y]) que.push(y),vis[y] = true;
			}
		}
	}
}
int main(){
	n = getInt(); m = getInt(); q = getInt();
	for(int i = 1,x,y;i <= m;i++){
		x = getInt(); y = getInt();
		addEdge(x,y+n,1); addEdge(y+n,x,1);
		addEdge(x+n,y,1); addEdge(y,x+n,1);
	}
	SPFA(1);
	//for(int i = 1;i <= n<<1;i++) cout << dis[i] << endl;
	for(int i = 1,a,l;i <= q;i++){
		a = getInt(); l = getInt();
		if(solve(a,l)) puts("Yes");
		else puts("No");
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章