2024牛客暑期多校训练营第四场

赛时解决题目:C,H,G,I

补题:A,F


G:

       题意:给马和将军的坐标,然后马需要喝水,在x轴和y轴有河流,问马喝完水回来的最短路径。
        题解:挺明显,数学中的镜像对称一下就可以了,让马或将军的坐标对x轴和y轴算出对称点再计算距离取min得到答案。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e5+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
double deal(ll x,ll y,ll tx,ll ty)
{
    return sqrtl((y-ty)*(y-ty)+(x-tx)*(x-tx));
}
void solve()
{
    double x,y;
    double tx,ty;cin>>x>>y>>tx>>ty;
    double nx=x,ny=y;
    double ans=min(deal(x,y,-tx,ty),deal(x,y,tx,-ty));
    cout<<fixed<<setprecision(14)<<ans<<'\n';
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



I:

       题意:给一堆朋友,问有多少个区间,彼此与彼此之间都是朋友。
        题解:通过题目我们可以知道,你是我朋友,那我一定是你朋友(废话)。那么对于一个新加入的人来说,他必须是我们共同的朋友。我觉得我们可以很容易的想到,如果一个区间l~r内,如果存在一个人i和其他人不是朋友,那么这个区间肯定不是合法区间(好像也是废话)。

        解题思路双指针加二分,很明显我们不能遍历所有区间,但为什么我会想到双指针呢,因为根据我上面讲到的性质:如果一个区间l~r内,如果存在一个人i和其他人不是朋友,那么这个区间肯定不是合法区间。可以想到应该枚举右端点,然后左端点的范围肯定要大于i,不然就是我所提到的不合法区间。

        那么二分又从何谈起呢,我们首先开个vector<ll>a[n+10],那么,我们在枚举右端点的时候,判断加入我们所维护的区间是否合理的条件就是,新加入的它,是否和原本的区间都是朋友,怎么判断呢,就是通过二分他的朋友,假设当前枚举到了r,那么我需要从a[r]中二分出r本身的位置,然后再二分出上一次得到的左端点的位置l在a[r]中的位置,很明显,如果r-l==位置(r)-位置(l),那么说明这是一个合法区间,否则我们需要让左端点进行右移或者让位置(l)右移,来得到新的好朋友区间,对于这个区间的权值,我们肯定要加上他所有的情况,也就是他左端点的情况数,因为右端点已经定下了,所以左端点情况数就是r-l,我单独将一个人的情况挑了出来,具体见代码。    

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
ll c[N];
void solve()
{
    ll n,m;
    cin>>n>>m;
    vector<ll>a[n+10];
    for(int i=1; i<=m; i++)
    {
        ll x,y;
        cin>>x>>y;
        a[x].pb(y);
        a[y].pb(x);
    }
    for(int i=1; i<=n; i++)
    {
        a[i].pb(i);a[i].pb(n*2),a[i].pb(0);
        sort(a[i].begin(),a[i].end());
        c[i]=a[i].size();
    }
    ll nowl=1;
    ll ans=0;
    for(ll nowr=1; nowr<=n; nowr++)
    {
        ll l=1,r=c[nowr]-2;
        while(l<r)
        {
            ll mid=l+r>>1;
            if(a[nowr][mid]>=nowr)r=mid;
            else l=mid+1;
        }
        ll R=l;
        l=1,r=c[nowr]-2;
        while(l<r)
        {
            ll mid=l+r>>1;
            if(a[nowr][mid]>=nowl)r=mid;
            else l=mid+1;
        }
        while(a[nowr][l]!=nowl||R-l!=nowr-nowl)
        {
            if(a[nowr][l]<nowl&&l<c[nowr]-2)l++;
            else nowl++;
            if(nowl==nowr)break;
        }
//        cout<<nowl<<' '<<nowr<<'\n';
        ans+=nowr-nowl+1;
    }
    cout<<ans<<'\n';
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
//    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



C:

       题意:给一个乱序的排列(1~n只出现一次),问一次选四个数,然后这四个数乱排,问最少多少次能排成不升序。
        题解:这道题一开始确实没有想法,但是实际操作的时候,我们肯定希望,数字a[i]能换到他想要的位置去,这时候再去找到他想要的那个位置,发现那个位置上也有个不对应的数字,这样继续找下去,会发现,形成了一个闭环,没错,这些数字形成了一个又一个的闭环块。对于一个闭环块,我们对他进行观察会发现,他就是一个很规律的数组,比如1->2(1)->3(2),()表示这个位置本来应该谁在上面,每个数字的合理位置很明显就是在下一个,动手操作一下发现,你最多一次性排好三个,那么你的ans对于这个块,所加上的权值就是(i+1)/3,(这里把i%3==2的情况算了一个1),然后记录一下i%3==2的情况数(后期要减)。为什么呢,因为对于i%3==0||i%3==1的情况,第一种情况你排完了很合理,第二种你排完剩一个,那么它肯定在它自己合法的位置上,对于i%3==2的情况,就是剩下两个需要置换的,那么很明显,置换只需要选2个,但我们能一次性选4个,所以后期答案需要减去cnt_2>>1。.输出ans得到结果。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
ll f[N];
ll now[N];
ll find(ll x)
{
    if(f[x]==x)return x;
    else return f[x]=find(f[x]);
}
void solve()
{
    ll n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1; i<=n; i++)f[i]=i,now[i]=1,cin>>a[i];;
    for(int i=1; i<=n; i++)
    {
        if(find(i)!=find(a[i]))///归块
        {
            now[find(i)]+=now[find(a[i])];
            f[a[i]]=find(i);
        }
    }
    vector<ll>v;
    for(int i=1; i<=n; i++)
    {
        if(f[i]==i)
        {
            v.pb(now[i]);
        }
    }
    ll ans=0;
    ll cnt2=0;
    for(auto i:v)
    {
        ans+=i/3;
        if(i%3==2)ans--;
    }
//    ans+=cnt2/4;
    cout<<ans<<'\n';
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



H:

       题意:给个数组,然后可以这么操作,下图(不想叭叭了)

       题解:思考一下这两个操作,你会发现,不就是定个水平线然后翻面嘛,而且还是翻半面,因为全面翻的话就没意义了,手搓一下,发现跟上一场那个拖拉机(小车)撞墙很像,就会发现使用特定的水平线就可以精准控制最外面那个数字往里靠,思考一下,这不和小车撞墙一样拿gcd算一下就可以了,然后就搓出来了(其实就是辗转相减法)。
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)5e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
void solve()
{
    ll n;cin>>n;
    set<ll> st;
    for(int i=1;i<=n;i++)
    {
        ll x;cin>>x;
        st.insert(x);
    }
    if(st.size()<=1)
    {
        cout<<0<<'\n';
        return;
    }
    else
    {
        ll ans=0;
        vector<ll>a(st.size(),0);
        ll cnt=0;
        for(auto i:st)a[cnt++]=i;
        ll l=0;
        ans=a[1]-a[0];
        for(int i=2;i<cnt;i++)
            ans=gcd(ans,a[i]-a[i-1]);
        cout<<ans<<'\n';
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



A:带权并查集

题意:给一棵树,然后一边给边,一边给查询的点,问以这个点为根,得到的最深度(就是以它为根的树的深度)。

解题思路

     有几个关键信息:

  1. 最后会构成一棵有根树。
  2. 所给的边是有向边
  3. 求的是树的深度

解题

        看起来像是需要动态维护这棵树的深度,但后期观察好像没有必要这么做(题目还有内存限制但奈何我看不懂一点,嘿嘿,但是我发现我数组开把N开到5e6就内存超限了)我们可以发现,对于一棵树的深度,我们完全可以用dfs先跑一遍得到它每个节点的深度,而且题目给的根是固定的。

        然后我们会发现,如果想计算以其中一个节点作为根,想计算这棵树的深度的话,那么只需要找到这棵树的最深的节点就好了(因为他不能往他父亲那跑,因为有向边)。

        我们首先先用dfs把这棵树所有节点的深度求出来,再搭配并查集维护加入边后每棵树的最深点即可。 

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
vector<ll>vec[N];
ll a[N],b[N],w[N];
bool vis[N];
ll deep[N],fa[N];
ll find(ll x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void dfs(ll u,ll fa)
{
    deep[u]=deep[fa]+1;
    for(auto it:vec[u])
        dfs(it,u);
}
void solve()
{
    ll n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        vec[i].clear();
        vis[i]=0;///入度
        fa[i]=i;
    }
    for(int i=1;i<n;i++)
    {
        cin>>a[i]>>b[i]>>w[i];
        vec[a[i]].push_back(b[i]);
        vis[b[i]]=1;///b接在a的屁股后面
    }
    ll root=0;
    for(int i=1;i<=n;i++)
        if(!vis[i])
            root=i;///根节点,题目保证只有一个根
    dfs(root,0);///计算深度
    for(int i=1;i<n;i++)
    {
        ll x=find(fa[a[i]]);///找到父节点
        ll y=find(fa[b[i]]);
        ll ans1=y,ans2=x;
        if(deep[x]>=deep[y])///x的深度大于y
        {
            ans1=x;
            ans2=y;
        }
        ///ans1是比较深的那个
        fa[ans2]=fa[ans1];///浅的是深的儿子
        cout<<abs(deep[find(w[i])]-deep[w[i]])<<' ';
    }
    cout<<'\n';
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



(哔哔几句:)写题的时候就应该好好看题,我就是因为没完全看完题目在那瞎想所以才想的一片浆糊没出。

F:

        解题:试一下发现没什么特殊结构,不如直接造直线实在,然后再用二分的方式去查找。(最后发现如果分出来的答案有问题,判断答案和原来查找的数奇偶看是否加一就行)    

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
ll get(ll x)
{
    if(x&1)
        return (x-1)*(x-1)/4;///分别算中心点和边缘点的f再减就行
    else
        return x*x/4-x/2;
};
void solve()
{
    ll x;cin>>x;
    ll l=1,r=3e9;
    while(l<r)
    {
        ll mid=l+r>>1;
        if(get(mid)>=x)r=mid;
        else l=mid+1;
    }
    if(l%2==0)
        if(get(l)%2==x%2)cout<<l<<'\n';
        else cout<<l+1<<'\n';
    else
        cout<<l<<'\n';
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值