2023牛客多校第一场题解

2023暑期牛客多校第一场

能做的题 7题,赛场上 4题

链接:https://ac.nowcoder.com/acm/contest/57355#question

大致复盘:

两人队,计算几何没开亏麻了

L题的置换不是很容易看出来

M题思路是正确的,当时我的码力太烂了,没敢往下写

题解如下

签到题

D

给定一个 n × m n\times m n×m大小的巧克力,两个人轮流选择一个坐标 ( x , y ) (x,y) (x,y)将其左下角的矩形的巧克力吃掉(不能为空),吃到 ( n , m ) (n,m) (n,m)的人为负

给定 n , m n,m n,m问谁有必胜策略

要不是我做过,还真的挺难想的

考虑1*n的情况,先手必胜

当n*m时,先手可以先吃最左下角那一块,使得矩形缺角。先手只需要保证后手拿到的是矩形缺角的状态即可

所以只有1*1的情况下后手必胜

代码如下

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,m;
    cin>>n>>m;
    if(n==m&&n==1) cout<<"Walk Alone"<<endl;
    else cout<<"Kelin"<<endl;
    return 0;
}
J

题目大意:

Walk Alone 初始有 n 块钱,如果每次投 x 元,有一半的概率输掉这 x 元,另

一半概率赢得 2x 元。现在 Walk Alone 采取下述策略投注:

如果上一把赢了,这一把投 x i = 1 x_i=1 xi=1

如果上一把输了,这一把投 x i = 2 x i − 1 x_i=2x_{i-1} xi=2xi1 元。

问 Walk Alone 有多大概率拿到 n + m 元离开。

1 n,m ≤ 1e9。

思路:不难发现,每赢一次,不管前面输多少次,都是只赚一块钱

那么只需要考虑最多输多少轮即可

考虑如果现在有 x 元,找到最大的 r 满足 2 r − 1 ≤ x 2^r-1\le x 2r1x,从 x x x x + 1 x+1 x+1的概率就是 1 2 \frac{1}{2} 21为首项和公比的等比数列和 1 − ( 1 2 ) r 1-(\frac{1}{2})^r 1(21)r

n , m n,m n,m都是十的九次方的数量级,考虑优化,可以用类似整除分块(或者说相同的贡献直接乘起来)的思想

对于一个 r r r,对应一段 [ 2 r − 1 , 2 r + 1 − 2 ] [2^r-1,2^{r+1}-2] [2r1,2r+12] 的概率都一样,而且只有 l o g 2 n + m log_2^{n+m} log2n+m个这样的 r r r需要计算

总时间复杂度 O ( l o g 2 n ) O(log^2n) O(log2n)

代码如下

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int qpow(int a,int b=mod-2)
{
    int res=1;
    while(b)
    {
        if(b&1) res=(res*a)%mod;
        b>>=1;a=(a*a)%mod;
    }
    return res%mod;
}
void work()
{
    int n,m,ans=1;
    cin>>n>>m;
    m+=n;
    while(n<m)
    {
        int i=__lg(n+1);
        int r=min(m,((1ll<<(i+1))-1));
        (ans*=qpow(mod+1-qpow(2,mod-1-i),r-n))%=mod;
        n=r;
    }
    cout<<ans<<'\n';
}
signed main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	//cin>>t;
	while(t--)
		work();
	return 0;
}

一般题

M

赛场上最早出思路的题,但是却没写

题目大意:

给两个容积分别为 A*,*B 的水杯,每次可以执行以下的四种操作之一:

把其中一个水杯装满水。

把其中一个水杯中的全部水倒掉。

把其中一个水杯中现有的水全部喝完。

把一个杯子中的水尽可能转移到另一个水杯中,水不溢出。

Walk Alone 想喝恰好C单位的水,问最小操作次数

T 组询问,1 T 1e5,1 A*,*B 1e9

思路:很明显的exgcd

考虑不定方程Ax+By=C,倒A杯中的水操作可以看做x小于0,不可能既喝了A单位水又恰好倒了A单位水,显然是浪费步数

于是转变成了这个式子

