天下第一 txdy

  • description
  • solution
  • code

description

djq_cpp 是天下第一的。
djq_cpp 给了你一个 n 个点 m 条边的无向图(无重边自环),点标号为 1 ∼n。祂想要考考你,
有多少对整数对 (l, r) 满足:
• 1 ≤l ≤r ≤n
• 如果把区间 [l, r] 内的点以及它们之间的边保留下来,其他的点和边都扔掉,那么留下来的这张
图恰好是一条链。
注:链必须是连通的图。特别地,一个点的图也是链。

Input
第一行两个非负整数 n, m。
接下来 m 行每行两个正整数 u, v(1 ≤u, v ≤n, u ̸= v),表示 u, v 之间有一条边。
保证图无重边自环。

Output
输出一个整数表示答案。

Sample
Input
3 3
1 2
2 3
3 1
Output
5
Explanation
(1, 1), (1, 2), (2, 2), (2, 3), (3, 3) 是符合条件的整数对。

Constraint
对于 10% 的数据,n, m ≤100。
对于 20% 的数据,n, m ≤1000。
对于 60% 的数据,n, m ≤50000。
对于另外 10% 的数据,保证每个点度数 ≤2。
对于 100% 的数据,1 ≤n ≤250000, 0 ≤m ≤250000。

solution

首先清楚为链的必要条件

  • 点数减边数=1=1=1
  • 无环
  • 每个点度数≤2\le 2≤2

正解就是寻找辅助工具来判断点[l,r][l,r][l,r]内的所有边是否满足上面所有条件

实际上,三个条件可以分开保证后再合并

双指针法

考虑枚举点区间的右端点rrr

然后找到满足[l,r][l,r][l,r]内每个点度数都≤2\le 2≤2的最小的lll,记为f[r]f[r]f[r]

显然随着rrr的右移,lll只会变大不会变小

  • 具体而言,利用度数did_idi​,每次右端点+1+1+1后,加入新右端点连接的所有在[l,r+1][l,r+1][l,r+1]的边,并判断边连接两点是否度数超过222,如果超过就选择右移左端点,断掉原来左端点的所有已连边,直到度数不超过222才停止左端点右移

同理,双指针法

考虑枚举右端点rrr

然后找到满足[l,r][l,r][l,r]内所有边加入后无环的最小的lll,记为g[r]g[r]g[r]

显然随着rrr的右移,lll只会变大不会变小

  • 具体而言,与维护度数本质上是完全一样的,但是这里涉及到了边的动态变化,那就不得不使用动态树LCT\text{LCT}LCT了,判断新右端点的边连接的两个点原本已经连接,这条边加入就会形成环,右移左端点,断掉原来左端点的所有已连边。这些完完全全就是LCT\text{LCT}LCT的模板专场了

为了满足以上两个条件,则真正的左端点是l=max⁡(g[r],f[r])l=\max(g[r],f[r])l=max(g[r],f[r])

最后就是必须是同一条链的连通性问题

要求点数减边数=1=1=1,就可以用线段树维护点数减边数最小值,再记录最小值的个数

  • 具体而言,对于每一个右端点rrr,线段树叶子结点lll表示的意思是[l,r][l,r][l,r]区间内所有边都加入后,点数减边数的最小值。写法上,是将线段树与无环的判断放在一起写的

考场上硬刚LCT的人真的是强者

