天下第一 txdy (LCT+双指针+线段树)
天下第一 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+双指针+线段树)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么用AI做书(怎么用ai做书籍封面)
- 下一篇: 黑客(续) (压位高精+状压dp)