当前位置: 首页 > news >正文

P8456 「SWTR-8」地地铁铁 题解

Description

原题意很简洁。

主要注意简单路径不能经过重复的节点

Solution

小清新图论题,给出题人点赞。

不能重复经过节点,那实际上一条路径就是走进一个点双、过割点、然后再进一个点双。

于是很自然地建一棵广义圆方树。

情况分为两种,(x,y)(x,y)(x,y) 在同一点双和在不同点双。

先考虑在同一点双,按照点双里面有的边的种类再分为只有一种和两种都有。

对于第一种,那没办法,这个点双肯定没有合法的点对,总不能走出去再进来吧。

对于第二种,由于点双的性质,任一对点对间至少有两条点不交的路径,只有两点之间每一条路径都只有一种字母才不合法(而且一个点双中最多只有一对),否则一定合法,形如这样:

图中 (1,2)(1,2)(1,2) 就是一对不合法的点对,而其他所有点对 (x,y)(x,y)(x,y) 都合法。

x,yx,yx,y 都在同一种边上,如 (6,7)(6,7)(6,7),就有 7→⋯→2→⋯→1→⋯→67\to\dots\to2\to\dots\to1\to\dots\to67216 这种路径,2→⋯→12\to\dots\to121 走与 7→⋯→27\to\dots\to272 不同的边即可,否则如 (3,6)(3,6)(3,6) 这种,就有 3→⋯→1/2→⋯→63\to\dots\to1/2\to\dots\to631/26 这样子的路径。

如果不存在 (1,2)(1,2)(1,2) 这种点对,那 (x,y)(x,y)(x,y) 间若干条路径一定有一条是 dD都有的,不然就是 (1,2)(1,2)(1,2) 了。

如何判断一个点双中是否有这种点对呢?

发现只有 111222 与两种边都相连,因为如果有第三个点 xxx 也是这样的,那么一定存在合法路径 1→x→21\to x\to21x2

所以只要计算点双中与两种边都相连的点的数量,如果其为 222,那么这个点双的贡献为 size×(size−1)2−1\dfrac{size\times(size-1)}{2}-12size×(size1)1,否则为 size×(size−1)2\dfrac{size\times(size-1)}{2}2size×(size1)

然后考虑 (x,y)(x,y)(x,y) 不在同一点双的情况。

首先对每个方点求出其 pospospos 值,pos=1pos=1pos=1 表示此点双只有 d边,pos=2pos=2pos=2 表示只有 D边,pos=3pos=3pos=3 表示两种边都有。

找到 (x,y)(x,y)(x,y) 在圆方树上路径中所有方点,只要出现了 pos=3pos=3pos=3 的或者 pos=1/2pos=1/2pos=1/2 都出现了,就是合法的。

但是会发现一个问题,如果 pos=3pos=3pos=3 的方点在路径上的与它相连的两个圆点恰好就分别是上图中 111222 那样,那这个点双就只能提供一种边。

由于现在求的是跨过这个点双的路径,所以还要走其他的点双,不管其他提供的哪种边,这个点双给另外一种边就合法了。

至于怎么求,圆方树上树形 dp即可。这种 dp多写就好了,推荐 P10140。

fx,1/2/3f_{x,1/2/3}fx,1/2/3 表示 xxx 这个点的子树内有多少个圆点到 xxx 的路径上已经是 1/2/31/2/31/2/3 类型(定义与 pospospos 类似)了,注意圆点和方点的区别,圆点的 fff 是不含自己的,方点则是不含上面那个圆点的,以及在方点算贡献更复杂,具体实现见代码。

时间复杂度是 O(n+m)O(n+m)O(n+m),复杂度瓶颈在于怎么求每个点双中与两种边都相连的点的数量。

如果遍历点双的导出子图,然后判断每个点的话,在菊花图中会被卡成 O(n2)O(n^2)O(n2)

所以直接对每条边考虑,若两端点在同一点双 bbb 内就把这条边放到 aba_bab 中,计算 bbbpospospos 与贡献时遍历 aba_bab 即可。至于找 bbb,圆方树上两点中间的方点就是 bbb,复杂度 O(n+m)O(n+m)O(n+m)

