Loading

模拟27—「牛半仙的妹子图·牛半仙的妹子Tree·牛半仙的妹子序列」

牛半仙的妹子图

这是个水题(并不能阻止我这题爆零),有很多种方法干掉他。
我考场上的思路是看到\(c_i\leq600\),然后想搞一个\(O(cq)\)的算法。

其实很简单,就是用并查集来维护,每个并查集维护一个\(\mathrm{bitset}\),代表有哪些种类的妹子。

和并联通块的时候看有没有新妹子,如果有就存起来,作为一个界限。

对于每个询问在线处理就行了,\(calc\)函数需要注意一些细节,否则爆零

这种细节比较多的题写完就要拍,向我一样留到最后\(30min\),打出来拍之后找到错误数据,没调试出来,白打一个拍还浪费了时间。

code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <iostream>
#define int long long 
#define R register int
#define printf Ruusupuu = printf
#define scanf Ruusupuu = scanf 
#define freopen rsp_5u = freopen

int Ruusupuu ; 
FILE * rsp_5u ;

using namespace std ;
typedef long long L ;
typedef double D ;
const int N = 5e5 + 10 ;
const int C = 6e2 + 10 ;	
const int Inf = 0x3f3f3f3f3f3f3f ;

inline void of(){ freopen( "in.in" , "r" , stdin ) , freopen( "out.out" , "w" , stdout ) ; }
inline void cf(){ fclose( stdin ) , fclose( stdout ) ; }
inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) , ch = getchar() ;
	return fg ? -w : w ; 
}

int n , m , x , y , z , beg , q , opt , M , cnt ;
int c [N] , fa [N] , head [N] , zt , nop , l , r , last ;
bitset< C > rch [N] , mst ;
struct E{ int f , t , w , net ; } a [N << 1] ;
struct S{ int w , num ; } nz [N] ;
bool fg [N] ; 

int fd( int x ){
	if( x == fa [x] ) return x ;
	else return fa [x] = fd( fa [x] ) ; 
}

inline bool cmp( E a , E b ){ return a.w < b.w ; }

inline void add( int f , int t ){
	a [++ cnt].f = f , a [cnt].t = t , a [cnt].w = z ; 
	a [cnt].net = head [f] , head [f] = cnt ;
}

void sc(){
	n = read() , m = read() , q = read() , beg = read() , opt = read() ;
	if( opt ) M = read() ; memset( head , -1 , sizeof( head ) ) ;
	for( R i = 1 ; i <= n ; i ++ ) c [i] = read() , rch [i][c [i]] = 1 , fa [i] = i ;
	for( R i = 1 ; i <= m ; i ++ ) x = read() , y = read() , z = read() , add( x , y ) , add( y , x ) ;
}

inline int calc(){
	int ans = 0 ;
	for( R i = 1 ; i <= nop ; i ++ ){ //区间:左闭右开( a [i].w -> a [i + 1].w - 1 )
		
		if( l > nz [i].w && l >= nz [i + 1].w && !ans ) continue ;
		if( r < nz [i].w ) break ;
		
		if( l >= nz [i].w && l < nz [i + 1].w && r >= nz [i].w && r < nz [i + 1].w ){
			ans += nz [i].num * ( r - l + 1 ) ;
			continue ;
		}
		
		else if( l >= nz [i].w && l < nz [i + 1].w && i != nop - 1 ){
			ans += nz [i].num * ( nz [i + 1].w - l ) ;
			continue ;
		}
		else if( r >= nz [i].w && r < nz [i + 1].w ){
			ans += nz [i].num * ( r - max( nz [i].w , l ) + 1 ) ;
			continue ;
		}
		if( i != nop - 1 ) ans += ( nz [i + 1].w - nz [i].w ) * nz [i].num ;
	} return ans ;
}

