[題解]ABC364 A~F

Sinktank發表於2024-07-28

A - Glutton Takahashi

給定\(n\)道菜,每道菜要麼是甜的(用sweet表示),要麼是鹹的(用salty表示)。必須按順序吃,如果連續吃到\(2\)個甜的菜,就會渾身難受吃不下去了。請問是否能吃完這些菜。

按題意模擬即可,只要前\(n-1\)個元素中有連續的sweet就輸出No

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int n,sweet;
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		string s;
		cin>>s;
		if(s=="sweet") sweet++;
		else sweet=0;
		if(sweet==2){
			cout<<"No\n";
			return 0;
		}
	}
	cout<<"Yes\n";
	return 0;
}

B - Grid Walk

給定一個地圖,有些位置有障礙,給定一個起始位置,再給定若干次操作。每次操作給定一個字元表示移動命令:U向上、D向下、L向左、R向右。對於每個命令,向指定方向移動\(1\)格,如果有障礙物則不移動。輸出最後位置。

按題意模擬即可。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y;
string s[60];
int main(){
	cin>>n>>m>>x>>y;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		s[i]=' '+s[i];
	}
	string t;
	cin>>t;
	for(auto i:t){
		int px=x,py=y;
		if(i=='U') x--;
		else if(i=='D') x++;
		else if(i=='L') y--;
		else if(i=='R') y++;
		if(x<1||x>n||y<1||y>m||s[x][y]=='#') x=px,y=py;
	}
	cout<<x<<" "<<y<<"\n";
	return 0;
}

C - Minimum Glutton

給定\(n\)道菜,每道菜有自己的甜度\(a_i\),鹹度\(b_i\)。可以順序隨意地吃這些菜,如果中途攝入的總甜度\(>X\)或者 總鹹度\(>Y\)就會渾身難受吃不下去了。請問最少吃多少道菜?

為了儘可能快地結束用餐,我們應該考慮兩種情況:

  • 按甜度從大到小排序,記錄下最多吃多少盤不會超標,記為\(a\)
  • 按鹹度從大到小排序,記錄下最多吃多少盤不會超標,記為\(b\)

則答案就是\(\min(\max(a,b)+1,n)\)。為什麼還要\(+1\)呢?因為\(a,b\)的含義是“最多吃多少盤不會超標”,那麼其實應該再多吃一盤才超標的。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct dish{
	int x,y;
}a[200010];
int n,x,y,ans=INT_MAX;
signed main(){
	cin>>n>>x>>y;
	for(int i=1;i<=n;i++) cin>>a[i].x;
	for(int i=1;i<=n;i++) cin>>a[i].y;
	sort(a+1,a+1+n,[](dish a,dish b){
		return a.x>b.x;//按甜度從大到小
	});
	int sum=0,i;
	for(i=1;i<=n;i++){
		sum+=a[i].x;
		if(sum>x) break;
	}
	ans=min(ans,i);
	sort(a+1,a+1+n,[](dish a,dish b){
		return a.y>b.y;
	});
	sum=0;
	for(i=1;i<=n;i++){
		sum+=a[i].y;
		if(sum>y) break;
	}
	ans=min(ans,i);
	if(ans==n+1) ans=n;
	cout<<ans<<"\n";
	return 0;
}

D - K-th Nearest

給定數軸上的\(n\)個點\(A_1\sim A_n\)\(q\)次詢問,每次給定一個點\(B\)和一個\(k\),詢問這\(n\)個點中到\(B\)\(k\)遠的點,到\(B\)的距離是多少。

實際上,每次詢問的答案,就是滿足\([B-dis,B+dis]\)中,\(A_i\)的個數\(\ge k\)的最小\(dis\),所以說可以二分\(dis\)

判斷\([l,r]\)內有多少個\(A_i\),仍然是一個二分(首先需要將\(A\)從小到大排序):

  • 找到滿足\(A_i\ge l\)的最小\(i\),記為\(x\)
  • 找到滿足\(A_i>r\)的最小\(i\),記為\(y\)

答案就是\(y-x\)。因為根據定義,滿足\(A_i\le r\)的最大\(i\)即為\(y-1\)。而我們的答案就是\(x\)\(y-1\)之間的元素個數,即為\((y-1)-x+1\ \ =\ \ y-x\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;
int n,q,a[N];
bool cmp(int a,int b){return a<b;}
int cntrange(int l,int r){//[l,r]有多少點A
	int lp=lower_bound(a+1,a+1+n,l)-a;
	int rp=upper_bound(a+1,a+1+n,r)-a-1;
	return rp-lp+1;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+1+n,cmp);
	while(q--){
		int b,k;
		cin>>b>>k;
		int l=0,r=2e8+10;
		while(l<r){
			int mid=(l+r)>>1;
			if(cntrange(b-mid,b+mid)>=k) r=mid;
			else l=mid+1;
		}
		cout<<l<<"\n";
	}
	return 0;
}

E - Maximum Glutton

給定\(n\)道菜,每道菜有自己的甜度\(a_i\),鹹度\(b_i\)。可以順序隨意地吃這些菜,如果中途攝入的總甜度\(>X\)或者 總鹹度\(>Y\)就會渾身難受吃不下去了。請問最多吃多少道菜?

首先我們可以發現這是一個二位費用的揹包問題,每個物品價值都為\(1\)。當然揹包得出的最大值\(x\)並不是答案,真正的答案是\(\min(x+1,n)\),原因見C題。

