导语
2021牛客暑期多校训练营1
选取了一些能做的题目和觉得自己应该掌握的知识点来整理
涉及的知识点
平面几何、字符串、贪心、线段树
题目
B
题目大意:一个球卡在一个直角等腰梯形内部,求问号的长度
思路:如图,有相似三角形可得两个方程
r
/
h
=
y
/
(
a
−
b
)
2
/
4
+
h
2
r/h=y/\sqrt{(a-b)^2/4+h^2}
r/h=y/(a−b)2/4+h2
(
y
−
b
/
2
)
/
(
(
a
−
b
)
/
2
)
=
x
/
h
(y-b/2)/((a-b)/2)=x/h
(y−b/2)/((a−b)/2)=x/h
联立求解即可
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
double r,a,b,h;
cin>>r>>a>>b>>h;
if(2*r<=b)
cout<<"Drop"<<endl;
else
{
cout<<"Stuck"<<endl;
double y=sqrt(a*a*((h/(a-b))*(h/(a-b))+0.25));
double n=2*y*r/a-b*h/(a-b);
printf("%.10lf\n",n);
}
return 0;
}
D
题目大意: 给出一个 n×n 的 01 矩阵,要用一个 1×m 的矩阵去覆盖一段 0,问方案数。
思路:直接暴力匹配即可,做的时候想用KMP反而弄巧成拙
代码
#include<iostream>
#include<cstring>
using namespace std;
int cal(string s1, string s2)
{
int r = 0;
int p = 0;
while ((p = s1.find(s2, p))!=-1)//移位查找
{
r++;
p++;
}
return r;
}
int main()
{
int n, m;
cin >> n >> m;
string str1,str2 = "";
for (int i = 0; i < m; i++)
str2.append("0");
long res = 0;
while (n--)
{
cin >> str1;
res += cal(str1, str2);
}
cin >> str2;
cout << res << endl;
return 0;
}
G
题目大意:给出两个长度为N的序列A、B,每次可以交换A中的任意两个位置的元素,需要交换K次,求出K次后式子 ∑ i = 1 n ∣ A i − B i ∣ \sum_{i=1}^{n}|A_i-B_i| ∑i=1n∣Ai−Bi∣的最值
思路:首先,对于给出的序列肯定有个式子的基础值,因此交换之后的总值是在基础值之上增大的,现在来考虑一下怎样的交换会使得总值增大,给定两对数 ( a 1 , b 1 ) ( a 2 , b 2 ) (a_1,b_1)(a_2,b_2) (a1,b1)(a2,b2)
- a 1 ≥ b 1 ≥ a 2 ≥ b 2 a_1\ge b_1\ge a_2\ge b_2 a1≥b1≥a2≥b2
- a 1 ≥ b 1 , a 2 ≥ b 2 , b 1 ≤ a 2 , b 2 ≤ a 1 a_1\ge b_1,a_2\ge b_2,b_1\le a_2,b_2\le a_1 a1≥b1,a2≥b2,b1≤a2,b2≤a1
这里没有考虑 b i ≥ a i b_i\ge a_i bi≥ai的情况,因为如果两对都属于 b i ≥ a i b_i\ge a_i bi≥ai,在绝对值的作用下能转换成上述两种,如果只有一个属于,完全可以将这一个换成与另一对相同的情况的另一个
第一种情况
原结果为
a
b
s
(
a
1
−
b
1
)
+
a
b
s
(
a
2
−
b
2
)
=
a
1
+
a
2
−
b
1
−
b
2
abs(a_1-b_1)+abs(a_2-b_2)=a_1+a_2-b_1-b_2
abs(a1−b1)+abs(a2−b2)=a1+a2−b1−b2
交换后为
a
b
s
(
a
2
−
b
1
)
+
a
b
s
(
a
1
−
b
2
)
=
a
1
+
b
1
−
a
2
−
b
2
abs(a_2-b_1)+abs(a_1-b_2)=a_1+b_1-a_2-b_2
abs(a2−b1)+abs(a1−b2)=a1+b1−a2−b2
变化值为
2
×
b
1
−
2
×
a
2
=
2
[
m
i
n
(
a
1
,
b
1
)
−
m
a
x
(
a
2
,
b
2
)
]
2×b_1-2×a_2=2[min(a_1,b_1)-max(a_2,b_2)]
2×b1−2×a2=2[min(a1,b1)−max(a2,b2)]
第二种情况
原结果为
a
b
s
(
a
1
−
b
1
)
+
a
b
s
(
a
2
−
b
2
)
=
a
1
+
a
2
−
b
1
−
b
2
abs(a_1-b_1)+abs(a_2-b_2)=a_1+a_2-b_1-b_2
abs(a1−b1)+abs(a2−b2)=a1+a2−b1−b2
变化后为
a
b
s
(
a
1
−
b
2
)
+
a
b
s
(
a
2
−
b
1
)
=
a
1
+
a
2
−
b
1
−
b
2
abs(a_1-b_2)+abs(a_2-b_1)=a_1+a_2-b_1-b_2
abs(a1−b2)+abs(a2−b1)=a1+a2−b1−b2
变化值为0
在这种情况下,如果
a
1
≤
b
2
a_1\le b_2
a1≤b2值就变成了
a
1
+
b
1
−
a
2
−
b
2
a_1+b_1-a_2-b_2
a1+b1−a2−b2和第一种情况结果一样
继续分析,要想使得最后的总值增大,需要找到两对值满足其中一对最小值大于另一对最大值,当 N > 2 N\gt 2 N>2时,结果中的对数大于3,必定存在两对满足 a i ≥ b i a_i\ge b_i ai≥bi或 a i ≤ b i a_i\le b_i ai≤bi,由以上分析,对这一对进行交换要么变大要么不变,此时新的 a i ≥ b i a_i\ge b_i ai≥bi或 a i ≤ b i a_i\le b_i ai≤bi就会产生,仍然可以通过选取使得原值更大或不变,所以恰好为K在 N > 2 N\gt 2 N>2时等价于小于等于K次
代码
#include <bits/stdc++.h>
using namespace std;
int N,K,A[600000],B[600000],M[600000],m[600000];
long long ans;
int main()
{
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++)
scanf("%d",&A[i]);
for(int i=1;i<=N;i++)
scanf("%d",&B[i]);
if(N==2)//特判只有两个
{
if(K%2)
swap(A[1],A[2]);
ans+=abs(A[1]-B[1])+abs(A[2]-B[2]);
printf("%lld",ans);
return 0;
}
for(int i=1;i<=N;i++)//获得基础值和每对的最值
{
ans+=abs(A[i]-B[i]);
M[i]=max(A[i],B[i]);
m[i]=min(A[i],B[i]);
}
sort(M+1,M+1+N,less<int>());//注意排序
sort(m+1,m+1+N,greater<int>());
for(int i=1;i<=K&&i<=N;i++)
if(m[i]>M[i])
ans+=((m[i]-M[i])<<1);
else
break;
printf("%lld",ans);
return 0;
}
J
题目大意:一段路上有 N 个点,每个点有一个合法时间段 [ u i u_i ui, v i v_i vi],相邻两个点有一个长度。每次问,在 u i u_i ui 的时间从 i 出发后,能否依次经过 i+1~j 的所有点,使得到达时间满足每个点的合法区间(如果提前到可以等待,迟到了失败了)。同时还可能修改一段路的长度,或者修改一个点的合法时间段
思路:
首先说句话
线段树!狗都不学!
感谢帮忙改了一天代码的学长
一开始以为本题是并查集然后连通,看了视频后才知道是线段树,视频的第一种解法没看懂,第二种勉强看懂,写了满是漏洞的代码结果折磨了自己和学长
对于每个询问,以(1,3)为例,如果要满足连通,需要满足下列条件
u 1 ≤ v 1 , m a x ( u 1 + d 1 , u 2 ) ≤ v 2 , m a x ( m a x ( u 1 + d 1 , u 2 ) + d 2 , u 3 ) ≤ v 3 u_1\le v_1,max(u_1+d_1,u_2)\le v_2,max(max(u_1+d_1,u_2)+d_2,u_3)\le v_3 u1≤v1,max(u1+d1,u2)≤v2,max(max(u1+d1,u2)+d2,u3)≤v3
上式可以等价于
u 1 ≤ v 1 , m a x ( u 1 + d 1 , u 2 ) ≤ v 2 , m a x ( u 1 + d 1 + d 2 , u 2 + d 2 , u 3 ) ≤ v 3 u_1\le v_1,max(u_1+d_1,u_2)\le v_2,max(u_1+d_1+d_2,u_2+d_2,u_3)\le v_3 u1≤v1,max(u1+d1,u2)≤v2,max(u1+d1+d2,u2+d2,u3)≤v3
如果设 u i ′ = u i + ∑ t = i n − 1 d t , v i ′ = v i + ∑ t = i n − 1 d t u_{i}^{'}=u_i+\sum_{t=i}^{n-1}d_t,v_{i}^{'}=v_i+\sum_{t=i}^{n-1}d_t ui′=ui+∑t=in−1dt,vi′=vi+∑t=in−1dt
原式则变为 u 1 ′ ≤ v 1 ′ , m a x ( u 1 ′ , u 2 ′ ) ≤ v 2 ′ ( 左 右 两 边 去 掉 重 复 累 和 值 与 上 式 相 同 ) , m a x ( u 1 ′ , u 2 ′ , u 3 ′ ) ≤ v 3 ′ u_{1}^{'}\le v_{1}^{'},max(u_{1}^{'},u_{2}^{'})\le v_{2}^{'}(左右两边去掉重复累和值与上式相同),max(u_{1}^{'},u_{2}^{'} ,u_{3}^{'})\le v_{3}^{'} u1′≤v1′,max(u1′,u2′)≤v2′(左右两边去掉重复累和值与上式相同),max(u1′,u2′,u3′)≤v3′
因此维护当前区间u’最大值,v’最小值和是否连通的标记即可
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef struct node {
ll u,v,lazy;
bool can;
} node;
node SegTree[4000010];
ll T,n,U[1212121],V[1212121],d[1212121],uu[1212121],vv[1212121];//存u'和v'
void PushDown(ll rt) {//区间延迟更新
if(SegTree[rt].lazy) {
SegTree[rt<<1].lazy+=SegTree[rt].lazy;//这里是区间累和修改,需要增加
SegTree[rt<<1|1].lazy+=SegTree[rt].lazy;
SegTree[rt<<1].u+=SegTree[rt].lazy,SegTree[rt<<1].v+=SegTree[rt].lazy;//下移
SegTree[rt<<1|1].u+=SegTree[rt].lazy,SegTree[rt<<1|1].v+=SegTree[rt].lazy;
SegTree[rt].lazy=0;
}
}
void PushUp(ll rt) {//更新最值
SegTree[rt].can=SegTree[rt<<1].can&&SegTree[rt<<1|1].can&&(SegTree[rt<<1].u<=SegTree[rt<<1|1].v);
//该区间能通的条件为左右可通并且中间连续可通
SegTree[rt].v=min(SegTree[rt<<1].v,SegTree[rt<<1|1].v);
SegTree[rt].u=max(SegTree[rt<<1].u,SegTree[rt<<1|1].u);
}
void Build(ll l,ll r,ll rt) {//建树
if(l>r)
return;
if(l==r) {
SegTree[rt].v=V[l];
SegTree[rt].u=U[l];
SegTree[rt].lazy=0;//清空叶子节点标记
SegTree[rt].can=SegTree[rt].u<=SegTree[rt].v;
return;
}
SegTree[rt].lazy=0;//建树的时候需要清空本节点的标记
ll mid=(r+l)>>1;
Build(l,mid,rt<<1);
Build(mid+1,r,rt<<1|1);
PushUp(rt);
}
void Update1(ll pos,ll u,ll v,ll l,ll r,ll rt) {//点更新,只更改u和v的值
if(l>r||pos<l||pos>r)
return ;
if(l==r&&l==pos) {
SegTree[rt].u+=u-uu[l];//更新单点的u值
SegTree[rt].v+=v-vv[l];
uu[l]=u;//需要保存,为计算之后更新的差值准备
vv[l]=v;
SegTree[rt].can=SegTree[rt].u<=SegTree[rt].v;//判断连通
return ;
}
PushDown(rt);
ll mid=(l+r)>>1;
if(pos<=mid)
Update1(pos,u,v,l,mid,rt<<1);
else
Update1(pos,u,v,mid+1,r,rt<<1|1);
PushUp(rt);
}
void Update2(ll L, ll R, ll D, ll l, ll r, ll rt) {//区间更新,因为修改了距离
if(l>r||L>r||R<l)
return;
if(L<=l&&R>=r) {
SegTree[rt].u+=D;
SegTree[rt].v+=D;
SegTree[rt].lazy+=D;
//这里不用判断连通,因为是对区间每个都增加相同值,相对大小不变,不改变连通
return;
}
PushDown(rt);
ll mid=(l+r)>>1;
if(mid>=L)
Update2(L,R,D,l,mid,rt<<1);
if(mid<=R)
Update2(L,R,D,mid+1,r,rt<<1|1);
PushUp(rt);
}
ll QU(ll L,ll R,ll l,ll r,ll rt) {//这里需要返回指定区间的最大u
if(L<=l&&R>=r)
return SegTree[rt].u;
if(L>r||R<l||l>r)
return -1;
PushDown(rt);
ll mid=(l+r)>>1;
return max(QU(L,R,l,mid,rt<<1),QU(L,R,mid+1,r,rt<<1|1));
}
ll QV(ll L,ll R,ll l,ll r,ll rt) {
if(L<=l&&R>=r)
return SegTree[rt].v;
if(L>r||R<l||l>r)
return 0x3f3f3f3f3f3f3f3f;
PushDown(rt);
ll mid=(l+r)>>1;
return min(QV(L,R,l,mid,rt<<1),QV(L,R,mid+1,r,rt<<1|1));
}
bool Query(ll L,ll R,ll l,ll r,ll rt) {//判断区间是否相连
if(l<=L&&r>=R&&SegTree[rt].can)
//剪枝,如果查找的区间属于某大区间并且该区间连通直接返回大区间状态
return 1;
if(L<=l&&R>=r)//在查询区间内直接返回
return SegTree[rt].can;
PushDown(rt);
ll mid=(l+r)>>1;
if(mid>=R)//如果中值大于目的右边界,代表需要查左边
return Query(L,R,l,mid,rt<<1);
if(mid<L)//如果中值小于目的左边界,代表需要查右边
return Query(L,R,mid+1,r,rt<<1|1);
return Query(L,R,l,mid,rt<<1)&&Query(L,R,mid+1,r,rt<<1|1)&&QU(L,R,l,mid,rt<<1)<=QV(L,R,mid+1,r,rt<<1|1);
//如果横跨区间,需要查询左右并且查询目的区间内左u与右v是否满足条件
}
int main() {
//freopen("02.in","r",stdin);
scanf("%lld",&T);
while(T--) {
ll cnt=0,Q=0;
scanf("%lld",&n);
for(int i=1; i<=n; i++) {
scanf("%lld",&U[i]);
uu[i]=U[i];
}
for(int i=1; i<=n; i++) {
scanf("%lld",&V[i]);
vv[i]=V[i];
}
for(int i=1; i<n; i++) {
scanf("%lld",&d[i]);
cnt+=d[i];
}
for(int i=1; i<n; i++) {//构造u'和v'
U[i]+=cnt;
V[i]+=cnt;
cnt-=d[i];
}
Build(1,n,1);
scanf("%lld",&Q);
while(Q--) {
int choice,a,b,c;
scanf("%d",&choice);
switch(choice) {
case 0:
scanf("%d%d",&a,&b);
if(Query(a,b,1,n,1))
printf("Yes\n");
else
printf("No\n");
break;
case 1:
scanf("%d%d",&a,&b);
Update2(1,a,b-d[a],1,n,1);//区间更新,注意区间为1~a
d[a]=b;//更新距离
break;
case 2:
scanf("%d%d%d",&a,&b,&c);
Update1(a,b,c,1,n,1);//点更新
break;
}
}
}
return 0;
}
本文总结了2021年牛客暑期多校训练营第1期的四个题目,涉及知识点包括平面几何、字符串处理、贪心算法和线段树数据结构。通过实例演示了解如何解决B题球体位置、D题矩阵覆盖、G题序列操作优化和J题路径连通问题。
1912





