Educational Codeforces Round 172 (Rated for Div. 2)題解記錄(A~D)

长皆發表於2024-12-03

比賽連結:https://codeforces.com/contest/2042
這場爆了,卡死在C題了,QWQ.卡題得跳題啊!!!

A.Greedy Monocarp

題面:
\(n\) 個箱子,第 \(i\) 個箱子最初包含 \(a_i\) 枚硬幣。對於每個箱子,你可以選擇任意非負數(0或更大)的硬幣新增到該箱子中,有一個約束條件:所有箱子中的硬幣總數必須變成 至少 \(k\)

在你向箱子中新增硬幣後,貪婪的 Monocarp 來了,他想要這些硬幣。他會一個接一個地拿走箱子,由於他很貪婪,他總是會選擇硬幣最多的箱子。當 Monocarp 拿走的箱子中的硬幣總數 至少為 \(k\) 時,他將停止。

你希望 Monocarp 拿走儘可能少的硬幣,因此你需要以這樣的方式向箱子中新增硬幣:當 Monocarp 停止拿走箱子時,他將擁有 恰好 \(k\) 枚硬幣。計算你必須新增的最小硬幣數。
輸入:
\(t\) 個測試用例,每個測試用例包含兩行:
第一行包含兩個整數 \(n\)\(k\) (\(1 \le n \le 50\); \(1 \le k \le 10^7\));
第二行包含 \(n\) 個整數 \(a_1, a_2, \dots, a_n\) (\(1 \le a_i \le k\))。
對於每個測試用例,計算需要新增的最小硬幣數,使得 Monocarp 拿走的箱子中的硬幣總數恰好為 \(k\)
輸出:
對於每個測試用例,列印一個整數 —— 你需要新增的最小硬幣數,以便當 Monocarp 停止拿箱子時,他恰好擁有 \(k\) 枚硬幣。可以證明,在問題的約束條件下,總是可能實現的。
樣例:
4
5 4
4 1 2 3 2
5 10
4 1 2 3 2
2 10
1 1
3 8
3 3 3
————————
0
1
8
2
思路:發現\(a_i<=k\),所以順著題意,從大到小拿,如果sum最後要溢位,break然後答案為k-sum,如果沒溢位最後答案還是k-sum

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
//#include<bits/stdc++.h>
#include <unordered_map>
#define ll                                   long long
#define lowbit(x) (x & -x)
#define endl "\n"//                           互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
//const ll p=rnd()%mod;
ll ksm(ll x, ll y)
{
	ll ans = 1;
	while (y)
	{
		if (y & 1)
		{
			ans = ans % mod * (x % mod) % mod;
		}
		x = x % mod * (x % mod) % mod;
		y >>= 1;
	}
	return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
	if (y == 0)
		return x;
	else
		return gcd(y, x % y);
}
void fio()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
}
ll a[250000];
int main()
{
	fio();
	ll t;
	cin>>t;
	while(t--)
	{
		ll n,m;
		cin>>n>>m;
		for(ll i=1;i<=n;i++)cin>>a[i];
		sort(a+1,a+1+n);
		ll sum=0;
		ll ans=0;
		for(ll i=n;i>=1;i--)
		{
			if(sum+a[i]<=m)
			sum+=a[i];
			else 
			{
				ans=m-sum;
				break;
			}
		}
		if(ans==0&&sum<=m)ans=m-sum;
		cout<<ans<<endl;
	}
	return 0;
}

B.Game with Colored Marbles

題面:
Alice 和 Bob 玩一個遊戲。有 \(n\) 個彈珠,第 \(i\) 個彈珠的顏色是 \(c_i\)。玩家輪流進行遊戲;Alice 先開始,然後是 Bob,接著是 Alice,然後是 Bob,以此類推。

在他們的回合中,玩家 必須 從剩下的彈珠中拿走 一個 並將其從遊戲中移除。如果沒有彈珠剩下(所有的 \(n\) 個彈珠都被拿走了),遊戲結束。