code

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 250005
int n, m, top, sta[maxn], pos[maxn], deg[maxn];
vector < int > G[maxn];namespace LCT {struct node { int son[2], fa, tag; }t[maxn];bool root( int x ) { return t[t[x].fa].son[0] ^ x and t[t[x].fa].son[1] ^ x; }void reverse( int x ) { swap( t[x].son[0], t[x].son[1] ); t[x].tag ^= 1; }void pushdown( int x ) {if( ! t[x].tag ) return;if( t[x].son[0] ) reverse( t[x].son[0] );if( t[x].son[1] ) reverse( t[x].son[1] );t[x].tag ^= 1;}void rotate( int x ) {int fa = t[x].fa;int Gfa = t[fa].fa;int d = t[fa].son[1] == x;if( ! root( fa ) ) t[Gfa].son[t[Gfa].son[1] == fa] = x;t[x].fa = Gfa;if( t[x].son[d ^ 1] ) t[t[x].son[d ^ 1]].fa = fa;t[fa].son[d] = t[x].son[d ^ 1];t[x].son[d ^ 1] = fa;t[fa].fa = x;}void splay( int x ) {sta[++ top] = x; int y = x;while( ! root( y ) ) sta[++ top] = y = t[y].fa;while( top ) pushdown( sta[top --] );while( ! root( x ) ) {int fa = t[x].fa, Gfa = t[fa].fa;if( ! root( fa ) ) (t[Gfa].son[0] == fa) ^ (t[fa].son[0] == x) ? rotate( x ) : rotate( fa );rotate( x ); }}void access( int x ) { for( int son = 0;x;son = x, x = t[x].fa ) splay( x ), t[x].son[1] = son; }void makeroot( int x ) { access( x ); splay( x ); reverse( x ); }void split( int x, int y ) { makeroot( x ); access( y ); splay( y ); }void link( int x, int y ) { makeroot( x ); t[x].fa = y; }void cut( int x, int y ) { split( x, y ); t[x].fa = t[y].son[0] = 0; }int findroot( int x ) { access( x ); splay( x ); while( t[x].son[0] ) pushdown( x ), x = t[x].son[0]; splay( x ); return x; }bool check( int x, int y ) { makeroot( x ); return findroot( y ) == x; }
}struct node { int ans, cnt, tag; }t[maxn << 2];
namespace SGT {#define lson now << 1#define rson now << 1 | 1#define mid  (l + r >> 1)node operator + ( node x, node y ) {if( x.ans < y.ans ) return x;else if( x.ans > y.ans ) return y;else { x.cnt += y.cnt; return x; }}void pushdown( int now ) {if( ! t[now].tag ) return;t[lson].ans += t[now].tag;t[lson].tag += t[now].tag;t[rson].ans += t[now].tag;t[rson].tag += t[now].tag;t[now].tag = 0;}void build( int now, int l, int r ) {t[now] = { 0, 1, 0 };if( l == r ) return;build( lson, l, mid );build( rson, mid + 1, r );t[now] = t[lson] + t[rson];}void modify( int now, int l, int r, int L, int R, int v ) {if( R < l or r < L ) return;if( L <= l and r <= R ) { t[now].ans += v; t[now].tag += v; return; }pushdown( now );modify( lson, l, mid, L, R, v );modify( rson, mid + 1, r, L, R, v );t[now] = t[lson] + t[rson]; t[now].tag = 0; //因为重载的写法问题 t[now]=t[lson]/t[rson] 会把儿子的懒标记也赋过来 但实际上now这里是不该存在懒标记的}node query( int now, int l, int r, int L, int R ) { if( r < L or R < l ) return { inf, 0, 0 };if( L <= l and r <= R ) return t[now];pushdown( now );return query( lson, l, mid, L, R ) + query( rson, mid + 1, r, L, R );}
}int main() {scanf( "%d %d", &n, &m );for( int i = 1, u, v;i <= m;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}for( int r = 1, l = 1;r <= n;r ++ ) {//枚举位置r作为右端点 //双指针l求出最远的满足度数<=2的条件sort( G[r].begin(), G[r].end() );for( int i : G[r] ) { //把每条r相连的属于[l,r]的边加进来 if( i > r ) break;while( l <= i and ( deg[i] == 2 or deg[r] == 2 ) ) {//在加这条边之前 两点度数就已经等于2 加了过后肯定不满足度数<=2的条件//这个时候说明l左端点应当右移//知道两点度数都<2为止 for( int j : G[l] )if( l < j and ( j < r or ( j == r and l < i ) ) )deg[l] --, deg[j] --;l ++;}if( l <= i ) deg[i] ++, deg[r] ++;//这条边还在调整后新区间内[l',r]内才真的加入}pos[r] = l;}SGT :: build( 1, 1, n );long long ans = 0;for( int r = 1, l = 1;r <= n;r ++ ) {SGT :: modify( 1, 1, n, 1, r, 1 );for( int i : G[r] ) {if( i > r ) break;while( l <= i and LCT :: check( i, r ) ) {for( int j : G[l] )if( l < j and ( j < r or ( j == r and l < i ) ) )LCT :: cut( l, j );/*不能写成if(l<j and j<= r) 上面同理因为新加一条边是i-r有可能i就恰好是l发现i和r已经联通就必须去除掉l连接的边l里面就会访问到l-r这条边 但是这里还没有加错误写法就会删去一条根本没加过的边导致错误 */l ++;}if( l <= i ) LCT :: link( i, r );SGT :: modify( 1, 1, n, 1, i, -1 );}pos[r] = max( pos[r], l );node now = SGT :: query( 1, 1, n, pos[r], r );if( now.ans == 1 ) ans += now.cnt;}printf( "%lld\n", ans );return 0;
}

天下第一 txdy (LCT+双指针+线段树)相关推荐

  1. 【Nowcoder】2021牛客暑假集训营(第七场): xay loves trees 双指针 + 线段树 + 尺取

    传送门 题意 给你两个树,求一个最大集合,要求集合内的任意两个点在第一个树上,比如是祖先关系,在第二棵树,不能存在祖先关系 分析 某人吐槽我的题解写的太简单了,然后我觉得...承认错误死不悔改 这道题 ...

  2. bzoj 4025 二分图——线段树分治+LCT

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4025 线段树分治,用 LCT 维护链的长度即可.不过很慢. 正常(更快)的方法应该是线段树分 ...

  3. BZOJ 4817: [Sdoi2017]树点涂色(LCT+树剖+线段树)

    题目描述 Bob有一棵 nn 个点的有根树,其中1号点是根节点.Bob在每个点上涂了颜色,并且每个点上的颜色不同. 定义一条路径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色. Bob ...

  4. 【BZOJ4817】【SDOI2017】树点涂色 [LCT][线段树]

    树点涂色 Time Limit: 10 Sec  Memory Limit: 128 MB [Submit][Status][Discuss] Description Bob有一棵n个点的有根树,其中 ...

  5. LOJ 121 「离线可过」动态图连通性——LCT维护删除时间最大生成树 / 线段树分治...

    题目:https://loj.ac/problem/121 离线,LCT维护删除时间最大生成树即可.注意没有被删的边的删除时间是 m+1 . 回收删掉的边的节点的话,空间就可以只开 n*2 了. #i ...

  6. 洛谷 - P6292 区间本质不同子串个数(SAM+LCT+线段树)

    题目链接:点击查看 题目大意:给出一个长度为 n 的字符串,再给出 m 次询问,每次询问需要回答区间 [ l , r ] 内有多少个本质不同的字符串 题目分析:首先简化模型,回顾一下如何求解 &quo ...

  7. 【线段树】【LCT】【LCA】树点涂色(luogu 3703)

    树点涂色 luogu 3703 题目大意 给出一棵树,每个节点的初始颜色不同,做若干操作: 1.在一个点到根节点路径上染上一种新的颜色 2.查询一条路径上有多少种不同的颜色 3.查询一个点x,使该点到 ...

  8. BZOJ4653: [Noi2016]区间(线段树 双指针)

    题意 题目链接 Sol 按照dls的说法,一般这一类的题有两种思路,一种是枚举一个点\(M\),然后check它能否成为答案.但是对于此题来说好像不好搞 另一种思路是枚举最小的区间长度是多少,这样我们 ...

  9. BZOJ 3779 重组病毒 LCT,树链剖分,线段树

    题意: 给一棵树,每个点一开始颜色互不相同,支持三个操作                 1. 将一个点到根的路径染成一种新的颜色                 2. 将一个新的点设为根,并将原来的 ...

最新文章

  1. Linux tail 命令详解
  2. 沈向洋、黄学东等大咖助阵,IoT in Action微软深圳物联网大会即刻报名
  3. c语言 将url图片存到本地_一个22万张NSFW图片的鉴黄数据集?我有个大胆的想法……...
  4. python软件下载安装要钱吗-PyCharm下载和安装详细步骤
  5. zpl代码可以编译的特殊字符_国际C语言混乱代码大赛(IOCCC)代码解析
  6. TEdit扩展:做成多按钮的Edit,可用作浏览器地址栏
  7. LeetCode 722. CPP删除注释(逻辑题)
  8. 续Gulp使用入门三步压缩CSS
  9. SpringCloud微服务(05):Zuul组件,实现路由网关控制
  10. axure菜单移动隐藏_如何使用隐藏的移动网络更快地完成工作
  11. 微胖女孩穿什么样的衣服好看?
  12. 【画图专题】sns.heatmap的用法简介
  13. LSMTree - SStable 初体验
  14. 基于C++编译的车牌识别系统
  15. html table 斜线表头,Table表格加斜线表头
  16. DeepMind 联合创始人加盟 Google AI【智能快讯】
  17. Pigeon中的Netty应用
  18. 3.2.2 nodeMCU固件烧录
  19. GF系列卫星分辨率介绍
  20. JSP如何统计页面访问次数

热门文章

  1. 资料分享 | 数据挖掘实例资料分享来袭
  2. github怎么隐藏自己的pr记录_记便签的软件哪个好?怎么及时记录自己的想法
  3. flex布局_Flex布局,真香
  4. dataframe 修改某列_python dataframe操作大全数据预处理过程(dataframe、md5)
  5. cent os重置mysql,linux mysql 能登陆不能修改用户(cent os 6.2)解决思路
  6. SimpleXMLRPC_python xmlrpclib SimpleXMLRPCServer 模块
  7. 如何在mysql中打开mongodb_图解:如何从MySQL移植到MongoDB
  8. pearson相关系数_Pearson(皮尔逊)相关系数
  9. leetcode349. 两个数组的交集(思路+详解)
  10. Java Number Math 类方法