Codeforces Round #727 div.2 A-F题解

视频讲解:BV15M4y1g7cf

A. Contest Start

题目大意

n n n 名选手参加比赛,第 i i i 名选手的比赛在 ( i − 1 ) ∗ x (i-1)*x (i1)x 时刻开始,持续 t t t 分钟后结束。
每名选手的不满意度,等于其结束比赛时,其他开始了(或恰好开始)比赛但尚未结束的选手数量。
求所有选手的不满意度总和。
1 ≤ n , x , t ≤ 2 ⋅ 1 0 9 1 \leq n,x,t \leq 2 \cdot 10^9 1n,x,t2109

题解

t < x t < x t<x ,则每名选手的比赛时间不会重叠,不满意度为 0 0 0
当选手足够多时,除了最后若干名选手,其余选手的不满意度等于 t / x t/x t/x 。最后若干名选手的不满意度是从 t / x − 1 t/x-1 t/x1 0 0 0 的公差为-1的等差数列。
当选手不够多时,则第一名选手结束比赛时,其他选手都已经开始比赛了。因此不满意度是从 n − 1 n-1 n1 0 0 0 的公差为 − 1 -1 1 的等差数列。
选手数量多少的分界线为 t / x t/x t/x

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

int main()
{
	int T;
	ll n,x,t,ans;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld%lld",&n,&x,&t);
		if(t<x)
			printf("0\n");
		else
		{
			if(t/x<=n-1)
				ans=t/x*n-(1+t/x)*(t/x)/2;
			else
				ans=(n-1+0)*n/2;
			printf("%lld\n",ans);
		}
	}
}

B. Love Song

题目大意

给定长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1n105) 的字符串 s s s,有 q ( 1 ≤ q ≤ 1 0 5 ) q(1 \leq q \leq 10^5) q(1q105) 次询问,每次询问指定其中的一个连续子串 s [ l , r ] s[l,r] s[l,r],将,求将其中的字母重复若干次后的长度。
‘a’ 重复1次, ‘b’ 重复2次,。。。, ‘z’ 重复26次。

题解

用前缀和处理即可。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=100100;
char s[MAXN];
int sum[MAXN];

int main()
{
	int n,q,i,j,l,r,ans;
	scanf("%d%d",&n,&q);
	scanf("%s",s+1);
	for(i=1;i<=n;i++)
		sum[i]=sum[i-1]+(s[i]-'a'+1);
	while(q--)
	{
		scanf("%d%d",&l,&r);
		printf("%d\n",sum[r]-sum[l-1]);
	}
}

C. Stable Groups

题目大意

给定 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \leq n \leq 2 \cdot 10^5) n(1n2105) 名学生,每名学生有水平 a i ( 1 ≤ a i ≤ 1 0 18 ) a_i(1 \leq a_i \leq 10^{18}) ai(1ai1018) ,将其分为若干个稳定组,允许增加任意水平的 k ( 0 ≤ k ≤ 1 0 18 ) k(0 \leq k \leq 10^{18}) k(0k1018) 名学生。求最少的稳定组数量。
稳定组定义:将组内学生按水平排序后,相邻两位学生的水平差不超过 x ( 1 ≤ x ≤ 1 0 18 ) x(1 \leq x \leq 10 ^{18}) x(1x1018)

题解

先对所有学生按水平排序,计算不添加额外学生的情况下的稳定组数量,并记录相邻组间,至少要添加多少名学生,才能使得这两组合并在一起。
对相邻组间合并所需的学生数量排序,从小到大贪心填补学生即可。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=200200;
ll a[MAXN],dif[MAXN];

int main()
{
	int n,i,ans,cnt;
	ll k,x,ad;
	scanf("%d%lld%lld",&n,&k,&x);
	for(i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	sort(a+1,a+n+1);
	cnt=0;
	ans=1;
	for(i=2;i<=n;i++)
	{
		if(a[i]-a[i-1]>x)
		{
			ans++;
			dif[cnt++]=a[i]-a[i-1];
		}
	}
	sort(dif,dif+cnt);
	for(i=0;i<cnt;i++)
	{
		ad=(dif[i]-1)/x;
		if(ad<=k)
		{
			ans--;
			k-=ad;
		}
	}
	printf("%d\n",ans);
}

D. PriceFixed

题目大意

n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1n105) 种商品要买,每种商品初始价格 2 2 2 元,第 i i i 种商品至少要买 a i ( 1 ≤ a i ≤ 1 0 1 4 ) a_i(1 \leq a_i \leq 10^14) ai(1ai1014) 个。
如果之前已经买了 b i ( 1 ≤ b i ≤ 1 0 1 4 ) b_i(1 \leq b_i \leq 10^14) bi(1bi1014) 件商品,那么之后买第 i i i 件商品半价。
可以任意调整购买顺序,求最小花费。

题解

将所有商品按 b i b_i bi 排序,贪心考虑如何购买。
如果有打折的,则优先购买打折商品直到该商品买够 a i a_i ai 个。
如果没有打折的,则购买尚未买够的 b i b_i bi 最高的商品,直到有商品能够打折,或 b i b_i bi 最高的商品买够 a i a_i ai 件。
实现时,用双指针头尾维护可能打折的 b i b_i bi 最低的商品和尚未买够的 b i b_i bi 最高的商品。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=100100;
struct Product
{
	ll a,b;
	int id;
}p[MAXN];

bool cmp(Product p1,Product p2)
{
	return p1.b<p2.b;
}

int main()
{
	int n,i,l,r;
	ll ans,sum,num;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%lld%lld",&p[i].a,&p[i].b);
		p[i].id=i;
	}
	sort(p+1,p+n+1,cmp);
	l=1,r=n;
	sum=0,ans=0;
	while(l<=r)
	{
		if(p[l].b<=sum)
		{
			sum+=p[l].a;
			ans+=p[l].a;
			l++;
		}
		else
		{
			num=min(p[r].a,p[l].b-sum);
			sum+=num;
			ans+=2*num;
			p[r].a-=num;
			if(p[r].a==0)
				r--;
		}
	}
	printf("%lld\n",ans);
}

E. Game with Cards

题目大意

初始左右手上各有一张数字为 0 0 0 的卡牌。有 n n n 次操作,每次操作有一张新的数字为 k i k_i ki 的卡牌,用其替换掉任意一只手上的卡牌,并使得左手卡牌上的数字在 [ a l , i , b l , i ] [a_{l,i},b_{l,i}] [al,i,bl,i] 范围内,右手卡牌上的数字在 [ a r , i , b r , i ] [a_{r,i},b_{r,i}] [ar,i,br,i] 范围内。

问是否存在使得每次操作后双手卡牌数字都合法的方案,若有则输出每次操作后替换掉左手的卡牌还是右手的卡牌。

题解

参考 Intercept 的代码。

为便于描述,用 o o o 表示左右手。 o = 0 o=0 o=0 表示左手, o = 1 o=1 o=1 表示右手。
r g i , o rg_{i,o} rgi,o 表示第 i i i 回合 o o o 手上的卡牌应该在 [ r g i , o . l , r g i , o . r ] [rg_{i,o}.l,rg_{i,o}.r] [rgi,o.l,rgi,o.r] 范围内,才能使得后续回合的操作有合法解。若 r g i , o rg_{i,o} rgi,o 为空集则表示无解。
o p i , o op_{i,o} opi,o 表示第 i i i 回合用新卡片替换掉 o o o 手上的卡牌后,下一回合的新卡牌应该替换 o p i , o op_{i,o} opi,o 手上的卡牌。

考虑倒叙进行决策,维护 r g i , o rg_{i,o} rgi,o o p i , o op_{i,o} opi,o 数组。
对于第 i i i 回合,枚举 o o o 手上的卡牌保留到下一回合,那么有以下两种情况:

  1. 当前回合的新卡牌,替换掉另一只手上的卡牌后,符合另一只手的当前回合的合法范围;
  2. 当前回合的新卡牌,替换掉另一只手上的卡牌后,不符合另一只手的当前回合的合法范围;

对于第二种非法情况,通过赋值 r g i , 0 = [ m , 0 ] rg_{i,0}=[m,0] rgi,0=[m,0] 为空集。表示该做法无解。

接下来考虑第一种情况,替换掉另一只手上的卡牌后,其对下一回合的影响有以下两种可能:

  1. 当前回合的新卡牌,也符合另一只手下回合的合法范围,则下一回合应该替换 o o o 手上的卡牌;
  2. 当前回合的新卡牌,不符合另一只手下回合的合法范围,则下一回合应该再次替换另一只手上的卡牌。此时 o o o 手上的卡牌会保留到下一回合,因此当前回合 o o o 手上的卡牌的合法范围,应该继承下一回合 o o o 手上的卡牌的合法范围(取交集);

这样倒叙遍历到第一回合,通过判断第一回合左右手的合法范围 r g 1 , o rg_{1,o} rg1,o 是否有解。
若有,则选择任意有解的方案起点,沿着 o p i , o op_{i,o} opi,o 向后推导。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=100100;
int k[MAXN],op[MAXN][2];

struct Range
{
	int l,r;
	Range(){}
	Range(int _l,int _r)
	{
		l=_l,r=_r;
	}
}rg[MAXN][2],tmp[2];

void change(Range &now,Range t)
{
	now.l=max(now.l,t.l);
	now.r=min(now.r,t.r);
}

bool check(Range t,int x)
{
	return t.l<=x&&x<=t.r;
}

int main()
{
	int n,m,i,o;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&k[i]);
		for(o=0;o<2;o++)
			scanf("%d%d",&rg[i][o].l,&rg[i][o].r);
	}
	rg[n+1][0]=rg[n+1][1]=Range(0,m);
	for(i=n;i>=1;i--)
	{
		tmp[0]=rg[i][0];
		tmp[1]=rg[i][1];
		//枚举哪一只手的手牌保留到下一回合 
		for(o=0;o<2;o++)
		{
			//若替换另一只手的卡牌后符合当前回合的范围 
			if(check(tmp[o^1],k[i]))
			{
				//且替换后也符合另一只手下回合的范围 
				if(check(rg[i+1][o^1],k[i]))
				//则下一回合的卡牌,替换当前手的卡牌 
					op[i][o^1]=o;
				//若替换后不符合另一只手下回合的范围
				//则下一回合必须替换另一只手的卡牌 
				else
				{
					//当前手的卡牌不变 
					//因此当前手的范围本回合的范围,需要继承下一回合的范围 
					change(rg[i][o],rg[i+1][o]);
					//记录下一回合必须替换另一只手的卡牌 
					op[i][o^1]=o^1;
				}
			}
			//若替换另一只手的卡牌后不符合当前回合的范围
			//则当前回合的手牌不能保留到下一回合,设为无解 
			else
				rg[i][o]=Range(m,0);
		}
	}
	if(!check(rg[1][0],0)&&!check(rg[1][1],0))
		puts("No");
	else
	{
		puts("Yes");
		o=check(rg[1][0],0);
		for(i=1;i<=n;i++)
		{
			printf("%d ",o);
			o=op[i][o];
		}
		puts("");
	}
}

F. Strange Array

题目大意

给定一个大小为 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \leq n \leq 2 \cdot 10^5) n(1n2105) 的数组 a a a ,求其每个元素的奇怪值。
你可以选择一个连续子区间 [ l , r ] [l,r] [l,r] ,将 a l , a l + 1 , a l + 2 , . . . , a r a_l,a_{l+1},a_{l+2},...,a_r al,al+1,al+2,...,ar 按从小到大排序。如果有元素相等,你可以任意决定他们的顺序。
定义子区间中心:

  • 若子区间有奇数个元素,则其中心在位置 ( l + r ) / 2 (l+r)/2 (l+r)/2 上;
  • 若子区间有偶数个元素,则其中心在位置 ( l + r + 1 ) / 2 (l+r+1)/2 (l+r+1)/2 上;

i i i 个元素的奇怪值,等于所有可能的连续子区间 [ l , r ] ( 1 ≤ l ≤ i ≤ r ≤ n ) [l,r](1 \leq l \leq i \leq r \leq n) [l,r](1lirn) 中,排序后的新位置位置距离子区间中心的最大距离。

题解

参考 的代码。
子区间排序后的给定元素到中心的距离,可以通过小于、等于、大于该元素的其他元素数量得到。设:

  • c n t L cntL cntL 表示小于该元素的数量;
  • c n t M cntM cntM 表示等于该元素的数量;
  • c n t R cntR cntR 表示大于该元素的数量;

则排序后的给定元素 a x a_x ax 到中心的距离可以表示为:

  • a x a_x ax 大于中心元素,则 a n s = ⌊ c n t L + c n t M − c n t R 2 ⌋ ans=\lfloor \frac{cntL+cntM-cntR}{2} \rfloor ans=2cntL+cntMcntR
  • a x a_x ax 小于等于中心元素,则 a n s = ⌊ c n t R + c n t M − c n t L + 1 2 ⌋ ans=\lfloor \frac{cntR+cntM-cntL+1}{2} \rfloor ans=2cntR+cntMcntL+1

元素 a x a_x ax 的奇怪值等于上述两种情况的最大值。
两种情况的解决方法类似。如果能计算其中一个的最大值,那么另一个可以将数组翻转并取相反数后用同样方法得到。

以下考虑给定元素 a x a_x ax 大于中位数的情况。
假设对于指定元素 a x a_x ax,存在这样一个数组 b b b ,满足以下条件:

  • a i ≤ a x a_i \leq a_x aiax ,则 b i = 1 b_i=1 bi=1
  • a i > a x a_i>a_x ai>ax ,则 b i = − 1 b_i=-1 bi=1

b b b 数组的维护,可以初始全设为 − 1 -1 1 ,再从小到大遍历元素 a i a_i ai ,将 b i b_i bi 逐个更新为 1 1 1

那么对于子区间 [ l , r ] [l,r] [l,r] c n t L + c n t M − c n t R cntL+cntM-cntR cntL+cntMcntR 值,就等于 ∑ i = l r b i − 1 \sum_{i=l}^{r}{b_i}-1 i=lrbi1
要求 c n t L + c n t M − c n t R cntL+cntM-cntR cntL+cntMcntR 的最大值,可以将其拆分为 ∑ i = l x b i + ∑ i = x r b i − 2 \sum_{i=l}^{x}{b_i}+\sum_{i=x}^{r}{b_i}-2 i=lxbi+i=xrbi2 进行计算。
∑ i = l x b i \sum_{i=l}^{x}{b_i} i=lxbi ∑ i = x r b i \sum_{i=x}^{r}{b_i} i=xrbi 的最大值,可以用区间合并线段树维护查询。

对于区间合并线段树上 [ l , r ] [l,r] [l,r] 区间对应的节点 n o w now now ,有以下几种属性:

  • s u m sum sum ,该区间的 b i b_i bi 总和,即 ∑ i = l r b i \sum_{i=l}^r{b_i} i=lrbi
  • l m a x lmax lmax ,该区间左起连续子区间的最大总和,即 M a x { ∑ i = l j b i } ( l ≤ j ≤ r ) Max\{\sum_{i=l}^{j}{b_i}\}(l \leq j \leq r) Max{i=ljbi}(ljr)
    • 维护方式: n o w . l m a x = m a x ( l s o n . l m a x , l s o n . s u m + r s o n . l m a x ) now.lmax=max(lson.lmax,lson.sum+rson.lmax) now.lmax=max(lson.lmax,lson.sum+rson.lmax)
  • r m a x rmax rmax ,该区间右起连续子区间的最大总和,即 M a x { ∑ i = l j b i } ( l ≤ j ≤ r ) Max\{\sum_{i=l}^{j}{b_i}\}(l \leq j \leq r) Max{i=ljbi}(ljr)
    • 维护方式: n o w . r m a x = m a x ( r s o n . r m a x , r s o n . s u m + l s o n . r m a x ) now.rmax=max(rson.rmax,rson.sum+lson.rmax) now.rmax=max(rson.rmax,rson.sum+lson.rmax)

其中 l s o n lson lson r s o n rson rson 分别为当前节点 n o w now now 的左右儿子。

参考代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int MAXN=200200;

struct Node
{
	int l,r,lm,rm,sum;
}node[MAXN<<2];

void pushUp(int i)
{
	node[i].sum=node[i<<1].sum+node[i<<1|1].sum;
	node[i].lm=max(node[i<<1].lm,node[i<<1].sum+node[i<<1|1].lm);
	node[i].rm=max(node[i<<1|1].rm,node[i<<1|1].sum+node[i<<1].rm);
}

void build(int i,int l,int r)
{
	node[i].l=l;
	node[i].r=r;
	if(l==r)
	{
		node[i].lm=node[i].rm=node[i].sum=-1;
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	pushUp(i);
}

Node query(int i,int l,int r)
{
	if(node[i].l==l&&node[i].r==r)
		return node[i];
	int mid=(node[i].l+node[i].r)>>1;
	if(r<=mid)
		return query(i<<1,l,r);
	else if(l>mid)
		return query(i<<1|1,l,r);
	else
	{
		Node a,b,c;
		a=query(i<<1,l,mid);
		b=query(i<<1|1,mid+1,r);
		c.sum=a.sum+b.sum;
		c.lm=max(a.lm,a.sum+b.lm);
		c.rm=max(b.rm,b.sum+a.rm);
		return c;
	}
}

void change(int i,int x,int v)
{
	if(node[i].l==node[i].r)
	{
		node[i].lm=node[i].rm=node[i].sum=node[i].mx=v;
		return;
	}
	int mid=(node[i].l+node[i].r)>>1;
	if(x<=mid)
		change(i<<1,x,v);
	else
		change(i<<1|1,x,v);
	pushUp(i);
}

struct Date
{
	int x,id;
}a[MAXN];

bool cmp(Date a,Date b)
{
	return a.x<b.x;
}

int ans[MAXN];

int main()
{
	int n,i,j,p,q;
	Node ln,rn;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a[i].x);
		a[i].id=i;
	}
	sort(a+1,a+n+1,cmp);
	for(i=0;i<2;i++)
	{
		build(1,1,n);
		p=q=1;
		for(j=1;j<=n;j++)
		{
			while(p<=n&&a[p].x==j)
			{
				change(1,a[p].id,1);
				p++;
			}
			while(q<=n&&a[q].x==j)
			{
				ln=query(1,1,a[q].id);
				rn=query(1,a[q].id,n);
				ans[a[q].id]=max(ans[a[q].id],ln.rm+rn.lm-1-!i);
				q++;
			}
		}
		for(j=1;j<=n/2;j++)
			swap(a[j],a[n-j+1]);
		for(j=1;j<=n;j++)
			a[j].x=n-a[j].x+1;
	}
	for(i=1;i<=n;i++)
		printf("%d ",ans[i]/2);
	puts("");
}
  • 13
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值