s = { 2 x + 2 y x > 0 , y > 0 ∣ 2 x ∣ + ∣ 2 y ∣ − 1 x y < 0 s = \left\{ \begin{aligned} &2x+2y & & x>0,y>0 \cr &|2x|+|2y|-1 & & xy<0\cr \end{aligned} \right. s={2x+2y∣2x+∣2y1x>0,y>0xy<0

为了求 s s s的最小值

我们只需要利用扩展欧几里得算法求出一组特解,再代入通解公式算一下即可

代码如下

#include<bits/stdc++.h>
#define int long long
using namespace std;
int exgcd(long long a,long long b,long long &x,long long &y)
{
    if(a==0&&b==0) return -1;
    if(b==0) {x=1;y=0;return a;}
    long long d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
int f(int x,int y)
{
    if(x<0||y<0) return abs(2*x)+abs(2*y)-1;
    else return 2*x+2*y;
}
void work()
{
    int A,B,C,ans=0x7fffffff;
    cin>>A>>B>>C;
    int x,y;
    int d=exgcd(A,B,x,y);
    if(C%d) {cout<<-1<<endl;return;}

    x=x*C/d;y=y*C/d;
    A/=d;B/=d;C/=d;

    int t1=-x/B,t2=y/A,xx,yy;

    t1--;
    t2++;
    xx=x+B*t1;yy=y-A*t1;
    ans=min(ans,f(xx,yy));
    xx=x+B*t2;yy=y-A*t2;
    ans=min(ans,f(xx,yy));
	//这个times<=3很玄学,反正2不能过,3就过了
    int times=0;
    while(times<=3)
    {
        t1++;
        xx=x+B*t1,yy=y-A*t1;
        ans=min(ans,f(xx,yy));
        t2--;
        xx=x+B*t2,yy=y-A*t2;
        ans=min(ans,f(xx,yy));
        times++;
    }
    cout<<ans<<endl;
}
signed main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

K

赛场上思路一下就出来了,但是我TM写链式前向星的时候tot少打了一个t,调到我怀疑人生

题目大意

给定一个无向图 G ( n , m ) G(n,m) G(n,m),可以将其中任意一条边分裂成一条长度为任意的链(向边中插任意多个点),可以操作任意多次(也可以不操作)

问经过这样处理之后,从 1 号节点出发,至多走 k k k步最多可以到多少个节点。

数据范围:1 n 1e5,1 m 2e5,1 k 1e9

思路:贪心

一个很直观的贪心策略是

首先跑出这张图的最短路树,由于这张图的边权为1,那么其实就是这张图的BFS树了。

对于图上的非树边,显然可以将它们最大化利用,假设连接的两个点 u 1 , u 2 u1,u2 u1,u2的最短路径分别为 d i s [ u 1 ] , d i s [ u 2 ] dis[u1],dis[u2] dis[u1],dis[u2],那么加上 k − d i s [ u 1 ] k-dis[u1] kdis[u1] k − d i s [ u 2 ] k-dis[u2] kdis[u2]个点即可,不会影响其他点的最短路径

对于图上的树边,显然深度越深的点边对于最短路径的影响是最小的。因为如果一个点连了 k k k棵子树,那么在这个点的前驱边上加一个节点,会影响所有子树以及子树上点连的非树边。

所以对于树边,我们在叶子节点的前驱边上添加点。

但需要考虑叶子节点有没有连非树边,如果连了非树边的话,那么就没必要再考虑这条树边了,因为这个叶子节点已经在考虑非树边的时候被充分利用过了

代码如下

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+5;
int ans=0;
struct Edge
{
    int next;int to;bool sel;
}edge[maxn];
int head[maxn],n,m,k,tot;

void addedge(int from,int to)
{
    edge[++tot].next=head[from];
    edge[tot].to=to;
    head[from]=tot;
}
queue<int> q;
bool vis[maxn];
int dis[maxn];
void bfs(int s)
{
    q.push(s);dis[s]=0;vis[s]=true;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(!vis[v])
            {
                dis[v]=dis[u]+1;q.push(v);
                vis[v]=true;edge[i].sel=true;
                //printf("u=%d,v=%d,i=%d\n",u,v,i);
            }
        }
    }
}

void dfs(int u,int fat)
{
	//printf("dfsu=%d\n",u);
    int childs=0;ans++;
    bool hase=false;
    for(int i=head[u];i;i=edge[i].next)
    {

        int v=edge[i].to;
        if(v==fat) continue;
        if(!edge[i].sel) 
			ans+=max(0ll,k-dis[u]),hase=true;
        
        if(edge[i].sel && dis[v]<=k) dfs(v,u);

        childs++;
    }
    if(!childs&&u!=1&&!hase) ans+=max(0ll,k-dis[u]);
}

void work()
{
    int x,y;
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        addedge(x,y);addedge(y,x);
    }
    dis[1]=0;
    memset(dis,0x3f,sizeof dis);
	
    bfs(1);

    dfs(1,0);

    cout<<ans<<endl;
}
signed main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	//cin>>t;
	while(t--)
		work();
	return 0;
}
B

计算几何,赛后我看了题意一下就推出来,求凸包内接三角形面积最大值就完事了,没开这道题血亏了

虽然赛场上只有11个队过了,但是这很明显会求凸包三角形最大值就能做呀。。。

给定平面上 n 个点 P i P_i Pi 构成一凸包 C,找到三个点 P r , P s , P t P_r,P_s,P_t Pr,Ps,Pt,使得以这三点为中点三角形的三角形 △ ABC 满足凸多边形 C 完全在其内部,如图所示

数据范围:1 n 1e6

思路:

考虑如何由中点三角形构造出原本的三角形即可,其实就是平移对应高的距离即可(说着可能不好理解,自己手画一下就很明白了)

那么要平移之后,包围其他凸包上的点,那么对应的高显然就应该是最高的,就是对踵点嘛

三条边都要求这样,那么我们求出三角形的最大值不就好了吗

于是想到做过的POJ 2079,套上去就完事了,还不用求凸包

另外计算几何能用int绝对不要用double,我上次VP澳门区域赛WA17发qwq,这次也明白了这一点

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+5;
int n;
struct Point
{
    int x,y;
}P[maxn];

int cross(Point p0,Point p1,Point p2)
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}

void work()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>P[i].x>>P[i].y;
    P[n+1]=P[1];
    int i=1,j=2,k=3;
    int ansi=1,ansj=2,ansk=3;
    int ans=cross(P[1],P[2],P[3]);
    bool flag=false;
    while(k!=1&&!flag)
    {
        int ii=i,jj=j,kk=k,tmp;
        while((tmp=cross(P[i],P[j],P[k]))<cross(P[i],P[j],P[k+1]))
        {
            k++;
            if(k==n+1)
            {
                flag=true;k=1;
            }
        }
        if(ans<tmp) ans=tmp,ansi=i,ansj=j,ansk=k;

        while((tmp=cross(P[i],P[j],P[k]))<cross(P[i],P[j+1],P[k]))
        {
            j++;if(j>=n+1) j=1;
        }
        if(ans<tmp) ans=tmp,ansi=i,ansj=j,ansk=k;

        while((tmp=cross(P[i],P[j],P[k]))<cross(P[i+1],P[j],P[k]))
        {
            i++;if(i>=n+1) i=1;
        }
        if(ans<tmp) ans=tmp,ansi=i,ansj=j,ansk=k;

        if(ii==i&&jj==j&&kk==k)
        {
            k++;if(k>=n+1) k=1;
        }
    }
    cout<<ansi<<' '<<ansj<<' '<<ansk<<' '<<'\n';
}
signed main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	//cin>>t;
	while(t--)
		work();
	return 0;
}

H

队友写的这道题,但是WA到他无法理解

第298分钟,我把他的代码看了一遍,int改long long过了……

三年ACM一场空,不开long long见祖宗

题目大意:

给定两个长度为 n n n的序列

现在可以选择其中一个序列交换其中的两个数字,问经过至多一次操作后最小的 ∑ i = 1 n ∣ a i − b i ∣ \sum^{n}_{i=1}|a_i-b_i| i=1naibi

数据范围1 n 2e5

思路:

记得赛后想了很久想不明白,直到队友提示我,将 a i a_i ai b i b_i bi看成区间

一下恍然大悟

在这里插入图片描述在这里插入图片描述

