2021牛客暑期多校训练营1

本文总结了2021年牛客暑期多校训练营第1期的四个题目,涉及知识点包括平面几何、字符串处理、贪心算法和线段树数据结构。通过实例演示了解如何解决B题球体位置、D题矩阵覆盖、G题序列操作优化和J题路径连通问题。

2021牛客暑期多校训练营1

导语

2021牛客暑期多校训练营1

选取了一些能做的题目和觉得自己应该掌握的知识点来整理

涉及的知识点

平面几何、字符串、贪心、线段树

链接: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/(ab)2/4+h2
( y − b / 2 ) / ( ( a − b ) / 2 ) = x / h (y-b/2)/((a-b)/2)=x/h (yb/2)/((ab)/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=1nAiBi的最值

思路:首先,对于给出的序列肯定有个式子的基础值,因此交换之后的总值是在基础值之上增大的,现在来考虑一下怎样的交换会使得总值增大,给定两对数 ( a 1 , b 1 ) ( a 2 , b 2 ) (a_1,b_1)(a_2,b_2) (a1,b1)(a2,b2)

  1. a 1 ≥ b 1 ≥ a 2 ≥ b 2 a_1\ge b_1\ge a_2\ge b_2 a1b1a2b2
  2. 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 a1b1,a2b2,b1a2,b2a1

这里没有考虑 b i ≥ a i b_i\ge a_i biai的情况,因为如果两对都属于 b i ≥ a i b_i\ge a_i biai,在绝对值的作用下能转换成上述两种,如果只有一个属于,完全可以将这一个换成与另一对相同的情况的另一个

第一种情况
原结果为 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(a1b1)+abs(a2b2)=a1+a2b1b2
交换后为 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(a2b1)+abs(a1b2)=a1+b1a2b2
变化值为 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×b12×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(a1b1)+abs(a2b2)=a1+a2b1b2
变化后为 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(a1b2)+abs(a2b1)=a1+a2b1b2
变化值为0
在这种情况下,如果 a 1 ≤ b 2 a_1\le b_2 a1b2值就变成了 a 1 + b 1 − a 2 − b 2 a_1+b_1-a_2-b_2 a1+b1a2b2和第一种情况结果一样

继续分析,要想使得最后的总值增大,需要找到两对值满足其中一对最小值大于另一对最大值,当 N > 2 N\gt 2 N>2时,结果中的对数大于3,必定存在两对满足 a i ≥ b i a_i\ge b_i aibi a i ≤ b i a_i\le b_i aibi,由以上分析,对这一对进行交换要么变大要么不变,此时新的 a i ≥ b i a_i\ge b_i aibi a i ≤ b i a_i\le b_i aibi就会产生,仍然可以通过选取使得原值更大或不变,所以恰好为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 u1v1,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 u1v1,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=in1dt,vi=vi+t=in1dt
原式则变为 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}^{'} u1v1,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;
}

参考文献

  1. Game of Swapping Numbers
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值