遊戲結束時,Alice 的得分計算如下:

她為每個顏色 \(x\) 獲得 \(1\) 分,只要她至少拿走了一個該顏色的彈珠;
此外,如果她拿走了所有顏色 \(x\) 的彈珠,她還會為每個顏色 \(x\) 額外獲得 \(1\) 分(當然,只有遊戲中出現的顏色才會被考慮)。
例如,假設有 \(5\) 個彈珠,它們的顏色是 \([1, 3, 1, 3, 4]\),遊戲進行如下:Alice 拿走第 \(1\) 個彈珠,然後 Bob 拿走第 \(3\) 個彈珠,然後 Alice 拿走第 \(5\) 個彈珠,然後 Bob 拿走第 \(2\) 個彈珠,最後 Alice 拿走第 \(4\) 個彈珠。那麼,Alice 獲得 \(4\) 分:\(3\) 分是因為她至少有一個顏色為 \(1\)\(3\)\(4\) 的彈珠,以及 \(1\) 分是因為她拿走了所有顏色為 \(4\) 的彈珠。注意,這種策略並不一定對兩個玩家都是最優的。

Alice 想要在遊戲結束時最大化她的得分。Bob 想要最小化它。兩個玩家都以最優的方式進行遊戲(即 Alice 選擇一個策略,允許她獲得儘可能多的分數,而 Bob 選擇一個策略,最小化 Alice 可以獲得的分數)。

計算遊戲結束時 Alice 的得分。
輸入:
第一行包含一個整數 \(t\) (\(1 \le t \le 1000\)) —— 測試用例的數量。

每個測試用例由兩行組成:

