模拟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\)可以看出我的码力还很弱,或曰,码之前思路还不是很清楚,码之前除了大概思路,还要多思考细节才行。

浙公网安备 33010602011771号