void work(){
	sort( a + 1 , a + 1 + cnt , cmp ) ;	
	nz [++ nop].w = 0 , nz [nop].num = 1 ;	
	for( R i = 1 ; i <= cnt ; i ++ ){
		x = a [i].f , y = a [i].t ;
		int fx = fd( x ) , fy = fd( y ) ;
		if( fx == fy ) continue ;
		fa [fy] = fx , rch [fx] |= rch [fy] ;
		int j = rch [fx].count() ;
		if( fd( x ) == fd( beg ) && !fg [j] ) nz [++ nop].w = a [i].w , nz [nop].num = j , fg [j] = 1 ; 			
	} nz [++ nop].w = Inf ;
	
	if( opt ){
		while( q -- ){
			l = read() , r = read() ;
			l = ( ( l ^ last ) % M ) + 1 , r = ( ( r ^ last ) % M ) + 1 ;
			if( l > r ) swap( l , r ) ;
			printf( "%lld\n" , last = calc() ) ;
		}
	}
	
	else{
		while( q -- ){
			l = read() , r = read() ;
			if( l > r ) swap( l , r ) ;
			printf( "%lld\n" , calc() ) ;
		}
	}

}

signed main(){
//	of() ;
	sc() ;
	work() ;
//	cf() ;
	return 0 ;
}

牛半仙的妹子Tree

这个题想打一下询问分块做法,先鸽一下吧。

实现大神\(\mathrm{yspm}\)的做法,不过由于我弱的原因,所以打了时间标记线段树(很上头)。

分类讨论一下传染自己的点在自己的子树内还是子树外。

如果在子树内,根据距离\(\leq\)时差就能被感染的思路,易得我们只需要维护一个\(dfs\)序为下标的线段树,支持区间取\(min\)就行了。

如果在子树外面,那么感染他的点和他之间一定有一个\(lca\),还是距离\(\leq\)时差,可得我们需要让\(\mathrm{dep_y+Time_y-2*dep_{lca}}\)最小。

这个\(\mathrm{-2*dep_{lca}}\)很烦,由于我们不知道这个点将来会用哪个点当作\(lca\)传染\(x\),所以我们没有办法把它一块塞到线段树中。

但是,对于不同的点,他的\(dep\)是固定的,我们在建树的时候就可以搞出来。

所以,只需要给每个节点都开两个域\(ex,data\),分别保存额外域和答案即可。

实现之前一定要想好细节,比如在本题中\(upd\)的时候把答案域处理好,\(ask\)的时候直接查询就行了,否则会出现重复或者漏掉\(ex\)域。

本弱实现较麻烦,就不放代码了。

牛半仙的妹子序列

这个考场上就有思路了,但是没打(其实还是主要因为有一个地方不是很明白)。

很容易看出来是线段树维护单调栈的板子,我就是没想到区间两边的信息是直接相加的。

其实也不难想到,手模一下就出来了,考场上一直以为乘,其实没什么道理。

推荐看God knows的题解

只是在本题中,我们需要把data的意义设置为

保存x的左区间中大于右区间最大值的部分中单调递减的子序列的方案数

calc设置为

x的节点中值大于val并且单调递减的子序列的方案数

然后直接写就行了。

总结

这场考试考场上会的题不少,然而就只有\(20\)分,非常失败。

\(\mathrm{T1}\)自认为"切了"之后暂时没打拍,想去看看别的。

然后看\(\mathrm{T2}\),研究了很长时间没什么思路,当时一直在研究序列上怎么解。
然后由于没有掰开两边分情况考虑,然后炸了。

其实只要分开两边之后,推广到树上虽然有难度,但是也不会浪费那么多时间了。

然后看到\(\mathrm{T3}\)是原题改编之后只有不到一个小时了,想了会,由于比较着急,也没想到是方案数相加,最后想了一会就剩\(30min\),决策了一下去打了\(\mathrm{T1}\)的拍,结果刚拍出来考试就结束了,眼睁睁看着交上去一个错误的答案然后爆炸。

还是败在策略上了,一个题没思路先码暴力去别的,暴力都码完,或者说确定可以长时间思考了在开始肝,否则心里面着急也想不到什么东西。

策略更正:打正解打完就拍,\(20min\)没思路先打暴力,记录一下思考到了什么地方,以供后面纠正参考,暴力都打完之后再深入部分分或者正解。

\(\mathrm{T2}\)调试了\(6h\)可以看出我的码力还很弱,或曰,码之前思路还不是很清楚,码之前除了大概思路,还要多思考细节才行。

posted @ 2021-07-29 21:12  Soresen  阅读(111)  评论(0)    收藏  举报