Codeforces Round #622 (Div. 2) A~C

链接:https://codeforces.com/contest/1313
A 已知有三道菜叫a,b,c吧 每个客人至少要一道菜,这三道菜都最多要个 ,给你三道菜的数量,问最多招待几个客人。
三道最多有七种组合情况,分别是 a,b,c,a+b,a+c,b+c,a+b+c.
所以排序后 先找一道菜的情况再找两道菜的情况最后三道菜

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e5+10;
int a[5];
int main(){
	int t;
	cin>>t;
	while(t--)
	{
		for(int i=0;i<3;i++)
			cin>>a[i];
		sort(a,a+3);
		int num=0;
		if(a[0]) 
		{
			num++;
			a[0]--; 
		}
		if(a[1]) 
		{
			a[1]--;
			num++;
		}
		if(a[2])
		{
			a[2]--;
			num++;
		}
		if(a[0]>0)
		{
			if(a[2]>=1) 
			{
				a[0]--;
				a[2]--;
				num++;
			}
		}
		if(a[1]>0) 
		{
			if(a[2]>0) 
			{
				num++;
				a[1]--;
				a[2]--;
			}
		}
		if(a[1]&&a[0])
		{
			a[0]--;
			a[1]--;
			num++;
		}
		if(a[0]&&a[1]&&a[2])
			num++;
		cout<<num<<endl;
	}
    return 0;
}

B 小明去参加比赛了,总共两场比赛,第一场他获得x名,第二场他y名(没有重叠),他的总分就是 x+y;比赛的排名规则是:名次等于他的总分大于等于所有选手的总分的数量(包括他自己,和他一样分数的选手的名次和他一样)。已知总人数n和小明的两场得分x,y,问你他可能获得的最低名次和最高名次。
这题就是找规律的,我是把n=5的所有情况列出来找到的。
这里有大佬的完整证明:https://www.bilibili.com/video/av91242850?from=search&seid=7923306162982651040

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e5+10;
int main(){
	int t;
	cin>>t;
	while(t--)
	{
		int n,x,y;
		cin>>n>>x>>y;
		if(x+y<=n) cout<<"1 ";
		else 
			cout<<min(x+y-n+1,n)<<' ';
		cout<<min(x+y-1,n)<<endl;
	}
    return 0;
}

C c1和c2的题意是一样的,只是数据范围不同,
已知一个数组m,让你构造一个数组a是的任意ai<=mi,而且任意j<i<k不能有aj>ai<ak(ijk不一定相邻)就是是这个数组是单峰的,在最高点两边是单调下降的。而且数组的和得最大
我们需要假设以每个点是最高点来计算这时的数组的和取最大的情况,c1可以直接暴力,从最高点向两边去遍历求解,c2是肯定不行的。假设ai是最高点,那么ai左边的数都是单调递增的,所以我们需要在左边找到第一个小于ai的点aj,在[j+1,i]这个区间内所有点一定是大于等于ai的(如果有点小于ai,那么我们找到的j就不是第一个,所以这个区间内一定都是大于等于ai的),因为ai是最高峰,所以整个区间都等ai才满足题意且最大,那么再想aj之前的,以为区间[1,i]是单增的,所以区间[1,j]也一定得是单增的,而且j在一定在i点之前,让j先经过刚才那样的处理[1,j]的总合就求过了直接加就可以了,所以就从1到n向右遍历求解;同样得求每个点左边的和时也和上面一样不过得从右向左遍历了。
求ai左边第一个小于ai的值就可以用单调栈(当时怎么没想到呢,太菜了)。
暴力的代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e3+10;
ll a[N],n,b[N][N];//b[i]保存以ai为最高峰的情况对应下标是[1,n],b[i][0]保存总和
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		 cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		b[i][i]=b[i][0]=a[i];
		for(int j=i-1;j>0;j--)
		{
			b[i][j]=min(b[i][j+1],a[j]);
			b[i][0]+=b[i][j];
		}
		for(int j=i+1;j<=n;j++)
		{
			b[i][j]=min(b[i][j-1],a[j]);
			b[i][0]+=b[i][j];
		}
	}
	ll res=0,l;
	for(int i=1;i<=n;i++)
	{
		if(b[i][0]>res)
		{
			res=b[i][0];
			l=i;
		}
	}
	for(int i=1;i<=n;i++)
		cout<<b[l][i]<<' ';
    return 0;
}

单调栈:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010;
ll n,a[N],l[N],r[N],dpl[N],dpr[N];/*li是i左边第一个小于ai的点的下标,ri是i右边
第一个小于ai的点的下标,dpli保存i左边单调递增前i个数的最大和(包括ai),dpli是
i右边单调递减i到n所有数的最大和(包括ai);*/
 
int main()
{
	stack<int> st;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		while(st.size()&&a[st.top()]>=a[i]) st.pop();/*求第一个小于等于ai的值的
	话,改成a[st.top()]>a[i]就可以了,注意 栈里存的是下标不是值!!!*/
		if(!st.size()) l[i]=0;
		else l[i]=st.top();
		st.push(i); //前边是单调栈的模板
		
		dpl[i]=dpl[l[i]]+(i-l[i])*a[i];
	}
	while(st.size()) st.pop();
	for(int i=n;i;i--)
	{
		while(st.size()&&a[st.top()]>=a[i]) st.pop();//和前边一样,单调递减逆序看的话就是单增了
		if(!st.size()) r[i]=n+1;
		else r[i]=st.top();
		st.push(i);
		dpr[i]=dpr[r[i]]+(r[i]-i)*a[i];
	}
	ll res=0,pos;//res保存最大值,pos保存最大值的最高峰的下标
	for(int i=1;i<=n;i++)
	{
		ll res1=dpl[i]+dpr[i]-a[i];//dpl和dpr都加了ai减去一个就行了
		if(res1>res)
		{
			res=res1;
			pos=i;
		}
	}
	for(int i=pos-1;i;i--)
		a[i]=min(a[i+1],a[i]);
	for(int i=pos+1;i<=n;i++)
		a[i]=min(a[i-1],a[i]);
	for(int i=1;i<=n;i++)
		printf("%lld ",a[i]);
	return 0;
}

再来加一个大佬的思路 线段树+分治
上面的思路是找最大的那个点,这次不是了,找最小的那个点,题目要求是,每个点的两边不能有同时大于它的值,那么在这个最小值的两边所有数本来就是大于等于它的,只有一边是能大于它的,那么另一边就只能全部与最小值相等了,那么大于它的那一边呢,是不是又是一个这样的过程呢,找到这个区间的最小值,一边尽量大于它且满足题意,另一边全部等最小值,已知这样递归下去,直到区间长度小于等于1(小于1也是可能的出现的,具体看代码)就可以结束了,至于说和最大的情况,每次递归的时候分别求以左右两个区间出现大雨最小值的情况,两种情况取最值即可。还有求区间的最小值就是线段树的经典应用了,其他算法也是可以的。

#include<iostream>
using namespace std;
typedef long long ll;
const int N=500010;
int n;
ll a[N];
struct node
{
	int l,r;
	ll mn,t;//mn区间最小值,t最小值在数组中的下标 
}tr[N*4];

void pushup(int rt)//由子区间更新父区间
{
	if(tr[rt<<1].mn<=tr[rt<<1|1].mn)
	{
		tr[rt].mn=tr[rt<<1].mn;
		tr[rt].t=tr[rt<<1].t;
	}
	else 
	{
		tr[rt].mn=tr[rt<<1|1].mn;
		tr[rt].t=tr[rt<<1|1].t;
	}
}
void build(int rt,int l,int r)//建树 
{
	if(l==r) 
	{
		tr[rt]={l,r,a[l],l};
		return ;
	}
	int mid= l+r >> 1;
	tr[rt]={l,r,(ll)1e12,0};
	build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
	pushup(rt);
	return ; 
}
pair<int,int> query(int rt,int l,int r)//查询最小值 
{
	if(l<=tr[rt].l&&r>=tr[rt].r)   return {tr[rt].mn,tr[rt].t};
	int mid= tr[rt].l+tr[rt].r >> 1;
	pair<int,int> left,right;
	if(r<=mid) return query(rt<<1,l,r);
	else if(l>mid) return query(rt<<1|1,l,r);
	else 
	{
		left=query(rt<<1,l,r);
		right=query(rt<<1|1,l,r);
		if(left.first<=right.first) return left;
		else return right;
	 } 
}
ll slove(int l,int r)
{
	if(l>=r) return l==r?a[l]:0ll;	
	int mid=query(1,l,r).second;/*找到区间中最小值的位置,分别递归左右两边,递归在这一边的最大可能取值相应另一边所有值都比最小值大,一个数两边不能同时大于它,所以另一边最大也只能全部等这个最小值了*/ 
	ll sum1=slove(l,mid-1)+(r-mid+1)*a[mid];//分别求两种情况所有取值的和 
	ll sum2=slove(mid+1,r)+(mid-l+1)*a[mid];
	if(sum1>=sum2)//选最大 
	{
		for(int i=mid+1;i<=r;i++) a[i]=a[mid];//更新区间的值
		return sum1;
	}
	else 
	{
		for(int i=l;i<=mid-1;i++) a[i]=a[mid];
		return sum2;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)  scanf("%lld",a+i);
	build(1,1,n);
	slove(1,n);	
	for(int i=1;i<=n;i++)
		printf("%lld ",a[i]);
	return 0;
}
发布了26 篇原创文章 · 获赞 3 · 访问量 607

猜你喜欢

转载自blog.csdn.net/qq_45288870/article/details/104479092