[关闭]
@xiaoziyao 2021-08-11T12:12:02.000000Z 字数 5895 阅读 879

Nyoi Round 1 Solution

考试总结


Nyoi Round 1 Solution

同步发表在作业部落

题目难度较上场有所下降,可见这是一场良心温暖比赛,理论上可以做到 2h ak!(T1 40min,T2 20min,T3 25min,T4 35min)

真正的 csp/noip 模拟赛!

在考前一天我写了一下 T4 的暴力,发现跑的飞快,直接过了,血压飙升,于是赶紧联系 cdz 重造数据,hack 掉暴力之后写了一个记忆化 ​,非常恐怖,于是卡暴力卡了一天。。。在 cxy 的帮助下终于卡掉了。

简化版题解:

t1:最短路板子
t2:kmp板子
t3:平凡的问题,差分解决
t4:有趣的问题,根号分治可以完成。

T1 小 N 的迷宫

签到题。

我们发现是图论问题,又要求最小次数,所以我们会联想到最短路。

由于图要么翻转,要么不翻转,所以我们可以对于两种状态分别建一张图,然后随便连一下边跑最短路,复杂度

由于边权只有两种,所以直接 bfs 可以做到复杂度 ,不过本题没卡 dijkstra。

  1. #include<stdio.h>
  2. #include<queue>
  3. #include<iostream>
  4. #define inf 1000000000
  5. using namespace std;
  6. const int maxn=2000005,maxm=maxn*3,maxk=1005;
  7. int n,m,e,sx,sy,ex,ey;
  8. int start[maxn],to[maxm],then[maxm],worth[maxm],dis[maxn],a[maxk][maxk],vis[maxn];
  9. priority_queue< pair<int,int> >q;
  10. string s;
  11. inline void add(int x,int y,int z){
  12. then[++e]=start[x],start[x]=e,to[e]=y,worth[e]=z;
  13. }
  14. inline int getp(int x,int y){
  15. return (x-1)*m+y;
  16. }
  17. void build(){
  18. for(int i=1;i<=n;i++)
  19. for(int j=1;j<=m;j++)
  20. if(a[i][j]==0){
  21. if(i>1&&a[i-1][j]==0)
  22. add(n*m+getp(i,j),n*m+getp(i-1,j),0);
  23. if(i<n&&a[i+1][j]==0)
  24. add(getp(i,j),getp(i+1,j),0);
  25. if(a[i-1][j]){
  26. add(n*m+getp(i,j),getp(i,j),1);
  27. if(j>1&&a[i][j-1]==0)
  28. add(n*m+getp(i,j),n*m+getp(i,j-1),0);
  29. if(j<m&&a[i][j+1]==0)
  30. add(n*m+getp(i,j),n*m+getp(i,j+1),0);
  31. }
  32. if(a[i+1][j]){
  33. add(getp(i,j),n*m+getp(i,j),1);
  34. if(j>1&&a[i][j-1]==0)
  35. add(getp(i,j),getp(i,j-1),0);
  36. if(j<m&&a[i][j+1]==0)
  37. add(getp(i,j),getp(i,j+1),0);
  38. }
  39. }
  40. }
  41. void dijkstra(int s){
  42. for(int i=1;i<=2*n*m;i++)
  43. dis[i]=inf;
  44. dis[s]=0,q.push(make_pair(0,s));
  45. while(!q.empty()){
  46. int x=q.top().second;
  47. q.pop();
  48. if(vis[x])
  49. continue;
  50. vis[x]=1;
  51. for(int i=start[x];i;i=then[i])
  52. if(dis[to[i]]>dis[x]+worth[i])
  53. dis[to[i]]=dis[x]+worth[i],q.push(make_pair(-dis[to[i]],to[i]));
  54. }
  55. }
  56. int main(){
  57. freopen("maze.in","r",stdin);
  58. freopen("maze.out","w",stdout);
  59. scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&ex,&ey);
  60. for(int i=1;i<=n;i++)
  61. for(int j=1;j<=m;j++)
  62. scanf("%d",&a[i][j]);
  63. build(),dijkstra(getp(sx,sy));
  64. int res=min(dis[getp(ex,ey)],dis[getp(ex,ey)+n*m]);
  65. printf("%d\n",res==inf? -1:res);
  66. return 0;
  67. }

