题解 | #2023牛客寒假算法基础集训营1#

World Final? World Cup! (I)

https://ac.nowcoder.com/acm/contest/46800/A

alt

温馨提示:如有遇到交题解内代码运行超时的情况,可以手动加上读入优化或参考提交记录

首先,感谢一下出题人——炸只因块哥哥!

其次,感谢一下可爱的波奇酱!

最后,安利一下《孤独摇滚》,大家一起波门!

A World Final? World Cup! (I)

签到题

考虑如下:

1.本球踢完,对手剩余所有球都踢进,本方所有球都踢不进是否已经胜利
2.本球踢完,对手剩余所有球都踢不进,本方所有球都踢进是否已经失败

3.注意事项:先手方球踢完时,剩余球比后手方少一个!

代码如下:

#include <bits/stdc++.h>

using namespace std;
 
using LL = long long;

int main(){
    int T = 1;
    cin>>T;
    while(T--){
        int n = 10;
        string s;
        cin>>s;
        s = " " + s;
        int a = 0 , b = 0 , ans = -1;
        for(int i=1;i<=n;i++){
            if(i&1){
                a += s[i] - '0';
                if(a-b>(n-i+1)/2){
                    ans = i;
                    break;
                }
                if(b-a>(n-i)/2){
                    ans = i;
                    break;
                }
            }
            else{
                b += s[i] - '0';
                if(a-b>(n-i)/2){
                    ans = i;
                    break;
                }
                if(b-a>(n-i)/2){
                    ans = i;
                    break;
                }
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60265893

B World Final? World Cup! (II)

hard题

思考过程:考虑四维DP,发现转移复杂度过大,二维DP,无法转移,不会做,摆烂——

C 现在是,学术时间 (I)

签到题

根据H指数式子可以发现,一篇论文可以提供的贡献最多为1(为什么呢?我也不会证,只能说显然了)

那么,每个教授发表其本人写的论文即可,只要至少有一个人引用,那么这篇论文就有贡献,否则没有,所以用论文总数直接减去的引用量为0的数量即可。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        int ans = n;
        for(int i=1;i<=n;i++){
            int x;
            cin>>x;
            if(!x) ans--;
        }
        cout<<ans<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/46800/C

D 现在是,学术时间 (II)

简单题

分类讨论

alt

假设粉色是原矩形,讨论P点位置,共有四种情况:

1.在原矩形内(波奇),那么,最优策略就是以P点和原矩形其中一个点作为对角线的新矩形中,面积最大的那一个
2.在原矩形右上方(归去来兮),那么,最优策略就是以P点和原矩形左下角点作为对角线的新矩形
3.在原矩形上方(大天使),那么,最优策略就是以P点和原矩形左下角点或右下角点作为对角线的新矩形中,面积大的那一个。
4.在原矩形右方(屑贝斯),那么,最优策略就是以P点和原矩形左下角点或左上角点作为对角线的新矩形中,面积大的那一个。

接下来就是计算交与并的面积,需要较好的代码基础快速求出。

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LD = long double;

int main(){
    int T = 1;
    while(T--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            int x,y,xp,yp;
            cin>>x>>y>>xp>>yp;
            LD ans = 0;
            if(xp>=x && yp>=y)
                ans = x*y*1.0/(xp*yp);
            else if(xp>=x && yp<=y)
                ans = max(x*yp , x*(y-yp)) * 1.0/((xp-x) * max(yp , y-yp) + x*y);
            else if(xp<=x && yp>=y)
                ans = max(xp*y , (x-xp)*y) * 1.0/((yp-y) * max(xp , x-xp) + x*y);
            else if(xp<=x && yp<=y)
                ans = max({xp*yp , xp*(y-yp) , (x-xp)*yp , (x-xp)*(y-yp)}) * 1.0/(x*y);
            printf("%.10Lf\n", ans);
        }
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60330772

E 鸡算几何

中等题

不太会几何,想到一个非常散兵的做法,然后直接套板子。(讲的比较不清楚)

前提:如果L型两边长度相等,那么一定无法判断是否需要操作三,所以直接特判。

首先把初始状态和最终状态的中点(B)平移至原点,然后将其中初始状态和最终状态的同一条边旋转至同一条坐标轴的同一个方向上,我选的(应该)是y轴正方向(由于边长不同,需要把边进行对应,也就是交换初始状态或最终状态的A、C点,体现在vector上就是翻转),最后考虑两种状态下的剩下一个点是否在y轴同侧,是就说明不一定需要操作三,否则必须使用操作三。

代码如下:

#include <bits/stdc++.h>

using namespace std;

using point_t=long double;  //全局数据类型,可修改为 long long 等

constexpr point_t eps=1e-8;
constexpr long double PI=3.1415926535897932384l;

// 点与向量
template<typename T> struct point{
    T x,y;

    bool operator==(const point &a) const {return (abs(x-a.x)<=eps && abs(y-a.y)<=eps);}
    bool operator<(const point &a) const {if (abs(x-a.x)<=eps) return y<a.y-eps; return x<a.x-eps;}
    bool operator>(const point &a) const {return !(*this<a || *this==a);}
    point operator+(const point &a) const {return {x+a.x,y+a.y};}
    point operator-(const point &a) const {return {x-a.x,y-a.y};}
    point operator-() const {return {-x,-y};}
    point operator*(const T k) const {return {k*x,k*y};}
    point operator/(const T k) const {return {x/k,y/k};}
    T operator*(const point &a) const {return x*a.x+y*a.y;}  // 点积
    T operator^(const point &a) const {return x*a.y-y*a.x;}  // 叉积,注意优先级
    int toleft(const point &a) const {const auto t=(*this)^a; return (t>eps)-(t<-eps);}  // to-left 测试
    T len2() const {return (*this)*(*this);}  // 向量长度的平方
    T dis2(const point &a) const {return (a-(*this)).len2();}  // 两点距离的平方

    // 涉及浮点数
    long double len() const {return sqrtl(len2());}  // 向量长度
    long double dis(const point &a) const {return sqrtl(dis2(a));}  // 两点距离
    long double ang(const point &a) const {return acosl(max(-1.0l,min(1.0l,((*this)*a)/(len()*a.len()))));}  // 向量夹角
    point rot(const long double rad) const {return {x*cos(rad)-y*sin(rad),x*sin(rad)+y*cos(rad)};}  // 逆时针旋转(给定角度)
    point rot(const long double cosr,const long double sinr) const {return {x*cosr-y*sinr,x*sinr+y*cosr};}  // 逆时针旋转(给定角度的正弦与余弦)
};

using Point=point<point_t>;

int main(){
    ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T = 1;
    cin>>T;
    while(T--){
        vector<Point> s(3),t(3);
        for(auto &[x,y] : s){
            cin>>x>>y;
        }
        for(auto &[x,y] : t){
            cin>>x>>y;
        }
        if(abs(s[1].dis2(s[0]) - s[1].dis2(s[2])) <= eps) goto no;
        if(abs(s[1].dis2(s[0]) - t[1].dis2(t[0])) > eps) reverse(t.begin(), t.end());
        {
            auto [dx,dy] = s[1];
            for(auto &[x,y] : s){
                x -= dx;
                y -= dy;
            }
            auto d = s[1].dis(s[0]);
            auto si = s[0].x/d , co = s[0].y/d;
            for(auto &x : s){
                x = x.rot(co,si);
            }
        }
        {
            auto [dx,dy] = t[1];
            for(auto &[x,y] : t){
                x -= dx;
                y -= dy;
            }
            auto d = t[1].dis(t[0]);
            auto si = t[0].x/d , co = t[0].y/d;
            for(auto &x : t){
                x = x.rot(co,si);
            }
        }
        if(t[2].x*s[2].x<0 || t[2].y*s[2].y<0) goto yes;
        else goto no;
        if(0) yes: cout<<"YES"<<endl;
        if(0) no:  cout<<"NO"<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60377745

F 鸡玩炸蛋人

中等题

并查集,这题比较诈骗,需要一定的思维。

首先读题需要发现,鸡可以在一个点连续下蛋,那么对于蛋只需要考虑有和没有,不需要考虑数量。然后要发现,一个联通块内的点,无论一个联通块内下蛋的情况是什么样的,在连通块内任取两个点都可以满足。由于S、T有顺序关系,并且可以重复,所以点对数量就是连通块大小的平方,记得开long long。

出题人的证明:在一棵连通块的生成树中,首先从S走到T,然后从T进行DFS,每次往回走时下蛋即可。

之后就是讨论有几个连通块内有蛋:

1.为0,那么S、T只要取自同一连通块即可,把所有连通块的贡献求和即可。
2.为1,那么S、T只能取自一个连通块,计算次连通块的贡献即可。
3.大于1,无解,因为鸡无法跨越连通块。

代码如下:

#include <bits/stdc++.h>
#define x first
#define y second

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        int n,m;
        cin>>n>>m;
        vector<int> s(n+1),num=s;
        for(int i=1;i<=n;i++){
            s[i] = i;
            num[i] = 1;
        }
        function<int(int)> find = [&](int x){
            if(s[x]!=x) return s[x] = find(s[x]);
            return s[x];
        };
        auto add = [&](int x,int y){
            if(x>y) swap(x,y);
            s[y] = s[x];
            num[x] += num[y];
        };
        for(int i=1;i<=m;i++){
            int u,v;
            cin>>u>>v;
            u = find(u);
            v = find(v);
            if(u!=v) add(u,v);
        }
        map<int,int> mp;
        for(int i=1;i<=n;i++){
            int x;
            cin>>x;
            if(x) mp[find(i)]++;
        }
        if(mp.size()>1) cout<<0<<endl;
        else if(!mp.size()){
            LL ans = 0;
            for(int i=1;i<=n;i++){
                if(find(i)==i) ans += 1ll*num[i]*num[i];
            }
            cout<<ans<<endl;
        }
        else{
            int x = mp.begin()->x;
            LL ans = num[x];
            ans *= num[x];
            cout<<ans<<endl;
        }
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60386142

G 鸡格线

中等题

STL战神,有各种数据结构解法,由于数据比较水,一些奇怪的做法也可以通过。

观察式子,需要有一个会收敛的直觉,最直观的就是收敛至100。实际打表一下,可以发现,小于100的正整数,会收敛至99,大于等于100的正整数会收敛至100,而0会收敛为0。并且收敛所需的次数不多

那么,一操作可以转化成直接对区间内每一个元素进行暴力,某一元素已经收敛时,这个元素在后续操作之后可以就不再访问。

因此,我们需要一个数据结构去排除这些不需要访问的元素,可以使用并查集、线段树、树状数组二分等做法。

当然,也可以当STL战神!想到删除操作,最容易想到的是map,将第i个元素存在map[i]中,map[i]收敛时,直接删除map[i]。对每一个操作,使用lower_bound操作可以快速找到第一个需要修改的元素,从此处开始遍历。

注意:map的迭代器需要正确使用,否则容易段错误!

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        int n,m;
        cin>>n>>m;
        LL sum = 0;
        map<int,int> a;
        for(int i=1;i<=n;i++){
            int x;
            cin>>x;
            a[i] = x;
            sum += x;
        }
        a[n+1] = 1;
        a[-1] = 1;
        while(m--){
            int t;
            cin>>t;
            if(t==2) cout<<sum<<endl;
            else{
                int l,r,k;
                cin>>l>>r>>k;
                for(auto it=a.lower_bound(l);it->first<=r;it++){
                    auto &[i,y] = *it;
                    for(int j=1;j<=k;j++){
                        int x = round(sqrt(y)*10);
                        sum -= y;
                        sum += x;
                        if(x==y){
                            it = a.erase(it);
                            it--;
                            break;
                        }
                        y = x;
                    }
                }
            }
        }
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60362566

H 本题主要考察了DFS

签到题

诈骗题,仔细读题可以发现,只需要计算面积,总面积是 n×n×10n \times n \times 10 ,那么把每一个拼图面积和计算出来,再用总面积一减即可。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    cin>>T;
    while(T--){
        int n,m;
        cin>>n;
        int sum = 0;
        for(int i=1;i<=n*n-1;i++){
            string s;
            cin>>s;
            sum += 10 - count(s.begin(),s.end(),'1') + count(s.begin(),s.end(),'2');
        }
        cout<<n*n*10-sum<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60308439

I 本题也主要考察了DFS

hard题

构造题,看完后没想到(没想),听了讲题发现很简单,待会打原神深渊了,明天再写(发出咕咕咕的声音),开摆!

J 本题竟也主要考察了DFS

hard题

居然真是DFS!不会,讲题听不懂,开摆!

K 本题主要考察了dp

简单题

居然真的是DP,据说有 O(1)O(1) 做法、贪心做法,但我没有脑子。

考虑前 ii 个数字,共有 jj 个1,最后两个数字是什么,也就是 dp[n][m][2][2]dp[n][m][2][2] ,然后考虑最后加入的是1还是0时的转移:

            dp[i][j][0][0] = min(dp[i-1][j][0][0]       , dp[i-1][j][1][0]);
            
            dp[i][j][0][1] = min(dp[i-1][j-1][0][0]     , dp[i-1][j-1][1][0] + 1);
            
            dp[i][j][1][0] = min(dp[i-1][j][0][1]       , dp[i-1][j][1][1] + 1);
            
            dp[i][j][1][1] = min(dp[i-1][j-1][0][1] + 1 , dp[i-1][j-1][1][1] + 1);

最后在dp[n][m][x][y]中找到最小的即可。

注意事项:特判 nn 为1的情况

代码如下:

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

int main(){
    int T = 1;
    while(T--){
        int n,m;
        cin>>n>>m;
        if(n<3){
            cout<<0<<endl;
            break;
        }
        vector dp(n+1,vector(n+1,vector(2,vector<int>(2,1e9))));
        dp[2][0][0][0] = 0;
        dp[2][1][0][1] = 0;
        dp[2][1][1][0] = 0;
        dp[2][2][1][1] = 0;
        for(int i=3;i<=n;i++){
            for(int j=0;j<=m;j++){
                dp[i][j][0][0] = min(dp[i-1][j][0][0] , dp[i-1][j][1][0]);
                if(j>0) dp[i][j][0][1] = min(dp[i-1][j-1][0][0] , dp[i-1][j-1][1][0] + 1);
                dp[i][j][1][0] = min(dp[i-1][j][0][1] , dp[i-1][j][1][1] + 1);
                if(j>0) dp[i][j][1][1] = min(dp[i-1][j-1][0][1] + 1 , dp[i-1][j-1][1][1] + 1);
            }
        }
        cout<<min({dp[n][m][0][0] , dp[n][m][0][1] , dp[n][m][1][0] , dp[n][m][1][1]})<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60289436

L 本题主要考察了运气

签到题

看不懂题,我很抱歉,直接从1开始一个个交或者直接rand!

M 本题主要考察了找规律

波门题

暴力DP,出题人说:本题没有规律,就把用来打表的DP当作std了。

数据范围只有500,考虑二维DP,已经把仙贝分给了 ii 个朋友(屑贝斯手),剩余 jj 个仙贝。

转移时考虑给最后一个朋友分了几个仙贝,贡献为 kk ,转移就是(有点怪的转移,但是能过):

dp[i+1][j-k] = max(dp[i+1][j-k] , dp[i][j] + k*1.0/j);

最后剩余的仙贝一定是0,因为把剩余所有仙贝都分给最后一个朋友肯定比只分一部分更优(显然),答案就是 dp[n][0]dp[n][0]

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LD = long double;

int main(){
    int T = 1;
    while(T--){
        int n,m;
        cin>>n>>m;
        vector dp(n+1,vector<LD>(m+1));
        for(int i=0;i<n;i++){
            for(int j=1;j<=m;j++){
                for(int k=0;k<=j;k++){
                    dp[i+1][j-k] = max(dp[i+1][j-k] , dp[i][j] + k*1.0/j);
                }
            }
        }
        printf("%.10Lf",dp[n][0]);
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60303868

一定要看《孤独摇滚》!

一定要看《孤独摇滚》!!

一定要看《孤独摇滚》!!!

一定要看《孤独摇滚》!!!!

一定要看《孤独摇滚》!!!!!

一定要看《孤独摇滚》!!!!!!

一定要看《孤独摇滚》!!!!!!!

一定要看《孤独摇滚》!!!!!!!!

一定要看《孤独摇滚》!!!!!!!!!

一定要看《孤独摇滚》!!!!!!!!!!

一定要看《孤独摇滚》!!!!!!!!!!!

一定要看《孤独摇滚》!!!!!!!!!!!!

alt

全部评论
嘤嘤!
点赞 回复 分享
发布于 2023-01-17 07:40 山东
E题两边长度相等直接输出no就行了,不一样就让长短边对应,然后判断一下叉积的符号是否相等就行了
点赞 回复 分享
发布于 2023-01-17 11:45 江西
G题有无分块写法,我写了半天还是T了
点赞 回复 分享
发布于 2023-01-17 20:46 浙江

相关推荐

点赞 评论 收藏
分享
Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
10-11 15:42
皖西学院 Java
青鱼LINK:我硕士,也是java0面试,吾道不孤
点赞 评论 收藏
分享
21 1 评论
分享
牛客网
牛客企业服务