1. 程式人生 > >CF Round #521 (Div. 3)

CF Round #521 (Div. 3)

ACM題集:https://blog.csdn.net/weixin_39778570/article/details/83187443
題目連結:http://codeforces.com/contest/1077
官方題解:https://codeforces.com/blog/entry/63274

A題

簡單的奇偶問題

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll t,a,b,k;
int main(){
	scanf("%I64d",&t);
	while(t--){
		scanf(
"%I64d%I64d%I64d",&a,&b,&k); ll ans =0; if(k&1){ ans += a*(k/2+1)-b*(k/2); }else{ ans += (a-b)*(k/2); } cout<<ans<<endl; } return 0; }

B題

求最少關掉幾棧燈等使得大家互不打擾,開關問題

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std; int n,a[105]; int main(){ cin>>n; fo(i,1,n)cin>>a[i]; int ans = 0; fo(i,3,n){ if(a[i-2]==1&&a[i-1]==0&&a[i]==1){ ans++; a[i]=0; } } cout<<ans; }

C題

題意:刪掉序列中的一個數,使得這個數列成為Good array(有一個數等於其餘的數的和)
解法:刪掉一個數變成good array
只有兩種刪法,排序後,刪掉最後一個數,然後檢視前n-2個數和是否等於a[n-1]
刪掉中間的某個數,檢視剩餘數的和是否等於最後一個數
刪掉的數為sum[n-1] - a[n], 計算該數有多少個就行(二分一下或者列舉)

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
struct node{
	int val,idx;
	bool operator < (const node &a)const{
		return val<a.val;
	}
}a[200005];
int n;
ll sum[200005]; // 千萬注意 int 1e10就gg了 
vector<int> ver;
int my_upper_bound(int x){
	int L=1,R=n-1,mid,ans=n; // ans設定比上界大一 
	while(L<=R){
		mid=(L+R)>>1;
		if(a[mid].val>x){
			ans = mid;
			R = mid-1;
		}else{
			L = mid+1;
		}
	}
	return ans;
}
int my_lower_bound(int x){
	int L=1,R=n-1,mid,ans=n;// ans設定比上界大一 
	while(L<=R){
		mid=(L+R)>>1;
		if(a[mid].val>=x){
			ans = mid;
			R = mid-1;
		}else{
			L = mid+1;
		}
	}
	return ans;
}
void solve(){
	fo(i,1,n){
		sum[i] = sum[i-1]+a[i].val;
	}
	// 刪掉非最後一個數,使得和等於最後一個數 
	ll t = sum[n-1] - a[n].val;
	if(t>0&&t<=a[n].val){  // 這個t一定要注意!!!要在查詢範圍內 
		int t2 = my_upper_bound(t);
		int t1 = my_lower_bound(t);
		fo(i,t1,t2-1){
			ver.push_back(a[i].idx); 
		}
//		fo(i,1,n-1)if(a[i].val==t)ver.push_back(a[i].idx); 
	}
	if(a[n-1].val==sum[n-2])ver.push_back(a[n].idx); // 刪掉最後一個數 
	printf("%d\n",ver.size());
	for(int i=0; i<ver.size(); i++){
		printf("%d%c",ver[i],i==ver.size()-1?'\n':' ');
	}
}
int main(){
	scanf("%d",&n);
	fo(i,1,n){
	//	scanf("%d",&a[i].val);
		a[i].val = read(); 
		a[i].idx=i;
	}
	sort(a+1,a+1+n);
	if(n>2)solve();
	else puts("0");
	return 0;
}

C題別人做法

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,a[200005];
map<int,int> mp; // 真的變慢 
vector<int> ver;
ll sum;
int main(){
	scanf("%d",&n);
	fo(i,1,n)scanf("%d",&a[i]),sum+=a[i],mp[a[i]]++;
	ll t;
	fo(i,1,n){
		t = sum - a[i];// 刪掉一個數 
		if(t&1)continue;
		t>>=1; // 一半 
		if(t>0&&t<=1000000){ // 在合法範圍內
		//	if((t!=a[i]&&mp[t]>=1) || (t==a[i]&&mp[t]>=2)){
			if(t!=a[i]&&mp[t]>=1 || t==a[i]&&mp[t]>=2){ // 找一個數等於一半的,注意可能剛好一半等於刪掉的那個數(則至少需要兩個) 
				ver.push_back(i);
			} 
		}
	}
	printf("%d\n",ver.size());
	for(int i:ver)printf("%d ",i);
	return 0;
}

D題

