牛客2021暑假多校训练营1

原题链接

https://ac.nowcoder.com/acm/contest/11166


A.Alice and Bob

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

Alice and Bob like playing games. There are two piles of stones with numbers \(n\) and \(m\). Alice and Bob take turns to operate, each operation can take away \(k(k>0)\) stones from one pile and take away \(s \times k(s \geq 0)\) stones from another pile. Alice plays first. The person who cannot perform the operation loses the game.

Please determine who will win the game if both Alice and Bob play the game optimally.

输入描述:

The first line contains an integer \(T(1 \le T \le 10^4)\) denotes the total number of test cases.
Each test case contains two integers \(n,m(1 \le n,m \leq 5 \times 10^3)\) in a line,indicating the number of two piles of stones.

输出描述:

For each test case, print "\(Alice\)" if Alice will win the game, otherwise print "\(Bob\)".

输入

5
2 3
3 5
5 7
7 5
7 7

输出

Bob
Alice
Bob
Bob
Alice

题目大意

给你两堆数量为 \(n,m\) 的石子,每次操作可以从其中一堆石子拿走 \(k(k>0)\) 个石子,从另外一堆石子拿走 \(k\times s(s \geq 0)\) 个石子,问最后谁能把石子拿完。

解题思路

博弈论,sg函数

  • 暴力seg

依题意,\(sg(i,j)=mex(sg(i-k,j-s \times k),sg(i-s\times k,j-k))\),类似于 SDOI2009 这题,我们可以用\(bitset(i,j)\)存储\(sg(i,j)\)的mex元素,可以得到

\[s(i,j).set(sg(i-k,j-s\times k)=mex(s(i-k,j-s\times k) )) \]

\[s(i,j).set(sg(i-s\times k,j-k)=mex(s(i-s\times k,j-k) )) \]

  • 时间复杂度:\(o(n^3log(n))\)
    打表代码:
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int sg[N][N];
bitset<300>s[N][N];
int mex(bitset<300> b)
{
    int i=0;
    while(b[i])i++;
    return i;
}
int main()
{
    for(int i=0;i<=100;i++)
        for(int j=0;j<=100;j++)
        {
            for(int k=1;k<=i;k++)
                for(int ss=0;ss<=j/k;ss++)
                    s[i][j].set(sg[i-k][j-ss*k]=mex(s[i-k][j-ss*k]));
            for(int k=1;k<=j;k++)
                for(int ss=0;ss<=i/k;ss++)
                    s[i][j].set(sg[i-ss*k][j-k]=mex(s[i-ss*k][j-k]));
        }
    for(int i=0;i<=50;i++)
    {
        for(int j=0;j<=50;j++)
            printf("%3d ",sg[i][j]);
        putchar('\n');
    }
    return 0;
}


打表结果:
image
没有规律,但是可以发现,为 \(0\) 的数很少,且每列至多一个 \(0\) ,于是,我们有如下结论:

  • 如果某堆石子数量是 i,另一堆石子最多只有一种数量满足后手胜。
    证明:
    反证法:
    假设对于一堆 \(i\) 个的石子,另外一堆石子存在两个数量 \(p\)\(q(p>q)\) ,使得后手胜,即 \(sg(i,p)=sg(i,q)=0\) ,而 \(sg(i,p)\) 可以转移到 \(sg(i,q)\) (\(i\) 堆石子不取,\(p\) 堆石子取走 \(p-q\) 个),这与 \(sg(i,q)\) 后手胜矛盾,证毕。
  • 优化sg
    我们可以利用上面这个结论,即每次循环 \(i,j\) 时,\(sg(i,j)=0\) 时进入循环,这样时间复杂度可以降为:

\[o(n^2log(n)) \]

但是注意枚举的时候与暴力枚举不同,每次只用枚举必败点之后的必胜点,由必败点向必胜点转移就行~