Code

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define ll long long
const int N=4e5+10,M=1e6+10;
int c,n,m,tot,cnt,fl;
int head[N],dfn[N],low[N],pos[N],fa[2*N],f[2*N][4];
bool vis[N][3],ff[3];
ll ans;
stack<int> s;
vector<int> v[2*N];
vector<pair<int,int> > a[N];
struct edge{int to,next,tp;
}e[2*M];
void add(int x,int y,char z){e[++tot]={y,head[x],z=='d'?1:2};head[x]=tot;
}
int find(int x,int y){  //找点双if(fa[x]==fa[y]) return fa[x];if(x==fa[fa[y]]) return fa[y];if(y==fa[fa[x]]) return fa[x];return 0;
}
void dfs(int x,int fax){s.push(x);dfn[x]=low[x]=++cnt;for(int i=head[x];i;i=e[i].next){int y=e[i].to;if(y==fax) continue;if(dfn[y]) low[x]=min(low[x],dfn[y]);else{dfs(y,x);low[x]=min(low[x],low[y]);if(low[y]>=dfn[x]){fl++;while(s.top()!=y){v[fl].pb(s.top());v[s.top()].pb(fl);s.pop();}s.pop();v[fl].pb(y),v[y].pb(fl);v[fl].pb(x),v[x].pb(fl);}}}
}
void get(int x,int fax){fa[x]=fax;for(int y:v[x]){if(y==fax) continue;get(y,x);}
}
void sol(int x,int fax){int sav[4]={0,0,0,0};for(int y:v[x]){if(y==fax) continue;sol(y,x);if(x<=n){ans+=(ll)f[y][1]*(sav[2]+sav[3]);  //圆点处算贡献不用考虑啥ans+=(ll)f[y][2]*(sav[1]+sav[3]);ans+=(ll)f[y][3]*(sav[1]+sav[2]+sav[3]);}else{  //方点处要考虑路径的终点是在点双内还是在另一棵子树内if(pos[x-n]==1) ans+=(ll)(v[x].size()-1)*(f[y][2]+f[y][3]);  //终点在该点双内else if(pos[x-n]==2) ans+=(ll)(v[x].size()-1)*(f[y][1]+f[y][3]);else ans+=(ll)(v[x].size()-1)*(f[y][1]+f[y][2]+f[y][3]);  //v[x].size()-1的原因是不算这棵子树与方点直接相连的那个圆点,因为在下面已经算过了ans+=(ll)f[y][1]*(sav[2]+sav[3]);  //只是经过点双ans+=(ll)f[y][2]*(sav[1]+sav[3]);ans+=(ll)f[y][3]*(sav[1]+sav[2]+sav[3]);if(pos[x-n]==1||pos[x-n]==3) ans+=(ll)f[y][2]*(sav[2]);if(pos[x-n]==2||pos[x-n]==3) ans+=(ll)f[y][1]*(sav[1]);}for(int i=1;i<=3;i++)sav[i]+=f[y][i];}if(x<=n){for(int i=1;i<=3;i++) f[x][i]=sav[i];}else if(pos[x-n]==1){f[x][1]=v[x].size()-1+sav[1];f[x][3]=sav[2]+sav[3];}else if(pos[x-n]==2){f[x][2]=v[x].size()-1+sav[2];f[x][3]=sav[1]+sav[3];}else f[x][3]=v[x].size()-1+sav[1]+sav[2]+sav[3];
}
int main(){ios::sync_with_stdio(0);cin.tie(nullptr);cin>>c>>n>>m;for(int i=1;i<=m;i++){int x,y;char z;cin>>x>>y>>z;add(x,y,z);add(y,x,z);}fl=n;dfs(1,0);get(1,0);for(int x=1;x<=n;x++){  //将每条边预处理进一个点双for(int i=head[x];i;i=e[i].next){int y=e[i].to,z=find(x,y);if(!z) continue;a[z-n].push_back({x,e[i].tp});}}for(int i=n+1;i<=fl;i++){  //求与两种边都相连的点的数量int tmp=0;ff[1]=ff[2]=0;for(int x:v[i]) vis[x][1]=vis[x][2]=0;for(auto [x,tp]:a[i-n]) vis[x][tp]=ff[tp]=1;for(int x:v[i]) tmp+=(vis[x][1]&vis[x][2]);pos[i-n]=ff[1]+ff[2]*2;if(pos[i-n]==3) ans+=(ll)v[i].size()*(v[i].size()-1)/2-(tmp==2);}sol(1,0);cout<<ans<<'\n';return 0;
}

文章转载自:

http://ADPxU71F.ztnmc.cn
http://zqTVjo8D.ztnmc.cn
http://X7O4HCvt.ztnmc.cn
http://F1Nw2fzV.ztnmc.cn
http://zFOkouQx.ztnmc.cn
http://GDeGeOT2.ztnmc.cn
http://zp5X2neV.ztnmc.cn
http://mo3rwAJk.ztnmc.cn
http://k1yoQLHc.ztnmc.cn
http://16Tt1Ryy.ztnmc.cn
http://9stKYXla.ztnmc.cn
http://gh49BqRT.ztnmc.cn
http://tOFpQpge.ztnmc.cn
http://HzAnxbPT.ztnmc.cn
http://JT3R9ig1.ztnmc.cn
http://rryuloCS.ztnmc.cn
http://CTQySBbi.ztnmc.cn
http://ORrg1GSf.ztnmc.cn
http://KAGJkXip.ztnmc.cn
http://4pkObtx5.ztnmc.cn
http://xuLwAr81.ztnmc.cn
http://FaUoFFab.ztnmc.cn
http://ByoSet1M.ztnmc.cn
http://R7lR3jgs.ztnmc.cn
http://ENk1LLGV.ztnmc.cn
http://49h1QpJb.ztnmc.cn
http://qoWPkZ8G.ztnmc.cn
http://vh5jlLkY.ztnmc.cn
http://daq5osbf.ztnmc.cn
http://yp8nFE6e.ztnmc.cn
http://www.dtcms.com/a/379357.html

相关文章:

  • 获Gartner®认可!锐捷入选2025年Gartner园区网络基础设施管理与运营软件市场指南
  • 告别环境地狱!Java生态“AI原生”解决方案入驻 GitCode​
  • 【leetcode】322. 零钱兑换
  • 数据清洗:缺失值、异常值与重复数据处理全解析
  • 审计过程中常见的文档缺失问题如何避免
  • 图像投影(透视)变换
  • Spring Cloud Gateway:下一代API网关的深度解析与实战指南
  • springboot 启动流程及 ConfigurationClassPostProcessor解析
  • git中rebase和merge的区别
  • 66-python中的文件操作
  • 【PostgreSQL内核学习 —— (SeqScan算子)】
  • 资源图分配算法
  • SpringBoot 中单独一个类中运行main方法报错:找不到或无法加载主类
  • 2025全球VC均热板竞争格局与核心供应链分析
  • 用“折叠与展开”动态管理超长上下文:一种 Token 高效的外部存储操作机制
  • 深度解析指纹模块选型与落地实践
  • 从用户体验到交易闭环的全程保障!互联网行业可观测性体系建设白皮书发布
  • grafana启用未签名插件
  • MySQL 数据类型与运算符详解
  • 编程实战:类C语法的编译型脚本解释器(五)变量表
  • 原生js拖拽
  • 数据结构--Map和Set
  • P1122 最大子树和
  • 【3DV 进阶-3】Hunyuan3D2.1 训练代码详细理解之-Flow matching 训练 loss 详解
  • Python写算法基础
  • 数据结构 优先级队列(堆)
  • FunASR GPU 环境 Docker 构建完整教程(基于 CUDA 11.8)
  • 探讨:线程循环与激活(C++11)
  • 拆解格行随身WiFi多网协同模块:智能切网+马维尔芯片,如何实现5秒跨网?
  • 游泳溺水检测识别数据集:8k图像,2类,yolo标注