图片来自本场的官方题解(其实知道看成区间之后,自己手画图也明白了)

那么我们只需要在 O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度内计算出所有反序相交到反序不交的答案贡献,和所有反序包络到正序不交的答案贡献,求最大值即可

a i < b i a_i<b_i ai<bi a i > b i a_i>b_i ai>bi看成两种区间,设为 S S S T T T,对于每个 S S S中的元素二分 T T T即可,也可以用优先队列维护

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码如下

#include<bits/stdc++.h>
#define int long long
using namespace std;
struct Edge{
	int l,r,kind;
}a[1000010];
map<int,int>mp;
bool cmp(const Edge &x,const Edge &y){
	if(x.l==y.l){
		if(x.r==y.r)return x.kind<y.kind;
		return x.r<y.r;
	}
	return x.l<y.l;
}
int xa[1000010],xb[1000010];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;cin>>n;
	for(int i=1;i<=n;++i)
		cin>>xa[i];
	for(int i=1;i<=n;++i)
		cin>>xb[i];
	long long sum=0;
	for(int i=1;i<=n;++i){
		int aa=xa[i],bb=xb[i];
		a[i].l=min(aa,bb);
		a[i].r=max(aa,bb);
		sum+=a[i].r-a[i].l;
		if(aa<bb)a[i].kind=1;
		if(aa>=bb)a[i].kind=-1;
	}
	
	int ans=0;
	sort(a+1,a+n+1,cmp);
	priority_queue<int>q1,q2;
	for(int i=1;i<=n;++i){
		if(a[i].kind==1){
			if(!q2.empty())
				ans=max(ans,min(a[i].r,q2.top())-a[i].l);
			q1.push(a[i].r);
		}
		if(a[i].kind==-1){
			if(!q1.empty())
				ans=max(ans,min(a[i].r,q1.top())-a[i].l);
			q2.push(a[i].r);
		}
	}
	cout<<sum-2*ans;
	return 0;
}
A

构造题,评价是,啥叫排序网络,题我TM看不懂

题目大意:

构造一个排序网络,使其能恰好排序除一个给定 01 串外的所有 01 串。01串长度为 n

数据范围:T 组测试,每组 n 16,T 100

以后再来补

L

歪榜题,考虑到循环节和3次置换之后两个中的任意一个都能想到正解,可惜两个都没想到,也只会跟榜

题目大意:

给定三个长度为 n 的排列a,b,c。 ( x , y , z ) (x,y,z) (x,y,z)最开始为 ( 1 , 1 , 1 ) (1,1,1) (1,1,1),每过一秒变为 ( a y , b z , c x ) (a_y,b_z,c_x) (ay,bz,cx) q q q次询问求变成 ( x ′ , y ′ , z ′ ) (x',y',z') (x,y,z)的最短时间

数据范围:1 n 1e5,1 q 1e5

思路:

​ 每过 3 秒 ( x , y , z ) (x,y,z) (x,y,z)变为 ( ( a ◦ b ◦ c ) x , ( b ◦ c ◦ a ) y , ( c ◦ a ◦ b ) z ) ((a◦b◦c)_x,(b◦c◦a)_y,(c◦a◦b)_z) ((abc)x,(bca)y,(cab)z)其中

a ◦ b a◦b ab表示置换的复合运算

​ 那么可以在线性时间内模拟计算,1经过多少轮迭代之后可以变成 x ′ x' x,记成 α \alpha α,以及多少轮迭代后会回到1,记成 β \beta β

那么问题就变成了同时满足三个同余方程

x ≡ α ( m o d β ) x \equiv \alpha (mod \beta) xα(modβ)

x x x的最小值

用扩展中国剩余定理解决即可

代码暂时没有,因为我暂时还没有找到一个可靠的EXCRT的板子,原理感觉是跟中国剩余定理是一点关系也没有啊

以后再来补

比赛总结

写比赛总结和题解其实感觉还不赖

其实属于是打得比较爽的一场,思路题比较多(或者说是我想得出的思路题比较多)

其实6题就是金牌区了吧,要是计算几何开了,M题敢写就好了

后面夺冠题的圆反演和生成函数,一直听到别人BB,之后我去系统学习一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值