題意:n個數找k個數,要求這k個數出現的次數最少為times求times的最大值
解法:二分答案,即二分cut time 找到最大的cut time 更新答案

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
struct node{
	int val,idx;
	// 按出現次數排序 
	bool operator < (const node &a)const{
		if(a.idx!=idx)return idx<a.idx;
		else{
			return val>a.val; // 出現次數相同按值排序 
		}
	}
}a[200005];
int n,k,mx=-1;
map<int,int> mp;
vector<int> ans;
bool ok(int mid){ // 重複次數 
	// 計算重複段(可剪次數)大於等於mid有幾個數(包含重複數字)
	// 例如: mid=2。  1,1,1,1,1,1  有3個1滿足 
	int t = 0,last = -1,loo=1;//重複次數 
	for(int i=n; i>=1;){
		if(a[i].val==last)loo++; // 和上一個重複了
		else loo=1,last=-1; // 新的數出現 
		if(a[i].idx>=mid*loo){
			last = a[i].val;
			t++;
			i-=mid;
		}else i--;
	}
	return t>=k; // 是否有大於等於k個位元組 
}
// 獲取答案,和ok函式差不多 
void get(int mid){
	int t = 0,last = -1,loo=1;
	for(int i=n; i>=1;){
		if(a[i].val==last)loo++;
		else loo=1,last=-1;
		if(a[i].idx>=mid*loo){
			last = a[i].val;
			ans.push_back(a[i].val);
			t++;
			i-=mid;
		}else i--;
		if(t==k)break;
	}
}
void solve(){
	int L=1,R=mx,mid,ANS=1;
	// 二分最大重複段 
	while(L<=R){
		mid = (L+R)>>1;
		if(ok(mid)){
			L = mid+1;	
			ANS = mid;		
		}else R = mid-1; 
	} 
	get(ANS);
	int t = ans.size();
	for(int i=0;i<t; i++){
		printf("%d%c",ans[i],i==t-1?'\n':' ');
	}
}
int main(){
	cin>>n>>k;
	fo(i,1,n){
		scanf("%d",&a[i].val);
		mp[a[i].val]++;
	}
	fo(i,1,n){
		a[i].idx = mp[a[i].val]; // 出現次數 
		mx = max(mx,a[i].idx);
	}
	sort(a+1,a+1+n); // 先對次數排序
//	for(int i=1;i<=n; i++){
//		printf("%d%c",a[i].val,i==n?'\n':' ');
//	}
	solve();
}

D題其他解法

#include<bits/stdc++.h>
using namespace std;
int b[300000];
int a[300000];
int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		b[x]++;
	}
	priority_queue<pair<int,int> >pq,res;
	for(int i=0;i<300000;i++)
	{
		if(b[i])pq.push({b[i],i});
	}
	for(int i=0;i<k;i++)
	{
		cout<<pq.top().second<<" "; //大根堆,堆頂必是答案
		pair<int,int>h=pq.top();
		pq.pop();
		a[h.second]++;
		h.first=b[h.second]/(a[h.second]+1); // 要出現 a[h.second]+1次的話每個數只能被cutb[h.second]/(a[h.second]+1)次 
		pq.push(h);// 剩下的次數,進入排序了 
	}
}

E題

題意:一堆數中找到和最大的倍增序列
解法:我們可以很容易知道,這題的數值是沒有作用的,因為每個數只能在在以個contenes中使用,所以我們只用考慮每個數出現的次數,得到一個序列
我一開始的做法
按次數排序 ,然後對值去重,
列舉每個每個數做為起點選擇話題,再列舉1~這個數的出現次數,做為倍增序列 的第一個起點數
然後算。。。。然後T了
上面的思想是從最小的開始列舉起
我們可以做一步優化,從最大的遞推算出起點 val[i] = min(val[i+1]/2, ver[i]) ,最後面的val設定為最大
也就是說,我們每一步都可以知道倍增序列的起點,和長度,
長度為i,起點為val[i]的倍增序列的和為
a[i] * ((1<<i)-1)

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
const int maxn = 2e5+5;
int n;
map<int,int>mp;
vector<int> ver;
int val[maxn];
int calc(){
	n = ver.size();
	int ans = val[n-1] = ver[n-1]; // 取最大次數做為第一次比賽,僅此一次比賽 
	for(int i=n-2; i>=0; i--){
		// val[i]作為第一次比賽 
		val[i] = min(val[i+1]/2, ver[i]); // 保證val[i] 與後面的數可以形成 val[i],2*val[i],4*val[i]...這樣的序列 
		if(val[i]==0) break; // 不會再更新答案了 
		// val[i],val[i+1]...val[n-1]共 n-i個數, 1+2+4+8... = 2^(n-i)-1 
		int t =  val[i] * ((1<<(n-i))-1);
		ans = max(ans,t);
	} 
	return ans;
}
int main(){
	cin>>n;int a;
	// 值去重,次數排序 
	fo(i,1,n){
		scanf("%d",&a);
		mp[a]++; // 去重 
	}	
	for(auto it : mp) ver.push_back(it.second);
	sort(ver.begin(), ver.end()); 
	cout<<calc();
}

F1題

題意:在n個數裡選x個數,並且這n個數中,每k個數至少要有一個數被選中,求選中的x個數和最大
解法: