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=2xi−1 元。
问 Walk Alone 有多大概率拿到 n + m 元离开。
1 ≤ n,m ≤ 1e9。
思路:不难发现,每赢一次,不管前面输多少次,都是只赚一块钱
那么只需要考虑最多输多少轮即可
考虑如果现在有 x 元,找到最大的 r 满足 2 r − 1 ≤ x 2^r-1\le x 2r−1≤x,从 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] [2r−1,2r+1−2] 的概率都一样,而且只有 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∣+∣2y∣−1x>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] k−dis[u1]和 k − d i s [ u 2 ] k-dis[u2] k−dis[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=1n∣ai−bi∣
数据范围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) ((a◦b◦c)x,(b◦c◦a)y,(c◦a◦b)z)其中
a ◦ b a◦b a◦b表示置换的复合运算
那么可以在线性时间内模拟计算,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,之后我去系统学习一下