视频讲解:BV15M4y1g7cf
A. Contest Start
题目大意
有
n
n
n 名选手参加比赛,第
i
i
i 名选手的比赛在
(
i
−
1
)
∗
x
(i-1)*x
(i−1)∗x 时刻开始,持续
t
t
t 分钟后结束。
每名选手的不满意度,等于其结束比赛时,其他开始了(或恰好开始)比赛但尚未结束的选手数量。
求所有选手的不满意度总和。
1
≤
n
,
x
,
t
≤
2
⋅
1
0
9
1 \leq n,x,t \leq 2 \cdot 10^9
1≤n,x,t≤2⋅109
题解
若
t
<
x
t < x
t<x ,则每名选手的比赛时间不会重叠,不满意度为
0
0
0 。
当选手足够多时,除了最后若干名选手,其余选手的不满意度等于
t
/
x
t/x
t/x 。最后若干名选手的不满意度是从
t
/
x
−
1
t/x-1
t/x−1 到
0
0
0 的公差为-1的等差数列。
当选手不够多时,则第一名选手结束比赛时,其他选手都已经开始比赛了。因此不满意度是从
n
−
1
n-1
n−1 到
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(1≤n≤105) 的字符串
s
s
s,有
q
(
1
≤
q
≤
1
0
5
)
q(1 \leq q \leq 10^5)
q(1≤q≤105) 次询问,每次询问指定其中的一个连续子串
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(1≤n≤2⋅105) 名学生,每名学生有水平
a
i
(
1
≤
a
i
≤
1
0
18
)
a_i(1 \leq a_i \leq 10^{18})
ai(1≤ai≤1018) ,将其分为若干个稳定组,允许增加任意水平的
k
(
0
≤
k
≤
1
0
18
)
k(0 \leq k \leq 10^{18})
k(0≤k≤1018) 名学生。求最少的稳定组数量。
稳定组定义:将组内学生按水平排序后,相邻两位学生的水平差不超过
x
(
1
≤
x
≤
1
0
18
)
x(1 \leq x \leq 10 ^{18})
x(1≤x≤1018) 。
题解
先对所有学生按水平排序,计算不添加额外学生的情况下的稳定组数量,并记录相邻组间,至少要添加多少名学生,才能使得这两组合并在一起。
对相邻组间合并所需的学生数量排序,从小到大贪心填补学生即可。
参考代码
#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(1≤n≤105) 种商品要买,每种商品初始价格
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(1≤ai≤1014) 个。
如果之前已经买了
b
i
(
1
≤
b
i
≤
1
0
1
4
)
b_i(1 \leq b_i \leq 10^14)
bi(1≤bi≤1014) 件商品,那么之后买第
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 手上的卡牌保留到下一回合,那么有以下两种情况:
- 当前回合的新卡牌,替换掉另一只手上的卡牌后,符合另一只手的当前回合的合法范围;
- 当前回合的新卡牌,替换掉另一只手上的卡牌后,不符合另一只手的当前回合的合法范围;
对于第二种非法情况,通过赋值 r g i , 0 = [ m , 0 ] rg_{i,0}=[m,0] rgi,0=[m,0] 为空集。表示该做法无解。
接下来考虑第一种情况,替换掉另一只手上的卡牌后,其对下一回合的影响有以下两种可能:
- 当前回合的新卡牌,也符合另一只手下回合的合法范围,则下一回合应该替换 o o o 手上的卡牌;
- 当前回合的新卡牌,不符合另一只手下回合的合法范围,则下一回合应该再次替换另一只手上的卡牌。此时 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(1≤n≤2⋅105) 的数组
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](1≤l≤i≤r≤n) 中,排序后的新位置位置距离子区间中心的最大距离。
题解
参考 的代码。
子区间排序后的给定元素到中心的距离,可以通过小于、等于、大于该元素的其他元素数量得到。设:
- 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+cntM−cntR⌋
- 若 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+cntM−cntL+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 ai≤ax ,则 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+cntM−cntR 值,就等于
∑
i
=
l
r
b
i
−
1
\sum_{i=l}^{r}{b_i}-1
∑i=lrbi−1 。
要求
c
n
t
L
+
c
n
t
M
−
c
n
t
R
cntL+cntM-cntR
cntL+cntM−cntR 的最大值,可以将其拆分为
∑
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=xrbi−2 进行计算。
∑
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}(l≤j≤r) ;
-
- 维护方式: 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}(l≤j≤r) ;
-
- 维护方式: 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("");
}