T2 小 Y 的串串

似乎比 T1 更加签到的签到题,是不是平均难度太低了?

观察数据范围判断是线性,观察一下题意,首先跑一遍 kmp 得到所有匹配位置

再预处理一个 表示 位置 表示的后缀至少要到哪里才能出现一个匹配位置。

我们设 为到了第 个位置,一共有多少种情况,不难发现 dp 转移方程是

直接前缀和扫一遍就没了,复杂度

  1. #include<stdio.h>
  2. #include<string.h>
  3. const int maxn=2000005,mod=1000000007;
  4. int n,m,now;
  5. int f[maxn],lst[maxn],p[maxn];
  6. char s[maxn],t[maxn];
  7. inline int add(int x){
  8. return x>=mod? x-mod:x;
  9. }
  10. int main(){
  11. freopen("string.in","r",stdin);
  12. freopen("string.out","w",stdout);
  13. scanf("%s%s",s,t),n=strlen(s),m=strlen(t);
  14. for(int i=1,j=0;i<m;i++){
  15. while(j>0&&t[i]!=t[j])
  16. j=p[j-1];
  17. if(t[i]==t[j])
  18. j++;
  19. p[i]=j;
  20. }
  21. for(int i=0,j=0;i<n;i++){
  22. while(j>0&&s[i]!=t[j])
  23. j=p[j-1];
  24. if(s[i]==t[j])
  25. j++;
  26. if(j==m)
  27. lst[i+1-m+1]=i+1;
  28. }
  29. lst[n+1]=n+1;
  30. for(int i=n;i>=1;i--)
  31. if(lst[i]==0)
  32. lst[i]=lst[i+1];
  33. for(int i=1;i<=n;i++)
  34. f[lst[i]]=add(f[lst[i]]+now+1),f[i]=add(f[i]+f[i-1]),now=add(now+f[i]);
  35. printf("%d\n",now);
  36. return 0;
  37. }

T3 小 N 的序列

这里令 为题目中的

题目难度有所提升,但是仍然很简单,有经验的选手应该可以秒掉。

考虑直接列出式子暴力上莫反:

这里看似要直接进行除法分块,但这样的复杂度为 ,实现精细可以做到 ,但是还是无法通过本题。

我们发现我们要对全局求解,于是对于每个位置求解是没有前途的,我们应该考虑每个值的贡献。

固定 ,考虑除以 下取整这个函数,隔 个数才会变化一遍,也就是说 得到的贡献永远为 得到的贡献永远为 得到的贡献永远为

这明显是差分的模型,直接枚举 并枚举 的倍数,差分解决就好了,记得用线性筛预处理数的 次幂。

时间复杂度:。(默认 同阶)

bonus:值域固定,长度不固定

  1. #include<stdio.h>
  2. const int maxn=2000005,mod=1000000007;
  3. int n,k,cnt,ans;
  4. int a[maxn],p[maxn],miu[maxn],b[maxn],mul[maxn];
  5. int ksm(int a,int b){
  6. int res=1;
  7. while(b){
  8. if(b&1)
  9. res=1ll*res*a%mod;
  10. a=1ll*a*a%mod,b>>=1;
  11. }
  12. return res;
  13. }
  14. int main(){
  15. freopen("sequence.in","r",stdin);
  16. freopen("sequence.out","w",stdout);
  17. scanf("%d%d",&n,&k);
  18. miu[1]=p[1]=1;
  19. for(int i=1;i<=k;i++){
  20. if(p[i]==0)
  21. a[++cnt]=i,miu[i]=-1;
  22. for(int j=1;j<=cnt&&i*a[j]<=k;j++){
  23. p[i*a[j]]=1;
  24. if(i%a[j]==0){
  25. miu[i*a[j]]=0;
  26. break;
  27. }
  28. miu[i*a[j]]=-miu[i];
  29. }
  30. }
  31. for(int i=1;i<=k;i++)
  32. mul[i]=ksm(i,n);
  33. for(int i=1;i<=k;i++)
  34. for(int j=i;j<=k;j+=i)
  35. b[j]=((b[j]+1ll*(mul[j/i]-mul[j/i-1]+mod)%mod*miu[i]%mod)%mod+mod)%mod;
  36. for(int i=1;i<=k;i++)
  37. b[i]=(b[i]+b[i-1])%mod,ans=(ans+(b[i]^i)%mod)%mod;
  38. printf("%d\n",ans);
  39. return 0;
  40. }