第一行包含一個整數 \(n\) (\(1 \le n \le 1000\)) —— 彈珠的數量;
第二行包含 \(n\) 個整數 \(c_1, c_2, \dots, c_n\) (\(1 \le c_i \le n\)) —— 彈珠的顏色。
額外的輸入約束:所有測試用例中 \(n\) 的總和不超過 \(1000\)
輸出:
對於每個測試用例,列印一個整數 —— 假設兩個玩家都以最優策略進行遊戲,Alice 在遊戲結束時的得分。
樣例:
3
5
1 3 1 3 4
3
1 2 3
4
4 4 4 4
————————
4
4
1
思路:首先對於Alice,她優先拿只有一個的數是最好的,對於Bob也是同理,不妨計這類數的個數為cnt,然後對於存在個數大於等於2的其他數,顯然不論Alice先後手,她都一定能有一個,不妨即為cn,所以最後答案為(cnt+1)/2*2+cn.這裡直接用了優先佇列寫

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
//#include<bits/stdc++.h>
#include <unordered_map>
#define ll                                   long long
#define lowbit(x) (x & -x)
#define endl "\n"//                           互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
//const ll p=rnd()%mod;
ll ksm(ll x, ll y)
{
	ll ans = 1;
	while (y)
	{
		if (y & 1)
		{
			ans = ans % mod * (x % mod) % mod;
		}
		x = x % mod * (x % mod) % mod;
		y >>= 1;
	}
	return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
	if (y == 0)
		return x;
	else
		return gcd(y, x % y);
}
void fio()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
}
ll a[250000];
int main()
{
	fio();
	ll t;
	cin>>t;
	while(t--)
	{
		ll n;
		cin>>n;	
		map<ll,ll>q;
		set<ll>f;
		for(ll i=1;i<=n;i++)
		{
			ll x;
			cin>>x;
			q[x]++;
			f.insert(x);
		}
		priority_queue<pair<ll,ll>,vector<pair<ll,ll>>,greater<pair<ll,ll>>>op;
		for(auto j:f)
		{
			op.push({q[j],j});
		}
		map<ll,ll>c;
		ll j=0;
		ll ans=0;
		while(!op.empty())
		{
			if(j==0)
			{
				if(op.top().first==1)
				{
					ans+=2;
					j=1;
				}
				else if(op.top().first%2==0)
				{
					j=0;
					ans++;
				}
				else 
				{
					j=1;
					ans++;
				}
				op.pop();
			}
			else 
			{
				if(op.top().first==1)
				{
					j=0;
				}
				else if(op.top().first%2==0)
				{
					j=1;
					ans++;
				}
				else 
				{
					j=0;
					ans++;
				}
				op.pop();
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

C.Competitive Fishing

題面:
Alice 和 Bob 參加了一個釣魚比賽!總共釣到了 \(n\) 條魚,編號從 \(1\)\(n\)(魚越大,編號越大)。這些魚中有些是 Alice 釣到的,有些是 Bob 釣到的。

他們的表現將根據以下方式評估。首先,選擇一個整數 \(m\),並將所有魚分成 \(m\) 個 非空 組。第一組應該包含一些(至少一條)最小的魚,第二組應該包含一些(至少一條)接下來最小的魚,以此類推。每條魚應該恰好屬於一個組,每個組應該是魚的連續子段。注意,這些組是按照確切的順序編號的;例如,第二組的魚不能比第一組的魚小,因為第一組包含最小的魚。

然後,將根據組索引為每條魚分配一個值:第一組的每條魚獲得等於 \(0\) 的值,第二組的每條魚獲得等於 \(1\) 的值,以此類推。因此,第 \(i\) 組的每條魚獲得等於 \((i-1)\) 的值。

每個參賽者的分數僅僅是參賽者釣到的所有魚的總價值。

你希望 Bob 的分數至少超過 Alice 的分數 \(k\) 分。你必須將魚分成的最小組數(\(m\))是多少?如果不可能,你應該報告這一點。
輸入:
第一行包含一個整數 \(t\) (\(1 \le t \le 10^4\)) —— 測試用例的數量。

每個測試用例的第一行包含兩個整數 \(n\)\(k\) (\(2 \le n \le 2 \cdot 10^5\); \(1 \le k \le 10^9\))。

每個測試用例的第二行包含一個字串,由恰好 \(n\) 個字元組成。第 \(i\) 個字元是 '0'(表示第 \(i\) 條魚被 Alice 釣到)或 '1'(表示第 \(i\) 條魚被 Bob 釣到)。

額外的輸入約束:所有測試用例中 \(n\) 的總和不超過 \(2 \cdot 10^5\)
輸出:
對於每個測試用例,列印一個整數 —— 你必須將魚分成的組數的最小值;如果不可能,則列印 -1。
樣例:
7
4 1
1001
4 1
1010
4 1
0110
4 2
0110
6 3
001110
10 20
1111111111
5 11
11111
——————————
2
-1
2
-1
3
4
-1
思路:令人痛心棘手的題,這裡如果往二分想了就G了。這裡證明下二分錯誤性!,如果假設二分割槽間個數越多,值越大,你就會發現如果最後一個為0,他其實被前面一點的1抵消更優,而你二分割槽間越小,值越大,只有一個區間時,答案為0,也不對,故這題二分不對。其實這種題應該用一種方法解決,自己總結為層次拆分法。顯然發現增加一個斷點,右邊區間的值會再之前的基礎上變大1.這就可以發現,如果我把每次操作視作增加一條從右端點連的線(選一個沒選過的點作為字尾合左端點),如下圖,就會發現你所有操作最後就是要形成這種圖。既然我要形成這種圖,而且我還優先滿足k,那為什麼不先選字尾合大的地方。於是直接求個字尾和,然後出了第一個點,其他排個序,每次貪心選最大的就好了。
例如

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
//#include<bits/stdc++.h>
#include <unordered_map>
#define ll                                   long long
#define lowbit(x) (x & -x)
#define endl "\n"//                           互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
//const ll p=rnd()%mod;
ll ksm(ll x, ll y)
{
	ll ans = 1;
	while (y)
	{
		if (y & 1)
		{
			ans = ans % mod * (x % mod) % mod;
		}
		x = x % mod * (x % mod) % mod;
		y >>= 1;
	}
	return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
	if (y == 0)
		return x;
	else
		return gcd(y, x % y);
}
void fio()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
}
ll pre[250000];
int main()
{
	fio();
	ll t;
	cin>>t;
	while(t--)
	{
		ll n,k;
		cin>>n>>k;
		string f;
		cin>>f;
		pre[f.size()]=0;
		ll ans=1;
		for(ll i=f.size()-1;i>=0;i--)
		{	
			pre[i]=pre[i+1]+(f[i]=='0'?-1:1);
		}
		if(k==0)
		{
			cout<<1<<endl;
			continue;
		}
		if(n!=1)
		{
		sort(pre+1,pre+n);
		for(ll i=n-1;i>=1;i--)
		{
			if(k<=0)
			break;
			k-=pre[i],ans++;
		}
		}
		if(k>0)cout<<-1<<endl;
		else cout<<ans<<endl;
	}
	return 0;
}

D. Recommendations

題面:
假設你在某個音訊流媒體服務工作。該服務有 \(n\) 個活躍使用者和 \(10^9\) 首曲目供使用者收聽。使用者可以喜歡曲目,基於喜好,服務應該向他們推薦新的曲目。

曲目編號從 \(1\)\(10^9\)。結果表明,第 \(i\) 個使用者喜歡的曲目形成了一個區間 \([l_i, r_i]\)

如果第 \(j\) 個使用者(\(j \neq i\))喜歡第 \(i\) 個使用者喜歡的所有曲目(並且,可能還包括一些其他的曲目),則稱使用者 \(j\) 是第 \(i\) 個使用者的預測者。

另外,如果一首曲目尚未被第 \(i\) 個使用者喜歡,但被第 \(i\) 個使用者的所有預測者喜歡,則稱這首曲目被強烈推薦給第 \(i\) 個使用者。

計算每個使用者 \(i\) 的強烈推薦曲目數量。如果一個使用者沒有任何預測者,則列印該使用者為 \(0\)
輸入:
第一行包含一個整數 \(t\) (\(1 \le t \le 10^4\)) —— 測試用例的數量。接下來是 \(t\) 個測試用例。

每個測試用例的第一行包含一個整數 \(n\) (\(1 \le n \le 2 \cdot 10^5\)) —— 使用者的數量。

接下來的 \(n\) 行每行包含兩個整數 \(l_i\)\(r_i\) (\(1 \le l_i \le r_i \le 10^9\)) —— 第 \(i\) 個使用者喜歡的曲目區間。

額外的輸入約束:所有測試用例中 \(n\) 的總和不超過 \(2 \cdot 10^5\)
輸出:
對於每個測試用例,列印 \(n\) 個整數,其中第 \(i\) 個整數是第 \(i\) 個使用者的強烈推薦曲目數量(如果沒有預測者,則為 \(0\))。
樣例:
4
3
3 8
2 5
4 5
2
42 42
1 1000000000
3
42 42
1 1000000000
42 42
6
1 10
3 10
3 7
5 7
4 4
1 2
——————
0
0
1
999999999
0
0
0
0
0
2
3
2
4
8
思路:題目意思其實是求一個使用者被其他使用者完全包圍的範圍最小交區間的長度-使用者本身範圍的長度。不妨想想對於一個使用者(l1,r1)來講,如果所有區間(設為l,r)l小於他的都已經選出來了,那麼此時我就只要去找到這些使用者中r大於r1的最大l(\(l_{max}\)),然後保留滿足(r>=r1)的最小\(r_{min}\)就好了,此時\(r_{min}\)-\(l_{max}\)+1-(r1-l1+1)就是答案。所以排個序(注意l相等時得r大的排在前面,因為他也是個約束),然後使用線段樹去維護右邊界(下標,離散化),線段樹的值為所有已經遍歷過的且剛好對應此r的最大l。然後用個set去儲存之前遍歷過的所有區間的右邊界,每次問這個點時就先二分下,看下有沒有符合的右值,沒有答案自然為0,否則答案用線段樹問下此時的最大左邊界,計算得出答案。注意得用map記錄相同左端點時的右端點,如果出現次數大於二,得把之前的答案設為0.最後答案就這樣得出了,題目實現細節挺多的。

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
//#include<bits/stdc++.h>
#include <unordered_map>
#define ll                                   long long
#define lowbit(x) (x & -x)
#define endl "\n"//                           互動題記得刪除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
// const ll p=rnd()%mod;
const ll maxn=2e5+15;
ll ksm(ll x, ll y)
{
	ll ans = 1;
	while (y)
	{
		if (y & 1)
		{
			ans = ans % mod * (x % mod) % mod;
		}
		x = x % mod * (x % mod) % mod;
		y >>= 1;
	}
	return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
	if (y == 0)
		return x;
	else
		return gcd(y, x % y);
}
void fio()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
}
struct s
{
	ll l,r,v;
}p[maxn<<2];
void build(ll i,ll l,ll r)
{
	p[i].l=l,p[i].r=r;
	p[i].v=0;
	if(l==r)
	return ;
	build(i<<1,l,(l+r)>>1);
	build(i<<1|1,(l+r>>1)+1,r);
}
void xg(ll i,ll l,ll r,ll v)
{
	if(p[i].l==l&&p[i].r==r)
	{
		p[i].v=max(v,p[i].v);
		return ;
	}
	ll mid=(p[i].l+p[i].r)>>1;
	if(l<=mid)
	xg(i<<1,l,min(mid,r),v);
	if(r>=mid+1)
	xg(i<<1|1,max(mid+1,l),r,v);
	p[i].v=max(p[i<<1].v,p[i<<1|1].v);
}
ll q(ll i,ll l,ll r)
{
	ll ans=0;
	if(p[i].l==l&&p[i].r==r)
	{
		ans=p[i].v;
		return ans;
	}
	ll mid=(p[i].l+p[i].r)>>1;
	if(l<=mid)
	ans=max(ans,q(i<<1,l,min(mid,r)));
	if(r>=mid+1)
	ans=max(ans,q(i<<1|1,max(mid+1,l),r));
	return ans;
}
struct f1
{
	ll z,id;
};
vector<f1>g[250000];
ll ans[250000];
bool zm(f1 x,f1 y)
{
	return x.z>y.z;
}
int main()
{
	fio();
	ll t;
	cin>>t;
	while(t--)
	{
		ll uop=0;
		set<ll>l,r;
		map<ll,ll>mp,mp1;
		ll n;
		cin>>n;
		ll ko=0;
		for(ll i=1;i<=n;i++)
		{
			ll x,y;
			cin>>x>>y;
			l.insert(x);
			if(mp1[x]==0)
			{
				ko++;
				mp1[x]=ko;
				g[ko].clear();
			}
			g[mp1[x]].push_back({y,i});
			r.insert(y);
		}
		for(auto j:l)
		{
			sort(g[mp1[j]].begin(),g[mp1[j]].end(),zm);
		}
		ll cnt=0;
		for(auto j:r)
		{
			cnt++;
			mp[j]=cnt;
		}
		build(1,1,cnt);
		//xg(1,2,2,2);
		//cout<<q(1,1,2)<<endl;
		r.clear();
		map<ll,ll>mu;
		for(auto j:l)
		{
			mu.clear();
			ll u=mp1[j];
			for(auto z:g[u])
			{
				auto d=r.lower_bound(z.z);
				if(mu[z.z]>0)
				{
					ans[mu[z.z]]=0;
					ans[z.id]=0;
				}
				else 
				{
				if(d==r.end())
				{
					ans[z.id]=0;
				}
				else 
				{
					ll k=q(1,mp[*d],cnt);
					ans[z.id]=j-k+*d-z.z;
				}
				}
				mu[z.z]=z.id;
				xg(1,mp[z.z],mp[z.z],j);
				r.insert(z.z);
			}
		}
		for(ll i=1;i<=n;i++)
		cout<<ans[i]<<endl;
	}
}

相關文章