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個數和最大
解法: