"蔚来杯"2022牛客暑期多校训练营3
A | B | C | D | E | F | G | H | I | J | |
赛时过题 | O | O | O | O | O | |||||
赛后补题 | 待补 |
赛后总结:
好耶!做出了5题,55名!!校内第二!芜湖起飞!
今天把所有力所能及的题全做出来了,还做出来了F题,非常棒!今后也要和这次一样!
除非是过题人数极少的题,否则赛场上每道题至少要有2个人看过!这点要牢记!
今天这场比较顺风,看一题切一题,之后可能没这么好的运气但也要做出每一道有能力做出来的题!
比赛规划:0h~0.5h 看4题+做签到,0.5h~2h再看4题,同时结合榜单开题,2h~5h开难题但不要三个人同时看一道题。
赛时排名:
6题末尾:33名
7题末尾:12名
C Concatenation
题目难度:check-in
题目大意:N个数字字符串(可有前导0),重新排列并拼接起来输出,使得输出的数字最小。
数据范围:1<=N<=2e6,∑|si|<=2e7
解题分析:由于输出的字符串长度固定,故数字最小=字典序最小。
故直接按照a+b<b+a进行sort即可,需要注意cmp函数中的传参需要加引用否则会T。
实际上线性做法的标答很复杂,但由于这题时限比较松(4s)没有把sort卡死,所以就成了简单题hhh
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=2e6+1000;
std::string s[N];
const int cmp(const std::string&a,const std::string&b)
{
return a+b<b+a;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n;std::cin>>n;
For(i,1,n) std::cin>>s[i];
std::sort(s+1,s+1+n,cmp);
For(i,1,n) std::cout<<s[i];
return 0;
}
A Ancestor
题目难度:easy
题目大意:给出两棵编号1-n的树A、B,A、B树上每个节点均有一个权值,给出k个关键点的编号x1…xk,问有多少种方案使得去掉恰好一个关键点使得剩余k-1个关键点在树A上LCA的权值大于树B上LCA的权值。
数据范围:2<=K<=N<=1e5
解题思路:用线段树做区间查询RMQ (Range Minimum/Maximum Query)来求LCA(Least Common Ancestor),然后再用set维护删点/加点时的最左端和左右端。
标答:预处理出关键点序列的在树A B上的前缀LCA和后缀LCA,枚举去掉的关键节点并使用前后缀LCA算出剩余节点的LCA比较权值即可。
参考代码:
查看代码
#include<iostream>
#include<vector>
#include<set>
#include<cstdio>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+1000;
std::vector<std::pair<int,int> > dep_val[2];
std::vector<int> To[2][N];
int L[2][N],R[2][N],depth[2][N],v[2][N],x[N];
void dfs(int opt,int now)
{
dep_val[opt].push_back(std::make_pair(depth[opt][now],v[opt][now]));
L[opt][now]=dep_val[opt].size();
For(i,0,(int)To[opt][now].size()-1)
{
depth[opt][To[opt][now][i]]=depth[opt][now]+1;
dfs(opt,To[opt][now][i]);
dep_val[opt].push_back(std::make_pair(depth[opt][now],v[opt][now]));
}
R[opt][now]=dep_val[opt].size();
}
std::pair<int,int> min[2][4*2*N];
void build(int root,int l,int r,int opt)
{
if (l==r)
{
min[opt][root]=dep_val[opt][l-1];
return ;
}
int mid=(l+r)>>1;
build(root<<1,l,mid,opt);build(root<<1|1,mid+1,r,opt);
min[opt][root]=std::min(min[opt][root<<1],min[opt][root<<1|1]);
}
std::pair<int,int> query(int root,int l,int r,int a,int b,int opt)
{
if (l==a&&r==b) return min[opt][root];
int mid=(l+r)>>1;
if (b<=mid) return query(root<<1,l,mid,a,b,opt);
else
{
if (a>mid) return query(root<<1|1,mid+1,r,a,b,opt);
else return std::min(query(root<<1,l,mid,a,mid,opt),query(root<<1|1,mid+1,r,mid+1,b,opt));
}
}
std::set<int> minL[2], maxR[2];
int main()
{
int n,k;scanf("%d%d",&n,&k);
For(i,1,k) scanf("%d",&x[i]);
For(i,1,n) scanf("%d",&v[0][i]);
For(i,2,n)
{
int x;scanf("%d",&x);
To[0][x].push_back(i);
}
For(i,1,n) scanf("%d",&v[1][i]);
For(i,2,n)
{
int x;scanf("%d",&x);
To[1][x].push_back(i);
}
dfs(0,1);dfs(1,1);
For(i,0,1) build(1,1,dep_val[i].size(),i);
For(i,0,1) For(j,1,k) minL[i].insert(L[i][x[j]]),maxR[i].insert(R[i][x[j]]);
int ans=0;
For(j,1,k)
{
For(i,0,1) minL[i].erase(minL[i].find(L[i][x[j]])),maxR[i].erase(maxR[i].find(R[i][x[j]]));
int va=query(1,1,dep_val[0].size(),*minL[0].begin(),*maxR[0].rbegin(),0).second;
int vb=query(1,1,dep_val[1].size(),*minL[1].begin(),*maxR[1].rbegin(),1).second;
if (va>vb) ans++;
For(i,0,1) minL[i].insert(L[i][x[j]]),maxR[i].insert(R[i][x[j]]);
}
printf("%d\n",ans);
return 0;
}
J Journey
题目难度:easy
题目大意:给定一个城市有N个十字路口,除了右转,直行、左转和掉头都需要等红灯,求起点<s1,s2>到终点<t1,t2>最少等几次红灯。
其中<i,j>表示当前位于十字路口i->十字路口j的路上,从<i,j>到<j,i>需要调头。
数据范围:2<=N<=5e5
解题思路:每条路看做一个点,不同路通过十字路口连权值为0/1的边。求起点到终点的最短路。
赛时情况:
一开始用map来存点,然后跑spfa,第一发T。
怀疑是spfa被卡了,改成map+双端队列,第二发WA。
怀疑是双端队列有问题,比如说某个点可以从一个距离为3的点a花1代价到达,也可从令一个距离为3的点b花0代价到达。如果从到a到达被加入队列,在处理b的时候就不会再更新了,答案就会偏大。
(后来想想可以在处理b的时候也更新并加入队头,之后处理由a转移的那个点时直接continue即可)。
那么就用ε-closure的方式,每次入队时把该点走0边到达的点全部加入,采用map+普通队列,第三发T。
怀疑是找右转时的%4比较慢,但%4应该会被优化成&3,那么问题应该出在map。
发现一共就4*N个点把map改成根据十字路口和方向重标号,再加上普通队列,第四发A!
赛后总结:
一共有4N=2e6个点,每个点有4个方向,也就是8e6条边。
如果使用map,则复杂度大约为8e6 * 25 =2e8
限时7s,不太理解为什么map会超时,也许是评测机比较卡吧,第一发、第三发罚时也确实是难以避免。
不过第二发罚时理论上应该能避开的,不过考虑到赛场上有失误也正常,害
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#include<map>
#include<queue>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=5e5+1000;
int c[N][5],n,s1,s2,t1,t2;
int dis[N*4];
std::queue<int>Q;
int id(int x,int y)
{
return (x-1)*4+y;
}
void Add(int u)
{
int first=u/4+1;int second=c[first][u%4];
For(i,0,3) if (c[second][(i-1+4)&3]==first)
{
if (c[second][i]==0) continue;
int v=id(second,i);
if (!dis[v])
{
dis[v]=dis[u];
Q.push(v);
Add(v);
}
}
}
int main()
{
scanf("%d",&n);For(i,1,n) For(j,0,3) scanf("%d",&c[i][j]);
scanf("%d%d%d%d",&s1,&s2,&t1,&t2);
For(i,0,3) if (c[s1][i]==s2)
{
int now=id(s1,i);
dis[now]=1;
Q.push(now);
Add(now);
}
while (!Q.empty())
{
int u=Q.front();Q.pop();
int first=u/4+1;int second=c[first][u%4];
// printf("%d %d %d\n",u,first,second);
For(i,0,3)
{
if (c[second][i]==0||c[second][(i-1+4)&3]==first) continue;
int v=id(second,i);
if (!dis[v])
{
dis[v]=dis[u]+1;
Q.push(v);
Add(v);
}
}
}
For(i,0,3) if (c[t1][i]==t2)
printf("%d\n",dis[id(t1,i)]-1);
return 0;
}
H Hacker
题目难度:medium
题目大意: 给出长度为n的小写字符串A和k个长度为m的小写字符串B1…Bk,B的每个位置拥有统一的权值v1…vm。
对于每个Bi求最大区间和满足该区间构成的字符串是A的子串(空区间合法)。
数据范围:n,m,k<=1e5,m*k<=1e6
解题思路:注意到可以用双指针形式求出对每一个Bi的开始位置,最长能和A匹配多远,然后再用线段树求最大子段和。
又发现双指针中,l+1即去掉首字母,等价于在SAM上跳fail,r+1即加上为字母,等价于在SAM上跳son,那么这题就是一道板子题了。
赛时经历:非常迅速地抄了板子,结果一交WA了。原本以为是线段树维护最大子段和有问题,后来发现问题出在SAM板子抄错了。。。
以后抄板子完了以后一定要和板子一行一行核对一下!
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+1000;
int n,m,k;long long v[N];
char A[N],B[N];
namespace SAM
{
const int M=2*N;
int cnt=1,last=1;
int len[M],size[M],pa[M],son[M][30];
void insert(int c,int v)
{
int now=++cnt;len[now]=v;size[now]=1;
for(;last&&!son[last][c];last=pa[last]) son[last][c]=now;
if (!last) pa[now]=1;
else
{
int last_son=son[last][c];
if (len[last_son]==len[last]+1) pa[now]=last_son;
else
{
++cnt;
len[cnt]=len[last]+1;
For(i,1,26) son[cnt][i]=son[last_son][i];
for(;son[last][c]==last_son;last=pa[last]) son[last][c]=cnt;
pa[cnt]=pa[last_son];
pa[last_son]=cnt;
pa[now]=cnt;
}
}
last=now;
}
int go(int &now,int c)
{
if (son[now][c]) return now=son[now][c],1;
return 0;
}
void fail(int &now,int nowlen)
{
if (len[pa[now]]+1==nowlen) now=pa[now];
}
}
long long max[3][4*N],sum[4*N];
void Build(int root,int l,int r)
{
if (l==r)
{
max[1][root]=max[2][root]=max[0][root]=std::max(0ll,v[l]);
sum[root]=v[l];
return ;
}
int mid=(l+r)>>1;
Build(root<<1,l,mid);Build(root<<1|1,mid+1,r);
max[0][root]=std::max(std::max(max[0][root<<1],max[0][root<<1|1]),max[2][root<<1]+max[1][root<<1|1]);
max[1][root]=std::max(max[1][root<<1],sum[root<<1]+max[1][root<<1|1]);
max[2][root]=std::max(max[2][root<<1|1],sum[root<<1|1]+max[2][root<<1]);
sum[root]=sum[root<<1]+sum[root<<1|1];
}
void query(int root,int l,int r,int a,int b,long long &maxt,long long &maxl,long long &maxr,long long &sumt)
{
// printf("?? %d %d %d %d %d\n",root,l,r,a,b);
if (l==a&&r==b)
{
maxt=max[0][root];
maxl=max[1][root];
maxr=max[2][root];
sumt=sum[root];
return ;
}
int mid=(l+r)>>1;
if (b<=mid) return query(root<<1,l,mid,a,b,maxt,maxl,maxr,sumt);
else
{
if (a>mid) return query(root<<1|1,mid+1,r,a,b,maxt,maxl,maxr,sumt);
else
{
long long maxt1,maxt2,maxl1,maxl2,maxr1,maxr2,sumt1,sumt2;
query(root<<1,l,mid,a,mid,maxt1,maxl1,maxr1,sumt1);
query(root<<1|1,mid+1,r,mid+1,b,maxt2,maxl2,maxr2,sumt2);
maxt=std::max(std::max(maxt1,maxt2),maxr1+maxl2);
maxl=std::max(maxl1,sumt1+maxl2);
maxr=std::max(maxr2,sumt2+maxr1);
sumt=sumt1+sumt2;
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
scanf("%s",A+1);For(i,1,n) SAM::insert(A[i]-'a'+1,i);
For(i,1,m) scanf("%lld",&v[i]);Build(1,1,m);
For(i,1,k)
{
scanf("%s",B+1);
int now=1,r=0;long long ans=0;
For(l,1,m)
{
if (r<l) now=1,r=l-1;while (r<m&&SAM::go(now,B[r+1]-'a'+1)) r++;
if (r<l) continue;
long long maxt=0,maxl=0,maxr=0,sumt=0;
query(1,1,m,l,r,maxt,maxl,maxr,sumt);
ans=std::max(ans,maxt);
SAM::fail(now,r-l+1);
}
printf("%lld\n",ans);
}
return 0;
}
F Fief
题目难度:medium
题目大意:给定一个n个点m条边的无向图,q个询问每次询问两点x, y,求是否存在一个n的排列,使得第一个元素为x,最后一个元素为y,且排列的任意一个前缀、任意一个后缀都连通。
赛时经历:
首先如果原图是一条链且x,y是其两端那么就肯定可行。
又猜测这题和割点有关,那么就先用tarjan求出强联通分量。
一开始以为问题等价于x->y有经过所有点恰好一次的哈密顿路径,但是后来发现不用这么强的条件,每次往X(x所在连通图)新加的点b是未必是由之前的最后一个点走一步到达的,可能是更前面的点走一步到达。
然后猜测如果将新图按照强联通分量缩点后是一条链然后x,y在其两端肯定可行。
D Directed
题目难度:medium-hard
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~