• 「Gym102759E」Chemistry


    题目

    点这里看题目。

    分析

    首先思考一下链的要求:

    1. 连通
    2. 图是一棵树——没有环
    3. 图上任何一个点的度数 (le 2)

    对于区间 ([l,r]),当我们固定 (r) 的时候,使得 2,3 条件满足的 (l) 一定也对应了一段区间。但是条件 1 并不是,我们先放一放。

    找出条件 2 中最小的 (l):不妨记为 (f_r),那么 (f) 一定是关于 (r) 单调不降的。因此尺取扫描,顺便维护一棵 LCT 判断是否成环;

    找出条件 3 中最小的 (l):不妨记为 (g_r),那么 (g) 也一定是关于 (r) 单调不降的。同样尺取,一种维护方式是用线段树找最大度数,但另一种更加优雅的方式是,如果所有点的度数都 (le 2),那么 (le 2) 的点的总个数一定为区间长度,我们直接用桶就好了。

    回头再来看条件 1,我们相当于要求 ([max{f_r,g_r},r]) 中满足图连通的区间的个数。由于此时图一定是森林,那么图连通等价于 (|V|-|E|=1)。而在这种条件下 (|V|-|E|ge 1),因此维护每个左端点的 (|V|-|E|),用线段树找出区间内的最小值及其个数即可。

    小结:

    1. 判断最大值的方法比较巧妙,我们实际上是利用较紧的值域限制,从而高效地将最大值转化到数量
    2. 注意一下森林上的连通判断。这个思路其实是,由于连通性根本不单调,因此我们只能将连通性转化成数值来判断。(|V|-|E|) 是一个很自然的抓手。
    3. 关注单调性。所有条件的交集不单调不代表我们不可以首先研究一些保证单调的条件,再回头看整个问题

    代码

    #include <cstdio>
    #include <iostream>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    typedef long long LL;
    
    const int MAXN = 3e5 + 5;
    
    template<typename _T>
    void read( _T &x )/*{{{*/
    {
    	x = 0; char s = getchar(); int f = 1;
    	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    	x *= f;
    }/*}}}*/
    
    template<typename _T>
    void write( _T x )/*{{{*/
    {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }/*}}}*/
    
    struct Edge/*{{{*/
    {
    	int to, nxt;
    }Graph[MAXN << 1];/*}}}*/
    
    int head[MAXN], lim1[MAXN], lim2[MAXN];
    int N, M, cnt;
    
    void AddEdge( const int from, const int to )/*{{{*/
    {
    	Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
    	head[from] = cnt;
    }/*}}}*/
    
    namespace CalcLimit1/*{{{*/
    {
    	int deg[MAXN], buc[MAXN];
    
    	void Add( const int x ) { buc[deg[x]] --; buc[++ deg[x]] ++; }
    	void Sub( const int x ) { buc[deg[x]] --; buc[-- deg[x]] ++; }
    	bool Chk( const int s ) { return buc[0] + buc[1] + buc[2] == s; }
    
    	void Work()
    	{
    		for( int l = 1, r = 1 ; r <= N ; lim1[r ++] = l )
    		{
    			buc[deg[r] = 0] ++;
    			for( int i = head[r], v ; i ; i = Graph[i].nxt )
    			{
    				v = Graph[i].to;
    				if( l <= v && v <= r ) Add( r ), Add( v );
    			}
    			for( ; ! Chk( r - l + 1 ) ; l ++ )
    			{
    				for( int i = head[l], v ; i ; i = Graph[i].nxt )
    				{
    					v = Graph[i].to;
    					if( l <= v && v <= r ) Sub( l ), Sub( v );
    				}
    				buc[deg[l]] --;
    			}
    		}
    	}
    }/*}}}*/
    
    namespace CalcLimit2/*{{{*/
    {
    	bool rev[MAXN];
    	int ch[MAXN][2], fa[MAXN];
    
    	bool Chk( const int x ) { return ch[fa[x]][1] == x; }
    	bool isrt( const int x ) { return ch[fa[x]][0] ^ x && ch[fa[x]][1] ^ x; }
    	bool nrt( const int x ) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
    	void Reverse( const int x ) { if( x ) std :: swap( ch[x][0], ch[x][1] ), rev[x] ^= 1; }
    	void Normalize( const int x ) { if( rev[x] && x ) Reverse( ch[x][0] ), Reverse( ch[x][1] ), rev[x] = false; }
    	void TagOff( const int x ) { if( nrt( x ) ) TagOff( fa[x] ); Normalize( x ); }
    
    	void Rotate( const int x )/*{{{*/
    	{
    		if( ! x || isrt( x ) ) return ;
    		int y = fa[x], z = fa[y], side = Chk( x ), son = ch[x][! side];
    		if( nrt( y ) ) ch[z][Chk( y )] = x; ch[x][! side] = y, ch[y][side] = son;
    		if( son ) fa[son] = y; fa[y] = x, fa[x] = z;
    	}/*}}}*/
    
    	void Splay( const int x )/*{{{*/
    	{
    		TagOff( x );
    		for( int y ; nrt( x ) ; Rotate( x ) )
    			if( nrt( y = fa[x] ) ) Rotate( Chk( y ) == Chk( x ) ? y : x );
    	}/*}}}*/
    
    	void Access( int x ) { for( int y = 0 ; x ; x = fa[y = x] ) Splay( x ), ch[x][1] = y; }
    	void MakeRt( const int x ) { Access( x ), Splay( x ), Reverse( x ); }
    	void Link( const int x, const int y ) { MakeRt( x ), fa[x] = y; }
    	void Cut( const int x, const int y ) { MakeRt( x ), Access( y ), Splay( x ), ch[x][1] = fa[y] = 0; }
    
    	int FindRt( int x ) /*{{{*/
    	{ 
    		Access( x ), Splay( x ); 
    		while( ch[x][0] ) Normalize( x ), x = ch[x][0]; 
    		Splay( x ); return x;
    	}/*}}}*/
    
    	bool Same( const int x, const int y ) { MakeRt( x ); return FindRt( y ) == x; }
    
    	void Work()/*{{{*/
    	{
    		for( int l = 1, r = 1 ; r <= N ; lim2[r ++] = l )
    			for( int i = head[r], v ; i ; i = Graph[i].nxt )
    			{
    				if( ( v = Graph[i].to ) > r ) continue;
    				for( ; l <= v && Same( v, r ) ; l ++ )
    					for( int j = head[l], w ; j ; j = Graph[j].nxt )
    					{
    						w = Graph[j].to;
    						if( l <= w && w < r ) Cut( l, w );
    					}
    				if( l <= v && ! Same( v, r ) ) Link( v, r );
    			}
    	}/*}}}*/
    }/*}}}*/
    
    namespace Finale/*{{{*/
    {
    	struct Element/*{{{*/
    	{
    		int mn, cnt;
    
    		Element(): mn( 1e9 ), cnt( 0 ) {}
    		Element( int V ): mn( V ), cnt( 1 ) {}
    		
    		Element operator + ( const Element &b )/*{{{*/
    		{
    			Element ret;
    			if( mn < b.mn ) ret.mn = mn, ret.cnt = cnt;
    			if( mn > b.mn ) ret.mn = b.mn, ret.cnt = b.cnt;
    			if( mn == b.mn ) ret.mn = mn, ret.cnt = cnt + b.cnt;
    			return ret;
    		}/*}}}*/
    	};/*}}}*/
    
    	Element tre[MAXN << 2]; int tag[MAXN << 2];
    
    	void Upt( const int x ) { tre[x] = tre[x << 1] + tre[x << 1 | 1]; }
    	void Add( const int x, const int v ) { tre[x].mn += v, tag[x] += v; }
    	void Normalize( const int x ) { if( tag[x] ) Add( x << 1, tag[x] ), Add( x << 1 | 1, tag[x] ), tag[x] = 0; }
    
    	void Build( const int x, const int l, const int r )/*{{{*/
    	{
    		if( l > r ) return ;
    		if( l == r ) { tre[x] = Element( 0 ); return ; }
    		int mid = ( l + r ) >> 1;
    		Build( x << 1, l, mid );
    		Build( x << 1 | 1, mid + 1, r );
    		Upt( x );
    	}/*}}}*/
    
    	void Update( const int x, const int l, const int r, const int segL, const int segR, const int delt )/*{{{*/
    	{
    		if( segL <= l && r <= segR ) { Add( x, delt ); return ; }
    		int mid = ( l + r ) >> 1; Normalize( x );
    		if( segL <= mid ) Update( x << 1, l, mid, segL, segR, delt );
    		if( mid  < segR ) Update( x << 1 | 1, mid + 1, r, segL, segR, delt );
    		Upt( x );
    	}/*}}}*/
    
    	Element Query( const int x, const int l, const int r, const int segL, const int segR )/*{{{*/
    	{
    		if( segL > segR ) return Element();
    		if( segL <= l && r <= segR ) return tre[x];
    		int mid = ( l + r ) >> 1; Normalize( x );
    		if( segR <= mid ) return Query( x << 1, l, mid, segL, segR );
    		if( mid  < segL ) return Query( x << 1 | 1, mid + 1, r, segL, segR );
    		return Query( x << 1, l, mid, segL, segR ) + Query( x << 1 | 1, mid + 1, r, segL, segR );
    	}/*}}}*/
    
    	LL Solve()/*{{{*/
    	{
    		Build( 1, 1, N ); LL ans = 0;
    		for( int i = 1 ; i <= N ; i ++ )
    		{
    			Update( 1, 1, N, 1, i, 1 );
    			for( int k = head[i] ; k ; k = Graph[k].nxt )
    				if( Graph[k].to <= i ) Update( 1, 1, N, 1, Graph[k].to, -1 );
    			Element tmp = Query( 1, 1, N, std :: max( lim1[i], lim2[i] ), i );
    			if( tmp.mn == 1 ) ans += tmp.cnt;
    		}
    		return ans;
    	}/*}}}*/
    }/*}}}*/
    
    int main()
    {
    	read( N ), read( M );
    	rep( i, 1, M )
    	{
    		int u, v; read( u ), read( v );
    		AddEdge( u, v ), AddEdge( v, u );
    	}
    	CalcLimit1 :: Work();
    	CalcLimit2 :: Work();
    	write( Finale :: Solve() ), putchar( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    Stream 和 byte[] 之间的转换
    C# Process类_进程_应用程序域与上下文之间的关系
    C# Process类_进程管理器Demo
    C# attribute_特性
    SqlDataAdapter类
    SqlDataReader类
    SqlCommand类
    SqlConnection类
    DataTable类
    C# 语法技巧_三目运算_switch_case
  • 原文地址:https://www.cnblogs.com/crashed/p/15367360.html
Copyright © 2020-2023  润新知