题解 ZROI2

暑假集训的第二次模拟赛,成绩..更加惨不忍睹。又滑了rk20,(#`-_ゝ-)(sk)

链染色

考场上想出了半正解,思路上期望得分80pts,代码得分0pts,(我这辣鸡代码能力╯︿╰)实际考试的时候,由于网络波动,没能交上去,导致该题没有提交记录..(正好掩盖一下爆零的事实)

这道题在题干里就提到了并查集,再加上考前讲课的时候我听明白了,所以考场推导其实蛮顺利的

先加强一下条件,考虑一下线性情况:在一条链上有一串节点,每一次都会对指定区间内所有未染色的节点进行染色,询问所有操作之后链上节点的情况

一种显而易见的\(O(n^2)\)暴力做法是:枚举所有节点,模拟染色

这种做法的弊端是:需要大量重复访问已经被染色的节点。假如我们能够跳过被染色的节点呢?每一次只访问仍未被染色的节点,就可以在线性时间内解决问题

设 f[i] 表示区间 [i,n] 最靠左的未染色节点的坐标。初始时,f[i]=i

假如节点 i 被染色了,就将 f[i] 改成 i+1。可以发现只有一个节点 x , f[x]=x时, x 才是一个未染色的点。因此我们就顺着 f[i]不断向上跳,直到找到一个节点满足 f[x]=x

顺着f[i]不断上跳.. 抵达 f[i]=i .. !并查集

借用一下并查集的路径压缩思想,在一个跳跃的过程中,更新沿途的 f[] ,这样的话可以保证查询的效率。

红线部分即使借鉴并查集路径压缩的优化

总结一下就是:

\[f[i]=i\quad col[a]=未染色
\]
\[f[i]=i+1\quad col[a]=已染色
\]

至此,是对强化版条件的求解,我们尝试弱化回题意版本:在树上应当如何处理?

事实上,这种做法的推广比较容易实现。对于一棵树,任意节点可以有多数个子节点,但父节点总是只有一个。

\[f[i]=i \quad col[i]=未染色
\]
\[f[i]=fa[i] \quad col[i]=已染色
\]

然后将一条链按照LCA裂解,分别处理两条链即可

而在两条链上的情况就等同于链上的处理方法。

但是!这就ok了?观察一下数据范围:100pts n(2e6)..

LCA的预处理复杂度就在\(O(nlogn)\),数据范围好像有点不太可以接受..

发现:最后两个指针最后总会到达绿色区域上第一个未染色的节点上。!!我们并不需要事先知道LCA,两个指针不断移动的时候,判断两个指针是否相同即可(^-^

ok,口头ac了一遍,接着代码再ac一遍:

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long ll;
  7. const int MAX=2e6+5;
  8. int n,m,ans;
  9. int ecnt,edge[MAX],nxt[MAX],head[MAX];
  10. int fat[MAX];
  11. int fa[MAX][22],hei[MAX];
  12. bool vis[MAX];
  13. ll taru,tarv;
  14. inline int read();
  15. inline void insert(int,int,int);
  16. int find(int);
  17. void dfs(int,int);
  18. int lca(int,int);
  19. void calc(int,int,int);
  20. int main(){
  21. #ifndef ONLINE_JUDGE
  22. freopen("test.in","r",stdin);
  23. #endif
  24. n=read(); m=read();
  25. for(int i=1;i<n;++i){
  26. int d=read(); insert(i+1,d,++ecnt); insert(d,i+1,++ecnt);
  27. }
  28. for(int i=1;i<=n;++i) fat[i]=i;
  29. dfs(1,1);
  30. scanf("%lld%lld",&taru,&tarv);
  31. for(int i=1;i<=m;++i){
  32. int LCA=lca((int)taru,(int)tarv);
  33. calc((int)taru,LCA,i); calc((int)tarv,LCA,i);
  34. ll newu,newv;
  35. newu=(taru*114514+tarv*1919810)%(ll)n+1;
  36. newv=(taru*415411+tarv*8101919)%(ll)n+1;
  37. taru=newu; tarv=newv;
  38. }
  39. cout<<ans<<endl;
  40. return 0;
  41. }
  42. inline int read(){
  43. char tmp=getchar(); int sum=0; bool flag=false;
  44. while(tmp<'0'||tmp>'9'){
  45. if(tmp=='-') flag=true;
  46. tmp=getchar();
  47. }
  48. while(tmp>='0'&&tmp<='9'){
  49. sum=(sum<<1)+(sum<<3)+tmp-'0';
  50. tmp=getchar();
  51. }
  52. return flag?-sum:sum;
  53. }
  54. void insert(int from,int to,int id){
  55. nxt[id]=head[from]; head[from]=id; edge[id]=to;
  56. }
  57. int find(int p){
  58. if(fat[p]==p) return fat[p];
  59. else return fat[p]=find(fat[p]);
  60. }
  61. void dfs(int u,int faa){
  62. fa[u][0]=faa; hei[u]=hei[faa]+1;
  63. for(int i=1;i<=20;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
  64. for(int i=head[u];i;i=nxt[i]){
  65. int v=edge[i]; if(v==faa) continue;
  66. dfs(v,u);
  67. }
  68. }
  69. int lca(int a,int b){
  70. if(hei[a]<hei[b]) swap(a,b);
  71. for(int i=20;i>=0;--i) if(hei[fa[a][i]]>=hei[b]) a=fa[a][i];
  72. if(a==b) return a;
  73. for(int i=20;i>=0;--i) if(fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
  74. return fa[a][0];
  75. }
  76. void calc(int a,int b,int del){
  77. if(hei[a]<hei[b]) swap(a,b);
  78. int cur=a;
  79. while(cur!=b){
  80. cur=find(cur);
  81. if(cur==b) break;
  82. if(!vis[cur]) ans+=del;
  83. vis[cur]=true;
  84. fat[cur]=fa[cur][0];
  85. cur=fa[cur][0];
  86. }
  87. }

链Max

起初是一道淀粉质题,敬而远之..事实上还是一个并查集题

首先,将点权信息转换成边权信息。具体而言,对于边\(<x,y> w(x,y)=max(w[x],w[y])\)

这样处理的好处在于,一个点可能会连接四面八方的边,但一条边只有两侧的链与之相连,可以降低情况的复杂程度

依旧是考虑强化条件:将树形数据调整为链形结构

由于是对于每一条链,其最大边才会对答案产生贡献,将所有答案贡献情况枚举累加起来,即使总答案

那么就依次考虑每一条边的贡献情况:

考虑将边的权值升序排序,然后顺序添加,将其连接的两个节点的联通块连接起来,并查集的同时维护一下联通块的大小。这样枚举到当前的链时刻,两侧的节点已经被并查集维护起来,且满足并查集里节点间的边的权值均小于当前枚举边的权值。

发现有面临着一个问题:需要对并查集里所有节点快速加减

由于并查集本质上维护的是一颗树上的父子关系。如果是一颗树的话,我们就可以在子树的根上打一个标记,记作:对该子树所有节点均加减delta。

并查集的路径压缩会破坏这种树形结构,因此我们需要一种保持树形结构和高效性的合并方法:按秩合并

通过按秩合并,我们可以\(O(log n)\)处理的同时,保证树形结构。每一次合并的时候,只需要在根打上标记即可。

还有一点细节:将两个子树合并之后,大树的标记会同样的作用于小树,简单运算一下,将正确结果打上标记即可

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long ll;
  7. const int MAX=2e6+5;
  8. int n,m,ans;
  9. int ecnt,edge[MAX],nxt[MAX],head[MAX];
  10. int fat[MAX];
  11. int fa[MAX][22],hei[MAX];
  12. bool vis[MAX];
  13. ll taru,tarv;
  14. inline int read();
  15. inline void insert(int,int,int);
  16. int find(int);
  17. void dfs(int,int);
  18. int lca(int,int);
  19. void calc(int,int,int);
  20. int main(){
  21. #ifndef ONLINE_JUDGE
  22. freopen("test.in","r",stdin);
  23. #endif
  24. n=read(); m=read();
  25. for(int i=1;i<n;++i){
  26. int d=read(); insert(i+1,d,++ecnt); insert(d,i+1,++ecnt);
  27. }
  28. for(int i=1;i<=n;++i) fat[i]=i;
  29. dfs(1,1);
  30. scanf("%lld%lld",&taru,&tarv);
  31. for(int i=1;i<=m;++i){
  32. int LCA=lca((int)taru,(int)tarv);
  33. calc((int)taru,LCA,i); calc((int)tarv,LCA,i);
  34. ll newu,newv;
  35. newu=(taru*114514+tarv*1919810)%(ll)n+1;
  36. newv=(taru*415411+tarv*8101919)%(ll)n+1;
  37. taru=newu; tarv=newv;
  38. }
  39. cout<<ans<<endl;
  40. return 0;
  41. }
  42. inline int read(){
  43. char tmp=getchar(); int sum=0; bool flag=false;
  44. while(tmp<'0'||tmp>'9'){
  45. if(tmp=='-') flag=true;
  46. tmp=getchar();
  47. }
  48. while(tmp>='0'&&tmp<='9'){
  49. sum=(sum<<1)+(sum<<3)+tmp-'0';
  50. tmp=getchar();
  51. }
  52. return flag?-sum:sum;
  53. }
  54. void insert(int from,int to,int id){
  55. nxt[id]=head[from]; head[from]=id; edge[id]=to;
  56. }
  57. int find(int p){
  58. if(fat[p]==p) return fat[p];
  59. else return fat[p]=find(fat[p]);
  60. }
  61. void dfs(int u,int faa){
  62. fa[u][0]=faa; hei[u]=hei[faa]+1;
  63. for(int i=1;i<=20;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
  64. for(int i=head[u];i;i=nxt[i]){
  65. int v=edge[i]; if(v==faa) continue;
  66. dfs(v,u);
  67. }
  68. }
  69. int lca(int a,int b){
  70. if(hei[a]<hei[b]) swap(a,b);
  71. for(int i=20;i>=0;--i) if(hei[fa[a][i]]>=hei[b]) a=fa[a][i];
  72. if(a==b) return a;
  73. for(int i=20;i>=0;--i) if(fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
  74. return fa[a][0];
  75. }
  76. void calc(int a,int b,int del){
  77. if(hei[a]<hei[b]) swap(a,b);
  78. int cur=a;
  79. while(cur!=b){
  80. cur=find(cur);
  81. if(cur==b) break;
  82. if(!vis[cur]) ans+=del;
  83. vis[cur]=true;
  84. fat[cur]=fa[cur][0];
  85. cur=fa[cur][0];
  86. }
  87. }

删数字

考场的时候精神分裂?c++玄学报错?总之好尿多磨..

如果从头开始删数字,会有大量大量大量到无法统计的情况需要分类讨论。“因此”(套路上的因此),考虑枚举最后一个删除的元素

对于最后一个删除的元素,其左右两侧区间的所有元素都不会彼此相邻,具有相互独立的性质,可以分治解决

记左区间的方案数为\(S_l\),右区间为\(S_r\),,那么根据简单的乘法原理,得到总方案数\(S_l\times S_r \times C_{r-l}^{k-l}\),最后答案累加即可

设 f[l][r] 表示区间 [l,r] 满足条件的所有方案数,\(O(n^3)\)转移

即可?好像还有一些细节欠推敲。考虑下面的这种情形

在s1中,显然绿色部分必须比黄色部分更先被删除,否则就会与s2串中的最左端绿色相邻。具体而言,对于区间 [l,r] ,不允许与 w[l-1] 和 w[r+1] 相同且相邻

这么一来,原来对于 f[l][r] 的定义似乎有些不够充分,补充如下

f[i][j] 表示区间 [l,r] 满足条件的所有方案数,且不与 w[l-1] 和 w[r+1] 相同且相邻

定义改变了,那么转移方式也必须相应的改变:当枚举到的k满足 “w[k]=w[l-1]” 或者 "w[k]=w[r+1]" 时,倘若k最后被删除,那么一定是不满足条件的;反之,只要k不是最后删除,根据定义的转移一定是满足条件

伪代码如下:

  1. 类似区间dp枚举区间 (l,r){
  2. 枚举 k 属于 (l,r) ,且 满足 (w[k]!=w[l-1]!=w[r+1]){
  3. f[l][r]+=s_l*s_r*c(r-l)(k-l)
  4. }
  5. }
  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long ll;
  7. #define foR(a,b,c) for(a=b;a<=c;++a)
  8. #define For(a,b,c) for(a=c;a>=b;--a)
  9. int i,j,k;
  10. const int MAX=555;
  11. const ll MOD=998244353ll;
  12. int n,a[MAX];
  13. ll f[MAX][MAX],c[MAX][MAX];
  14. void init();
  15. int main(){
  16. #ifndef ONLINE_JUDGE
  17. freopen("test.in","r",stdin);
  18. #endif
  19. scanf("%d",&n);
  20. foR(i,1,n) scanf("%d",&a[i]);
  21. init();
  22. a[0]=a[n+1]=-1;
  23. For(i,1,n){
  24. foR(j,i,n){
  25. foR(k,i,j){
  26. if(a[k]==a[i-1]||a[k]==a[j+1]) continue;
  27. ll left=(k==i?1:f[i][k-1]),right=(k==j?1:f[k+1][j]);
  28. f[i][j]+=(left*right%MOD*c[j-i][k-i]%MOD); f[i][j]%=MOD;
  29. }
  30. }
  31. }
  32. printf("%lld\n",f[1][n]);
  33. return 0;
  34. }
  35. void init(){
  36. c[0][0]=1;
  37. foR(i,1,500) c[i][0]=c[i][i]=1;
  38. foR(i,2,500) foR(j,1,i-1) c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;
  39. }

这场比赛其实的期望分数是100+60+0=160,期望排名在rk4。但是事实上实际得分10pts,连零头都不到。。总结一下还是代码能力不足。很多思想能构想出来,却缺乏依次性实现不出错的能力

(* ̄3 ̄)╭

ZROI2的更多相关文章

随机推荐

  1. Linux系统管理_磁盘管理——敬请期待!!!

    df 显示磁盘空间使用情况 -a 全部文件系统列表 -h 以方便阅读的方式显示 -T 列出文件系统类型 du 查看目录和文件的磁盘空间使用情况 -a 显示目录中所有文件大小 -h 以易读方式显示文件大 ...

  2. Linux实战笔记__Centos7上搭建DVWA网站(基于宝塔)

    安装宝塔套件 宝塔官网有远程安装代码https://www.bt.cn/bbs/thread-19376-1-1.html 下载DVWA并上传至/www/wwwroot目录 下载地址: 配置数据库连接 ...

  3. Linux执行jsp命令的时候报错:-bash: jps: command not found

    前言:在zookeeper学习的时候,执行jsp命令查看zookpper运行状态的时候发现报错: -bash: jps: command not found 翻阅了一大批文章,不是东拼西凑,就是缺斤少 ...

  4. MySQL 主从复制一主两从环境配置实战

    MySQL 初始化 MySQL 主从复制是指数据可以从一个 MySQL 数据库服务器主节点复制到一个或多个从节点.MySQL 默认采用异步复制方式;从节点可以复制主数据库中的所有数据库或者特定的数据库 ...

  5. 17.ViewSet和Router

    REST框架为我们提高了一个更加抽象的ViewSet视图集,ViewSet提供一套自动的urlconf路由 ViewSet与View类几乎相同,不同之处在于它们提供诸如read或update之类的操作 ...

  6. JK触发器与模12计数器

    JK触发器 JK触发器具有保持,置0,置1和翻转四个功能. 则可得出次态方程:\(Q_{n+1} = JQ_n'+K'Q_n\) Design `timescale 1ns / 1ps module ...

  7. 用Nodejs 实现一个简单的 Redis客户端

    目录 0. 写在前面 1. 背景映入 2. 数据库选择 3. Nodejs TCP连接 3. 代码编写 4. 实验 5. wireshark 抓包分析 6. 杂与代码 0. 写在前面 大家如果有去看过 ...

  8. 禁止eslint对指定代码检测

    有时候我们引入外部文件的API时,eslint无法识别,编译的时候就会出现warn eslint是可以禁用对指定代码的检测: 单行注释 let map = new BMap.Map('map') // ...

  9. 16.python中的回收机制

    python中的垃圾回收机制是以引用计数器为主,标记清除和分代回收为辅的 + 缓存机制 1.引用计数器 在python内部维护了一个名为refchain的环状双向链表,在python中创建的任何对象都 ...

  10. Python用yield form 实现异步协程爬虫

    很古老的用法了,现在大多用的aiohttp库实现,这篇记录仅仅用做个人的协程底层实现的学习. 争取用看得懂的字来描述问题. 1.什么是yield 如果还没有怎么用过的话,直接把yield看做成一种特殊 ...