代码

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
bitset<N> sg[N];
int t,n,m;
//预处理sg函数
void init()
{
    for(int i=0;i<=5000;i++)
        for(int j=0;j<=5000;j++)
            if(!sg[i][j])
            {
                //由必败点向必胜点转移,若枚举前面的必胜点,则会重复枚举,超时~
                for(int k=1;i+k<=5000;k++)
                    for(int s=0;j+s*k<=5000;s++)
                        sg[i+k][j+s*k]=1;
                for(int k=1;j+k<=5000;k++)
                    for(int s=0;i+s*k<=5000;s++)
                        sg[i+s*k][j+k]=1;
            }
}
int main()
{
    init();
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        puts(sg[n][m]?"Alice":"Bob");
    }
    return 0;
}


B.Ball Dropping

简单平面几何……

题目大意

一个球卡在一个直角等腰梯形内部,求卡着的高度。
image

解题思路

image

代码

#include <bits/stdc++.h>
using namespace std;
long double y,r,a,b,h;
int main()
{
    cin>>r>>a>>b>>h;
    if(b>2*r)
        puts("Drop");
    else
    {
        puts("Stuck");
        long double x=b*h/(a-b);
        printf("%.10Lf\n",sqrt(4*r*r*(h+x)*(h+x)/(a*a)+r*r)-x);
    }
    return 0;
}

C.Cut the Tree

时间限制:C/C++ 4秒,其他语言8秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld

题目描述

Assume we walk along the shortest path from node \(u\) to node \(v\) in the weighted tree \(T\). List the values of passing nodes (including \(u\) and \(v\)) on the sequence in order, and denote \(F_{u,v}\) as the length of Longest Increasing Subsequence for that sequence. Further denote \(F_{max}\) as:

\[F_{max}(S)=\max \limits_{u,v \in S}F_{u,v}(S) \]

After removing node \(k\) in an unrooted tree \(S\), we collect the remaining subtrees as a set and call it \(S_k\).
Denote \(G_k(S)\) as:

\[G_k(S)=\max \limits_{T \in S_k}F_{max}(T) \]

Now you are given a weighted tree \(S\) with \(n\) nodes, you should determine a node \(k\) so that the value of \(G_k(S)\) is minimum. In other words, please calculate:

\[G_{min}(S)=\min \limits_{k \in S} \]

输入描述:

The first line of input contains one integer \(n(2\leq n\leq 10^5)\), indicating the number of nodes.

In the next \(n-1\) lines, there are two integers \((u_i,v_i)(1 \le u_i, v_i \le n)\) in a line, describing the \(i\)-th edge on the tree.

There are \(n\) integers \(a_1,\dots,a_n(1 \le a_i \le n)\) in the last line, indicates the value on the node \(i\).

输出描述:

Output one integer in a line, indicating \(G_{min}(S)\).

输入

6
1 2
2 3
3 4
4 5
5 6
1 2 3 4 5 6

输出

3

D.Determine the Photo Position

简单模拟~

题目大意

给定一个 \(n\times n\) 的01矩阵和 \(1\times m\) 的矩阵,要求将 \(1\times m\) 的矩阵平移到 \(n\times n\)矩阵的0的位置,求方案数

解题思路

行与行之间并无联系,设置一个计数器cnt,枚举每一行的 \(0\) ,遇 \(0\)\(cnt++\),同时判断是否 \(cnt \geq m,\)是,则方案数++;否则遇 \(1\),则 \(cnt=0\)

代码

#include<bits/stdc++.h>
using namespace std;
int n,m,res;
char s[2005];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%s",s);
        int num=0;
        for(int j=0;j<n;j++)
        {
            if(s[j]=='1')
                num=0;
            else
            {
                num++;
                res+=num>=m;
            }
        }
    }
    scanf("%s",s);
    printf("%d",res);
    return 0;
}

E.Escape along Water Pipe

时间限制:C/C++ 3秒,其他语言6秒
空间限制:C/C++ 524288K,其他语言1048576K
Special Judge, 64bit IO Format: %lld

题目描述

You're trapped into a map with \(n \times m\) grids, each grid has a strange water pipe.

There are six types of water pipes, denote them as ID \(0,1,…,5\).
image
You are located at the top of grid \({(1,1)}\) and you want to reach the bottom of grid \({(n,m)}\). In each step, you should travel along the pipe and move to another grid.

image

Before each move, you can do beautiful magic once: pick one of the degrees from \(\{{0, 90, 180, 270}\}\), select any number of grids except the one you are located at, and rotate their water pipes with the same degree you pick in the clockwise direction. Note that after you step in a grid along the water pipe, you must walk along the other end of the pipe to leave this grid.

You may step in the same grid multiple times, and the direction can be different every time you enter.

Determine whether you can escape from the entrance to the exit successfully.

输入描述:

There are multiple test cases. The first line of the input contains an integer \(T(1 \le T \le 10000)\), indicating the number of test cases. For each test case:

The first line contains two integers \(n,m(2 \le n,m \le 1000)\) indicating the size of the map.

In the next \({n}\) lines, there are \({m}\) integers \(a_{i,j}(0 \le a_{i,j} \le 5)\), indicating the ID of water pipe in each grid.

It's guaranteed that the sum of \(n \times m\) over all test cases does not exceed \(10^6\).

输出描述:

For each test case, output 'YES' if you can escape from the map, otherwise output 'NO'.

If the answer is YES, then output a valid solution from the top of \({(1,1)}\) to the bottom of \({(n,m)}\). You should first output an integer \(k(k \le 20nm)\) indicating the length of your operations, and then output \({k}\) lines to describe your escaping line.

If you try to rotate some cells, first output two integers \(t,d(0 < t \le nm,d \in \{0,90,180,270\})\), indicating the number of cells and the degree you rotate, and then output \({2t}\) integers \((x_1,y_1,x_2,y_2,\dots,x_t,y_t)\) , indicating the cells you rotate. Two consecutive rotation requests are not allowed.

If you try to step into another cell, output three integers \({(0,x,y)}\), indicating the next cell you pass by. You should assure that the first pair of \({(x,y)}\) is \({(1,1)}\), the last pair of \({(x, y)}\) is \({(n, m)}\), and your direction when stepping into the last cell is downward.

If there are multiple solutions, you can print any of them.

Your submission is not accepted if the total number of \({(x,y)}\) you output (including the cells you rotate and the cells you pass by) exceeds \({20nm}\).

输入

2
2 2
4 5
0 1
2 2
0 1
2 3

输出

YES
5
2 90 1 1 2 1
0 1 1
0 2 1
2 180 1 2 2 2
0 2 2
NO

题目大意

水管迷宫,沿着水管从 \((0,1)\) 开始走, 每走一步,可以对水管旋转一定角度,问最后能否走到 \((n+1,m)\), 如能,求路线

解题思路

记忆化bfs

预处理
image
利用bfs有一个优势:最短路径,可以满足题目的要求:操作次数 \(k \le 20n\times m\)
1.在进行bfs的时候,维护一个记忆数组 \(vis[i][j][d]\) ,具体含义为:在 \((i,j)\) 处向 \(d\) 方向走一步,即为来时的方向,\(vis[i][j][d]\) 记录接下来的移动方向,同时该数组也可作为标记访问数组。另外维护该数组的时候需要注意水管形状的特殊性
2.bfs后,利用 \(vis\) 数组,从 \((n+1,m)\) 开始反向走,\(vis[i][j][d]\) 可以确定水管形状 \(k\),原水管形状 \(k'\) ,旋转角度即为 \((k'-k+4)\pmod 4 \times 90^\circ\) ,记录路径后反转即可

但是wa了……

错误代码

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int t,n,m,a[N][N];
int vis[N][N][5];//vis数组记录向下的轨迹
int dx[]={0,-1,0,0,1},dy[]={0,0,-1,1,0};//上,左,右,下
struct node
{
    int x,y,d;//记录轨迹:(x+dx[d],y+dy[d])->(x,y)
};
struct point
{
    int x,y;
}path[N*N];
int angle[N*N];
queue<node>q;
void bfs()
{
    while(q.size())
        q.pop();
    q.push({1,1,1});
    while(q.size())
    {
        auto [x,y,d]=q.front();
        q.pop();
        if(x==n&&y==m&&((a[n][m]<=3&&d==2)||(a[n][m]>3&&d==1)))
        {
            if(a[n][m]<=3)
                vis[n][m][2]=4;
            else
                vis[n][m][1]=4;
            break ;
        }
        if(a[x][y]<=3)
        {
            for(int i=1;i<=4;i++)
            {
                if(i==d||i+d==5)continue;//不能返回原方向,同时由于是弯曲的水管,不能走直线
                int nx=x+dx[i],ny=y+dy[i];
                if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&vis[x][y][d]==-1)
                {
                    vis[x][y][d]=i;
                    q.push({nx,ny,5-i});
                }
            }
        }
        else
        {
                    int nx=x+dx[5-d],ny=y+dy[5-d];//直水管,直接走
                    if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&vis[x][y][d]==-1)
                        vis[x][y][d]=5-d,q.push({nx,ny,d});
        }
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&a[i][j]);
                memset(vis[i][j],-1,sizeof vis[i][j]);
            }
        bfs();
        if((a[n][m]<=3&&vis[n][m][2]!=-1)||(a[n][m]>3&&vis[n][m][1]!=-1))
        {
            int cnt=0;
            puts("YES");
            int x=1,y=1;
            int td,d,dd=1,tdd=1,k,kk;//d记录前进方向,dd记录后退方向,k记录转换后的水管编号
            //kk记录现在的水管编号
            while(!(x==n+1&&y==m))
            {

                path[++cnt]={x,y};
                kk=a[x][y];
                td=d=vis[x][y][dd];
                if(td>tdd)swap(td,tdd);
                if(td==1&&tdd==2)k=0;
                if(td==1&&tdd==3)k=1;
                if(td==3&&tdd==4)k=2;
                if(td==2&&tdd==4)k=3;
                if(td==2&&tdd==3)k=4;
                if(td==1&&tdd==4)k=5;
                angle[cnt]=90*((k-kk+4)%4);
                tdd=dd=5-d;
                x=x+dx[d],y=y+dy[d];//前进
            }
            printf("%d\n",2*cnt);
            for(int i=1;i<=cnt;i++)
            {
                printf("1 %d %d %d\n",angle[i],path[i].x,path[i].y);
                printf("0 %d %d\n",path[i].x,path[i].y);
            }
        }
        else
            puts("NO");
    }
    return 0;
}

为什么?

因为我们走的时候是往下走的,而往下走是有很多个选择的,不一定是我们思路要求的唯一正确路径,而且代码失误的地方还有一点:
image
bfs时,此处 \(vis[x][y][d]\) 更新后没有再遍历其他方向,显然不对
所以,我们应该:
1.从下往上遍历
2.\(vis[i][j][d]\) 记录向上的方向,其中 \(d\) 为从 \((i,j)\) 到其他点的反方向
3.最为关键的一点,水管类型没有更新,因为可能返回到同一个方格,如下:
image
假设我们存在唯一一条路径,则红色方框会经过两次
最后,如果我们用数组 \(path\)\(angle\) 记录路径以及方向会出错,容易爆,尽量能不用数组就不用或少用

  • 时间复杂度:\(o(n^2)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int t,n,m,a[N][N];
int vis[N][N][5];//vis数组记录向上的轨迹
int dx[]={0,-1,0,0,1},dy[]={0,0,-1,1,0};//上,左,右,下
struct node
{
    int x,y,d;//记录轨迹:(x,y)->(x+dx[d],y+dy[d])
};
queue<node>q;
vector<int>op;//op记录方向操作
void bfs()
{
    while(q.size())
        q.pop();
    q.push({1,1,1});
    while(q.size())
    {
        auto [x,y,d]=q.front();
        q.pop();
        if(x==n&&y==m&&((a[n][m]<=3&&d==2)||(a[n][m]>3&&d==1)))
        {
            vis[n][m][1]=d;
            break ;
        }
        if(a[x][y]<=3)
        {
            for(int i=1;i<=4;i++)
            {
                if(i==d||i+d==5)continue;//不能返回原方向,同时由于是弯曲的水管,不能走直线
                int nx=x+dx[i],ny=y+dy[i];
                if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&vis[x][y][5-i]==-1)
                {
                    vis[x][y][5-i]=d;
                    q.push({nx,ny,5-i});
                }
            }
        }
        else
        {
                    int nx=x+dx[5-d],ny=y+dy[5-d];//直水管,直接走
                    if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&vis[x][y][d]==-1)
                        vis[x][y][d]=d,q.push({nx,ny,d});
        }
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&a[i][j]);
                memset(vis[i][j],-1,sizeof vis[i][j]);
            }
        bfs();
        if((a[n][m]<=3&&vis[n][m][1]==2)||(a[n][m]>3&&vis[n][m][1]==1))//到达方格(n,m),且弯水管或直水管可返回
        {
            op.clear();
            puts("YES");
            int x=n,y=m;//从终点(n,m)开始后退
            op.push_back(4);//从(n+1,m)到(n,m)是向上操作,记录反方向
            int d,dd=1;
            while(!(x==1&&y==1))
            {
                d=vis[x][y][dd];
                op.push_back(5-d);//记录的是反方向,便于从上往下
                x=x+dx[d],y=y+dy[d];
                dd=d;//后退
            }
            printf("%d\n",2*op.size());
            op.push_back(4);//从(1,1)到(0,1)是向上操作,记录反方向
            reverse(op.begin(),op.end());//反转,从上往下遍历
            x=0,y=1;
            for(int i=1;i<op.size();i++)
            {
                int nx=x+dx[op[i-1]],ny=y+dy[op[i-1]];
                int now=op[i],last=5-op[i-1];
                int angle,k;//旋转角度,旋转后的水管类型
                if(now>last)swap(now,last);
                if(now==1&&last==2)k=0;
                if(now==1&&last==3)k=1;
                if(now==3&&last==4)k=2;
                if(now==2&&last==4)k=3;
                if(now==2&&last==3)k=4;
                if(now==1&&last==4)k=5;
                angle=90*((k-a[nx][ny]+4)%4);
                a[nx][ny]=k;//更新水管类型
                printf("1 %d %d %d\n",angle,nx,ny);
                printf("0 %d %d\n",nx,ny);
                x=nx,y=ny;
            }
        }
        else
            puts("NO");
    }
    return 0;
}

F.Find 3-friendly Integers

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

A positive integer is 3-friendly if and only if we can find a continuous substring in its decimal representation, and the decimal integer represented by the substring is a multiple of \(3\).

For instance:
\({104}\) is 3-friendly because "0" is a substring of "104" and \(0 \mod 3 = 0\).
\({124}\) is 3-friendly because "12" is a substring of "124" and \(12 \mod 3 = 0\). "24" is also a valid substring.
\({17}\) is not 3-friendly because \(1\mod 3 \ne 0\), \(7 \mod 3 \ne 0\), \(17 \mod 3 \ne 0\).

Note that the substring with leading zeros is also considered legal.

Given two integers \({L}\) and \({R}\) ,you are asked to tell the number of positive integers \({x}\) such that \(L \le x \le R\) and \({x}\) is 3-friendly.

输入描述:

There are multiple test cases. The first line of the input contains an integer \(T(1 \le T \le 10000)\), indicating the number of test cases. For each test case:

The only line contains two integers \(L,R(1 \le L \le R \le 10^{18})\), indicating the query.

输出描述:

For each test case output one line containing an integer, indicating the number of valid \({x}\).

输入

3
4 10
1 20

输出

3
11
76
1 100

题目大意

给出一种数的定义:存在一个该数的子串为 \(3\) 的倍数。问一个范围内的这种数有多少个

解题思路

posted @ 2021-07-18 12:14  zyy2001  阅读(220)  评论(0编辑  收藏  举报