• ZJNU 20220226 个人排位赛1 部分题解


    2022/02/27 UPD:

    • 更正 \(C\) 题公比少了平方
    • 开头补充 INF 的数值
    • \(I\) 题补充特判


    写在前面:

    文中使用到的宏定义等参照此处,下文不再重复出现

    #define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
    #define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
    #define rep(i,a,b) for(auto i=(a);i<=(b);i++)
    #define repp(i,a,b) for(auto i=(a);i<(b);i++)
    #define per(i,a,b) for(auto i=(a);i>=(b);i--)
    #define perr(i,a,b) for(auto i=(a);i>(b);i--)
    #define REP(i,a,b,s) for(auto i=(a);i<=(b);i+=s)
    #define REPP(i,a,b,s) for(auto i=(a);i<(b);i+=s)
    #define PER(i,a,b,s) for(auto i=(a);i>=(b);i-=s)
    #define PERR(i,a,b,s) for(auto i=(a);i>(b);i-=s)
    #define mst(a,b) memset(a,b,sizeof(a))
    #define pb push_back
    typedef long long ll;
    typedef pair<int,int> pii;
    const int INF=0x3f3f3f3f;
    




    A - An (Almost) Perfect Match

    没读,题解好像是记忆化搜索




    B - Good Coin Denomination

    给定已按升序排序的至多 \(10\) 个数字 \(\{a\}\),问是否满足 \(2a_i\le a_{i+1}\)

    直接判断

    void solve()
    {
        int n;
        cin>>n;
        int a[n];
        cout<<"Denominations:";
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
            cout<<' '<<a[i];
        }
        cout<<'\n';
        for(int i=1;i<n;i++)
            if(a[i]<a[i-1]*2)
            {
                cout<<"Bad coin denominations!\n\n";
                return;
            }
        cout<<"Good coin denominations!\n\n";
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            solve();
        }
        return 0;
    }
    
    




    C - Cameron’s Crazy Circles

    给定直角三角形的两直角边 \(a,b\ (a\lt b)\)

    定义其第三条边为 \(c\),并绘制出其内接三角形 \(C_1\)

    再沿着较长的直角边 \(b\) 继续绘制与 \(b,c\)\(C_1\) 相切的内圆 \(C_2\)

    pic_c1

    如此循环下去,绘制出无限个圆 \(C_i\)

    问所有绘制出的圆的面积与该三角形的面积之比,即 \(\frac{\sum C_i}{S_{\triangle}}\)

    首先求出最大的内接圆的半径 \(R\),由于是直角三角形,所以公式直接得出 \(R=\frac{a+b-c}2\)

    (不知道这条公式的就先求两条角平分线,得出交点后来算 \(R\) 吧,见代码)

    根据勾股定理,算得所有可以绘制出的圆的直径和 \(totLen=\sqrt{R^2+(b-R)^2}+R\)

    pic_c2

    转为另一种方式看 \(C_1,C_2\) 两个圆,绘制出两相似等腰三角形,见下图蓝线

    pic_c3

    可得小三角形与大三角形的边长比即 \(ratio = \frac{totLen-2R}{totLen} = 1-\frac{2R}{totLen}\)

    由圆形面积公式 \(S_1=\pi R^2\) ,可知面积与边长呈平方关系

    \(C_2\) 的半径 \(R_2=R\times ratio\),圆 \(C_2\) 的面积即 \(S_2=\pi R_2^2=\pi R^2ratio^2=ratio^2S_1\)

    无限绘制圆形,比例不变,得 \(S_{i+1}=ratio^2S_i\)

    这是一个首项为 \(S_1=\pi R^2\),公比为 \(ratio^2\) 的等比数列

    \(\sum S_i = \frac {S_1(1-ratio^{\infin})}{1-ratio^2} = \frac{S_1}{1-ratio^2}\)

    答案即 \(\frac{2\sum S}{a\times b}\)

    void solve()
    {
        double a,b;
        cin>>a>>b;
        
        Point p1=Point(0,0);
        Point p2=Point(0,a);
        Point p3=Point(b,0);
        
        Vector v21=p1-p2;
        Vector v23=p3-p2;
        Vector v2f=Rotate(v21,Angle(v21,v23)/2); // 旋转一半求角平分向量,板子太长不放了
        
        Line la=Line(p1,Point(1,1));
        Line lb=Line(p2,p2+v2f);
        Point cross=la.intersectPoint(lb); // 求出内接圆圆心
        
        double R=cross.x; // R=(a+b-c)/2 ,上面可以都不用写
        double S=PI*R*R;
        
        double totLen=sqrt(R*R+(b-R)*(b-R))+R;
        double rt=1-R*2/totLen;
        
        double totS=S/(1-rt*rt);
        cout<<fixed<<setprecision(4)<<totS/(a*b/2)<<'\n';
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
            cout<<'\n';
        }
        return 0;
    }
    




    D - Matrix Transformation

    给定一个 \(n\times m\) 的矩形,每次可以选择任意两个相邻位置,令该两位置值同时 \(+1\)\(-1\)

    问是否存在一种方式能够使得矩形内每个位置的值均为 \(0\)

    按照每个位置的 \(x+y\) 的奇偶性进行分组

    由于每次选择的是两个相邻的位置,因此 \(x+y\) 的奇偶性一定不同

    又因为两个位置的值同时操作,且改变量相同,因此本题就变成了判断两组数值之和是否相同

    int a[111][111];
    
    void solve()
    {
        int n,m;
        cin>>n>>m;
        int s1=0,s2=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
                cin>>a[i][j];
            for(int j=i%2+1;j<=m;j+=2)
                s1+=a[i][j];
            for(int j=2-i%2;j<=m;j+=2)
                s2+=a[i][j];
        }
        if(s1==s2)cout<<"YES\n";
        else cout<<"NO\n";
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
            cout<<'\n';
        }
        return 0;
    }
    




    E - Cut the Cake!

    按相邻边的顺序给定一个多边形蛋糕,被 \(y=Y\) 的一条平行于 \(x\) 轴的直线切成上下两部分

    保证蛋糕一定会被切割到,且不会切割到蛋糕的任意顶点上

    问两部分蛋糕的周长分别是多长

    定义两个多边形容器 \(poly1/poly2\) 存两部分蛋糕的每个点

    从任意某条边开始,枚举所有边,并判断每条边是否会被 \(y=Y\) 切割

    如果不会被切割,则将上一个点归为当前的多边形容器内

    如果边 \(p_ip_{i+1}\) 被切割,交点为 \(pc\),则将 \(pc\) 放在当前多边形容器内,接下来切换为另一个容器来收集点(一旦产生交点即表示跨越了 \(y=Y\) 这条线,前一个点与后一个点应当归属于不同的多边形容器)

    pic_e

    如上图,按照顺序加入多边形的点如右侧所示(交点 \(pc_i\) 应当同时存在两个多边形中)

    得到的两个新多边形,可以直接通过两两相邻点距离之和求出周长

    Point p[10];
    vector<Point> poly[2];
    
    void solve()
    {
        for(int t=0;t<2;t++)
            poly[t].clear();
        
        int n,y;
        cin>>n>>y;
        Line ly=Line(Point(0,y),Point(1,y));
        for(int i=0;i<n;i++)
            cin>>p[i].x>>p[i].y;
        
        int cur=0;
        for(int i=0;i<n;i++) // 按顺序枚举每条边 p[i]p[(i+1)%n]
        {
            poly[cur].pb(p[i]);
            if((p[i].y>y)!=(p[(i+1)%n].y>y)) // 判断i与(i+1)%n两点是否位于y=Y这条线两侧
            {
                Point pp=Line(p[i],p[(i+1)%n]).intersectPoint(ly); // 获得交点
                poly[cur].pb(pp);
                cur^=1; // 切换另一个容器继续收集点
                poly[cur].pb(pp);
            }
        }
        
        double S[2]={0,0};
        rep(t,0,1)
            repp(i,0,poly[t].size()) // 遍历两个多边形容器内每条边,直接求距离和即可
                S[t]+=Distance(poly[t][i],poly[t][(i+1)%poly[t].size()]);
        
        if(S[0]>S[1])
            swap(S[0],S[1]);
        cout<<S[0]<<' '<<S[1]<<'\n';
    }
    signed main()
    {
        cout<<fixed<<setprecision(3);
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
            cout<<'\n';
        }
        return 0;
    }
    




    F - Camp Out

    每天 \(24\) 小时被分为 \(6\) 个时间段 \(0\sim 4,4\sim 8,\cdots,20\sim 24\) (左闭右开)

    你需要安排 \(10\) 个人,在一个星期 \(7\)\(168\) 个小时内搭帐篷,且需满足以下限制:

    • 每个时段至少有 \(3\) 个人搭帐篷
    • 只有整个时间段内 \(4\) 小时都有空的人才可以安排该时间段给他
    • 每个人至多被安排 \(80\) 个小时

    现在给定每个人没有空的时间段,问是否存在一种合法安排方案?

    标准二分图权值匹配题,下面考虑用最大流解(KM也可以)

    首先找出二分图两侧点的代表,明显一侧指人,一侧指时间段

    源点向每个人连边,流量为 \(20\),表示每个人至多被安排 \(20\) 个时间段

    每个时间段向汇点连边,流量为 \(3\),这里贪心让每个时间段只安排 \(3\) 人就行,最后判断网络是否能够流满即可

    对于每个人 \(i\),只要时间段 \(j\) 有空,就从 \(i\)\(j\) 连一条流量为 \(1\) 的边,表示该人可以安排时间段 \(j\)

    最后判断最大流是否等于 \(168/4*3=126\) 即可

    int bz[55];
    
    void solve()
    {
        init(54);
        rep(i,1,10)
            addedge(53,i,20);
        rep(i,11,52)
            addedge(i,54,3);
        rep(i,1,10)
        {
            mst(bz,0);
            int n;
            cin>>n;
            rep(j,1,n)
            {
                int d,s,e;
                cin>>d>>s>>e;
                s=s/4+(d-1)*6+11;
                e=(e-1)/4+(d-1)*6+11;
                rep(k,s,e)
                    bz[k]=1; //表示该时间段没空
            }
            rep(k,11,52)
                if(!bz[k])
                    addedge(i,k,1); //有空的连边
        }
        int f=dinic(53,54);
        //cout<<f<<'\n';
        cout<<(f==126?"YES\n":"NO\n");
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
            cout<<'\n';
        }
        return 0;
    }
    




    G - Sorry About That, Chief!

    判断给定数是否是素数,如果不是,问与最近的素数差值是多少

    素数筛,每个询问暴力即可

    bool vis[22222];
    void init()
    {
        rep(i,2,20000)
            if(!vis[i])
                REP(j,i*i,20000,i)
                    vis[j]=true;
    }
    
    void solve()
    {
        int n;
        cin>>n;
        cout<<"Input value: "<<n<<'\n';
        if(!vis[n])
            cout<<"Would you believe it; it is a prime!\n\n";
        else
        {
            rep(i,1,1000)
            {
                if(!vis[n-i]||!vis[n+i])
                {
                    cout<<"Missed it by that much ("<<i<<")!\n\n";
                    return;
                }
            }
        }
    }
    signed main()
    {
        closeSync;
        init();
        multiCase
        {
            solve();
        }
        return 0;
    }
    




    H - Knight Moves – Gold Edition

    I - Knight Moves – Black Edition

    \(n\times n\) 的国际象棋棋盘上,骑士从 \((r_1,c_1)\) 走到 \((r_2,c_2)\) 的最少步数

    EZ解

    bfs

    int d[25][25];
    const int ddx[8]={-2,-2,-1,-1,1,1,2,2};
    const int ddy[8]={-1,1,-2,2,-2,2,-1,1};
    
    void solve()
    {
        int n,r1,c1,r2,c2;
        cin>>n>>r1>>c1>>r2>>c2;
        mst(d,INF);
        d[r1][c1]=0;
        queue<pii> q;
        q.push(pii(r1,c1));
        while(!q.empty())
        {
            pii p=q.front();
            q.pop();
            repp(i,0,8)
            {
                int px=ddx[i]+p.first;
                int py=ddy[i]+p.second;
                if(px>0&&py>0&&px<=n&&py<=n&&d[px][py]>d[p.first][p.second]+1)
                {
                    d[px][py]=d[p.first][p.second]+1;
                    q.push(pii(px,py));
                }
            }
        }
        cout<<d[r2][c2]<<'\n'<<'\n';
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
        }
        return 0;
    }
    

    HD解

    大范围贪心 + 小范围暴力bfs

    为了方便,根据对称轴翻转,使得 \((r_1,c_1)\) 出现在 \((r_2,c_2)\) 的左下角

    pic_i1

    其后再根据点 \((r_1,c_1)\) 位于那一块区域内进行分类讨论

    如果点位于上图区域 \(1\) 内,贪心可知刚开始每次都走 \((+2,+1)\) 最优,直到 \(c1=c2\) 时,再反复 \((+2,-1),(+2,+1)\) 向右走,如下图所示:

    pic_i2

    如果点位于上图区域 \(4\) 内,贪心可知刚开始每次都走 \((+1,+2)\) 最优,直到 \(r1=r2\) 时,再反复 \((-1,+2),(+1,+2)\) 向上走,如下图所示:

    pic_i3

    如果位于上图区域 \(2\) 内,可以发现先走 \((+1,+2)\),直到与 \(y=\frac12x\) 这条直线接触时再一直走 \((+2,+1)\) 最优,但这样的走法可能比较难处理?

    这种走法实际上与先接触 \(y=x\) 这条直线,再反复 \((+1,+2),(+2,+1)\) 所需要的步数是相同的,也好写OvO

    区域 \(3\) 内也是相同,先靠近 \(y=x\) 再向右上走,如下图所示:

    pic_i4

    当然,以上也只是大范围贪心,小范围内这种走法可能会出问题

    因此,最终快要接近终点的时候,限制一下不要直接取到最近的位置,稍微留出个 \(20\times 20\) 大小的位置来跑 EZ 版本的代码即可

    int d[35][35];
    const int ddx[8]={-2,-2,-1,-1,1,1,2,2};
    const int ddy[8]={-1,1,-2,2,-2,2,-1,1};
    
    int solve2(int n,int r1,int c1,int r2,int c2)
    {
        mst(d,INF);
        d[r1][c1]=0;
        queue<pii> q;
        q.push(pii(r1,c1));
        while(!q.empty())
        {
            pii p=q.front();
            q.pop();
            repp(i,0,8)
            {
                int px=ddx[i]+p.first;
                int py=ddy[i]+p.second;
                if(px>0&&py>0&&px<=n&&py<=n&&d[px][py]>d[p.first][p.second]+1)
                {
                    d[px][py]=d[p.first][p.second]+1;
                    q.push(pii(px,py));
                }
            }
        }
        return d[r2][c2];
    }
    
    void solve()
    {
        ll n,r1,c1,r2,c2;
        cin>>n>>r1>>c1>>r2>>c2;
        
        // 如果范围较小,直接调用EZ Ver.的解法
        if(n<=20)
        {
            cout<<solve2(n,r1,c1,r2,c2)<<'\n';
            return;
        }
        
        //翻转点,使得点1位于点2左下,此后只需要考虑x++和y++
        if(r1>r2)
        {
            r1=n-r1+1;
            r2=n-r2+1;
        }
        if(c1>c2)
        {
            c1=n-c1+1;
            c2=n-c2+1;
        }
        
        ll ans=0;
        if(r2-r1>c2-c1) //如果x方向的增量比y方向的增量多,位于上图区域3,4
        {
            if(r1+(c2-c1)*2<=r2) //上图区域4
            {
                // 先 (+1,+2) 走到 c1=c2 的位置
                ll d=c2-c1;
                ans+=d;
                r1+=d*2;
                c1=c2;
                // 再反复向上,每两步可以使 y+4
                d=(r2-r1)/4-1; // 注意这里的 -1,为后面的暴力留出空间,下同
                if(d>0)
                {
                    ans+=d*2;
                    r1+=d*4;
                }
            }
            else //上图区域3
            {
                ll d=(r2-r1)-(c2-c1);
                ans+=d;
                r1+=d*2;
                c1+=d;
                
                d=min(r2-r1,c2-c1)/3-1;
                if(d>0)
                {
                    ans+=d*2;
                    r1+=d*3;
                    c1+=d*3;
                }
            }
        }
        else
        {
            if(c1+(r2-r1)*2<=c2) //上图区域1
            {
                ll d=r2-r1;
                ans+=d;
                c1+=d*2;
                r1=r2;
                
                d=(c2-c1)/4-1;
                if(d>0)
                {
                    ans+=d*2;
                    c1+=d*4;
                }
            }
            else //上图区域2
            {
                ll d=(c2-c1)-(r2-r1);
                ans+=d;
                c1+=d*2;
                r1+=d;
                
                d=min(r2-r1,c2-c1)/3-1;
                if(d>0)
                {
                    ans+=d*2;
                    r1+=d*3;
                    c1+=d*3;
                }
            }
        }
        
        // 最后差的一点点就暴力跑,特判边界
        if(r1<=4&&c1<=4) // 左下边界
            ans+=solve2(20,r1,c1,r2,c2);
        else if(r2>=n-3&&c2>=n-3) // 右上边界
            ans+=solve2(20,20-(n-r1),20-(n-c1),20-(n-r2),20-(n-c2));
        else
            ans+=solve2(20,10,10,10+(r2-r1),10+(c2-c1));
        cout<<ans<<'\n';
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
            cout<<'\n';
        }
        return 0;
    }
    




    J - A Prickly Problem – Gold Edition

    K - A Prickly Problem – Black Edition

    给定一张仙人掌图(两两简单环最多只有一个公共点)

    可以去除一些边使其变成一棵树,问生成树数量

    解1

    求生成树的数量,裸题

    矩阵树定理,求无向连通图生成树计数,套高斯消元板子即可

    没代码QAQ

    解2

    由于给定的图是仙人掌,明显答案就是每个简单环内的边数之乘积(由于生成树不存在环,因此每个简单环都可以任意选择一条边去掉)

    考虑点双连通分量,求出每条边所在的 bcc 编号,再统计出每个 bcc 包含的边数,求乘积即可

    没代码\(^2\)QAQ

    解3

    原理同解2,但可以直接根据 dfs 搜索出每个环内的边数

    从任意一个点开始搜索,为每个搜到的点分配深度 \(depth\)

    如果某个点此前没有被搜到,则朝下继续搜索

    如果已经被搜索到,则此时的搜索深度 \(d\) 与原先为该点分配的深度 \(depth\) 的差值即环内边数

    最后向上传递解即可

    int n,m;
    vector<int> G[50050];
    int dep[50050];
    bool vis[50050];
    
    int dfs(int u,int fa,int d)
    {
        if(vis[u])
            return max(d-dep[u],1);
        dep[u]=d;
        vis[u]=true;
        int r=1;
        for(int &v:G[u])
        {
            if(v==fa)
                continue;
            r=r*dfs(v,u,d+1)%mod;
        }
        return r;
    }
    
    void solve()
    {
        cin>>n>>m;
        rep(i,1,n)
        {
            G[i].clear();
            dep[i]=0;
            vis[i]=false;
        }
        rep(i,1,m)
        {
            int u,v;
            cin>>u>>v;
            G[u].pb(v);
            G[v].pb(u);
        }
        cout<<dfs(1,-1,1)<<'\n';
    }
    signed main()
    {
        closeSync;
        multiCase
        {
            cout<<"Case #"<<t<<": ";
            solve();
            cout<<'\n';
        }
        return 0;
    }
    
  • 相关阅读:
    暑假周总结六
    常用的Linux操作
    大数据概述
    实验一
    我对编译原理的看法
    ActiveReports中自定义Winforms的Viewer 工具栏
    ActiveReport 同一单元格内图片跟文字按条件显示
    ActiveReports最终报表设计器本地化方法介绍
    ActiveReports中如何使用Excel数据源
    如何设置WebViewer的参数栏显示状态
  • 原文地址:https://www.cnblogs.com/stelayuri/p/15940242.html
Copyright © 2020-2023  润新知