設計出\(f[i][j][k]\)表示前\(i\)道菜,甜度和鹹度的最大限度分別是\(j,k\),所能吃下的最多數量(即最大價值)。然後就可以像揹包一樣轉移:
\(f[i][j][k]=\begin{cases} \max(f[i-1][j][k],f[i-1][j-a[i]][k-b[i]]+1)&j\ge a[i]\text{ and }k\ge b[i]\\ f[i-1][j][k]&\text{otherwise} \end{cases}\)

但是!這道題的\(X,Y\)達到了\(10^5\),這麼設計狀態,就算能滾掉第\(1\)維也會超限。

我們已經想到揹包了,或許就可以想到“超大揹包問題”,即物品體積範圍很大、而價值範圍很小的揹包問題。我們用到了一個技巧來解決這個問題——交換鍵和值的位置,具體來說:

  • \(f[i][j]\)表示前\(i\)個物品,總體積最大為\(j\)最大價值
    變成了
  • \(f[i][j]\)表示前\(i\)個物品,總價值正好為\(j\)最小容量

放在這個題中,我們同樣可以這樣最佳化。互換一下鍵和值,用\(f[i][j][k]\)表示前\(i\)道菜,正好選\(k\)道菜吃(即總價值正好為\(k\)),使得總甜度正好為\(j\)的最小鹹度。

注意最佳化後變成了“正好裝滿”的揹包問題,所以初始化除了\(f[i][0][0]=0\)以外都要置為\(+\infty\)

轉移方程:
\(f[i][j][k]=\begin{cases} \min(f[i-1][j][k],f[i-1][j-a[i]][k-1]+b[i])&j\ge a[i]\\ f[i-1][j][k]&\text{otherwise} \end{cases}\)

轉移完成後,將滿足\(f[n][j][k]\le y\)的最大\(k\)記為\(x\),答案就是\(\min(x+1,n)\)

同樣這裡的\(f\)可以滾掉第\(1\)維(注意倒序列舉),我沒弄(^^;

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define N 90
#define W 10010
using namespace std;
int n,a[N],b[N],x,y;
int f[N][W][N];
signed main(){
	cin>>n>>x>>y;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
	memset(f,0x3f,sizeof f);
	for(int i=0;i<=n;i++) f[i][0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=x;j++){
			for(int k=1;k<=n;k++){
				if(j>=a[i]) f[i][j][k]=min(f[i-1][j][k],f[i-1][j-a[i]][k-1]+b[i]);
				else f[i][j][k]=f[i-1][j][k];
			}
		}
	}
	for(int k=n;k;k--){
		for(int j=1;j<=x;j++){
			if(f[n][j][k]<=y){
				cout<<min(k+1,n);
				return 0;
			}
		}
	}
	cout<<1;
	return 0;
}

F - Range Connect MST

給定\(n+q\)個節點,一開始點之間都沒有邊。接下來對於\(i\in[1,q]\),執行下面的操作:

  • 對於\(j\in[l_i,r_i]\),在\(i+n\)\(j\)之間連一條權值為\(c_i\)的邊。

所有操作完成後,如果該圖不連通,輸出-1,否則請輸出它的最小生成樹權值之和。
保證\(1\le l_i\le r_i\le n\)

如果暴力模擬的話會超時,考慮更好的做法。

我們為了方便,把\([n+1,n+q]\)\(q\)個節點叫做紅色節點,把\([1,n]\)\(n\)個節點叫做藍色節點。

這道題的突破口,就是“對於每個\(i\)處理完後,互相連通的藍色點,編號都是連續的”。

那麼很容易想到一個貪心的思路:把紅色節點按照\(c\)的值從小到大排序,然後把連通的藍色點寫成區間的形式(比如初始狀態就是\([1,1],[2,2],[3,3],[4,4],[5,5]\)),考慮某個紅色節點時,就看這個紅色節點的\([l,r]\)跨越了多少個區間,跨越多少個區間,該節點就需要用去多少條邊。然後把跨越的所有區間合併成一個大區間。

用某種資料結構,動態維護每個區間即可。

這裡使用set,儲存每個區間的右端點。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define N 200010
using namespace std;
int n,q,ans;
struct bl{
	int l,r,c;
}a[N];
bool cmp(bl a,bl b){return a.c<b.c;}
set<int> s;
signed main(){
	cin>>n>>q;
	for(int i=1;i<=n;i++) s.insert(i);
	for(int i=1;i<=q;i++)
		cin>>a[i].l>>a[i].r>>a[i].c;
	sort(a+1,a+1+q,cmp);
	for(int i=1;i<=q;i++){
		auto lp=s.lower_bound(a[i].l);
		int cnt=1;
		for(auto it=lp;it!=s.end()&&(*it)<a[i].r;it=s.erase(it))
			cnt++;
		ans+=cnt*a[i].c;
	}
	if(s.size()==1) cout<<ans<<"\n";
	else cout<<"-1\n";
	return 0;
}

後話

\(34\)分鐘打到了D題,然後對著E磕了半天沒做出來……想到超大揹包了,但沒有提煉出“鍵值交換”的精髓來,當時想的做法挺離譜的
F題只是過了一眼,完全沒想到其實思路還是挺簡單的,程式碼也很短。如果調整一下做題策略或許會更好

相關文章