T4 小 Y 的旅行

读题发现保证 的祖先这一性质,它提示我们可以预处理一个位置向上走若干个结点的答案并合并。

向上走多少个结点呢?此时在我们面前的有三条路:倍增/树剖/分块。

我们观察 的范围是很不对劲的,它只到了 ,根据大家良好的数感可以发现它是接近 的。

也就是说, 均只有 位。

考虑根号分治+分块,每个点预处理向上走 个结点的答案,那么这些结点的 前八位(指 对应的位)是相同的,但是会随着询问改变,于是我们预处理 表示结点 向上走 个结点,目前的 前八位为 的最大惊喜值。

这个东西在树上 dfs 一遍,每个位置向上暴力跳然后建一个 trie 就可以了。

向上跳完大小为 的整块之后,散块暴力算就可以了。

时间复杂度:,可以通过。

  1. #include<stdio.h>
  2. const int maxn=50005,maxm=maxn<<1,t=256,maxt=257;
  3. int n,m,e,tot;
  4. int val[maxn],start[maxn],to[maxm],then[maxm],f[maxn][maxt],g[maxn][maxt],chd[maxn<<8][2],ffa[maxn],fa[maxn],dep[maxn];
  5. inline int max(int a,int b){
  6. return a>b? a:b;
  7. }
  8. inline void add(int x,int y){
  9. then[++e]=start[x],start[x]=e,to[e]=y;
  10. }
  11. void insert(int x){
  12. int now=1;
  13. for(int i=7;i>=0;now=chd[now][(x>>i)&1],i--)
  14. if(chd[now][(x>>i)&1]==0)
  15. chd[now][(x>>i)&1]=++tot,chd[tot][0]=chd[tot][1]=0;
  16. }
  17. int query(int x,int p){
  18. int now=1,val=0,res=0;
  19. for(int i=7;i>=0;i--){
  20. int c=(x>>i)&1;
  21. if(chd[now][c^1])
  22. now=chd[now][c^1],val|=((c^1)<<i),res|=(1<<i);
  23. else now=chd[now][c],val|=(c<<i);
  24. }
  25. return (res<<8)|g[p][val];
  26. }
  27. void dfs(int x,int last){
  28. fa[x]=last,dep[x]=dep[last]+1;
  29. if(dep[x]>=t){
  30. int y=x;
  31. tot=1,chd[tot][0]=chd[tot][1]=0;
  32. while(dep[x]-dep[y]<t)
  33. insert(val[y]>>8),g[x][val[y]>>8]=max(g[x][val[y]>>8],((dep[x]-dep[y])^val[y])&(t-1)),y=fa[y];
  34. ffa[x]=y;
  35. for(int i=0;i<t;i++)
  36. f[x][i]=query(i,x);
  37. }
  38. for(int i=start[x];i;i=then[i])
  39. if(to[i]!=last)
  40. dfs(to[i],x);
  41. }
  42. int main(){
  43. freopen("trip.in","r",stdin);
  44. freopen("trip.out","w",stdout);
  45. scanf("%d%d",&n,&m);
  46. for(int i=1;i<=n;i++)
  47. scanf("%d",&val[i]);
  48. for(int i=1;i<n;i++){
  49. int x,y;
  50. scanf("%d%d",&x,&y);
  51. add(x,y),add(y,x);
  52. }
  53. dfs(1,0);
  54. for(int i=1;i<=m;i++){
  55. int x,y,res=0,tot=0,dis=0;
  56. scanf("%d%d",&x,&y);
  57. while(dep[y]-t>=dep[x])
  58. res=max(res,f[y][tot]),y=ffa[y],tot++;
  59. dis=tot<<8;
  60. while(y!=x)
  61. res=max(res,dis^val[y]),y=fa[y],dis++;
  62. res=max(res,dis^val[x]);
  63. printf("%d\n",res);
  64. }
  65. return 0;
  66. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注