复盘
7:40 开题
看 T1 ,妈呀,一上来就数数?盯了几分钟后发现会了,不就是 LCS 计数嘛
继续看,T2 看上去很恶心,线段覆盖,感觉可能是贪心什么的
再看 T3,先想了个 n 2 n^2 n2 的式子,再一看哎 max 肯定有决策单调性啊,而且决策还有区间限制,那刚好套个之前模拟赛的技巧,放到线段树上做,秒了?
胡完之后看 T4 ,推箱子,感觉是什么神秘不可做题
8:05 开始码了,发现 T1 还得套个小容斥,8:20 过样例了
接下来决定先写 T3,8:30 先交了个 n 2 n^2 n2,上决策单调性分治到 8:55 调过交了
然后放一边拍着,看 T2 ,简化了一下题意就是 若干个区间必须被一整段覆盖,转移点好像只有每个线段的两端点,嗯?梦回 NOIP T4?
去个厕所冷静下,发现实际上在值域上直接做就行了,然后求个区间 Max
回来看到时限开到 200ms,妈呀卡 log 呢?还好我已经胡出了单调队列线性做法
码码码,测大洋例挂了一个,发现没考虑 选择区间长度必须是偶数,那开两个 deque 分别维护奇偶就行,9:46 交了
看时间剩两个小时,感觉很稳,决定先不拍,看 T4 ,说不定就阿克了(伏笔
看完数据范围后感觉应该直接搜所有状态, ( n m ) 2 (nm)^2 (nm)2 做法是显然的,记人和箱子位置就好
然后会发现人一定在箱子四周,那么状态数可以压缩到 4 n m 4nm 4nm,唯一需要考虑的就是怎么快速判断人能否从箱子的一边到另一边?
隐隐感觉和那些图论算法有关,仔细想想,在不经过这个箱子的前提下两点互达,边双?
刚准备回来写,顺手造一组样例就把自己 hack 了,无奈
继续想,考虑每次删一个点,判两点联通,哎线段树分治!好像很对,维护可撤销并查集
可是很遗憾,我不会写线段树分治
继续想图论算法,修修补补发现其实是点双,箱子那儿一定是一条路,“两条不交路径” 就行了,这就是点双定义啊
写写写,11:10 写完了,一发过了大样例?
以为自己 AK 了,想想还是拍下 T4
结果一下子 WA 了,赶紧调调调,发现 2 个sb错误,最后 2 min 改过交了
然鹅:
100+15+100+100 = 315 , rk_ O ( n ! ) O(n!) O(n!)
没想到 T2 的弱智错误… 本场唯一没拍的题…
Upd : T4 少讨论了一种情况,没想到过了
题解
T3
补一下正解 ~
观察转移方程: f i = m i n ( f j + m a x ( h j + 1 . . h i ) ) , p o s i ≤ j ≤ i − 1 f_i=min(f_j+max(h_{j+1}..h_i)),pos_i\leq j\leq i-1 fi=min(fj+max(hj+1..hi)),posi≤j≤i−1 ,pos 表示合法区间左端点
考虑直接线段树维护区间 Max ,考虑阶段 i i i 右移对前面决策什么影响,发现会更改一段的 h m a x h_{max} hmax ,可以单调栈求出左边第一个比 h i h_i hi 大的,区间修改
由于每个位置的 f f f 值是固定的,线段树节点只需维护 f + h f+h f+h 与 f f f 最小值,就可以传标记了
T4
奇奇怪怪,前面口胡过了
#include<bits/stdc++.h>
using namespace std ;typedef long long LL ;
const int N = 1510 ;int n , m , Q ;
int stx , sty , bx , by ;
char ch[N][N] ;
struct nn
{int x , y , f ;
};
bool vis[N][N][4] ;
int dx[4] = {-1,1,0,0} ;
int dy[4] = {0,0,-1,1} ;
queue<nn> q ;
bool can[N][N] ;
inline bool F ( int x , int y )
{if( x<1||x>n||y<1||y>m ) return 0 ;return 1 ;
}
struct node
{int x , y ;
};
void Get()
{queue<node> q ;q.push({stx,sty}) ;while( !q.empty() ) {int x = q.front().x , y = q.front().y ; q.pop() ;for(int i = 0 ; i < 4 ; i ++ ) {int tx=x+dx[i] , ty=y+dy[i] ;if( F(tx,ty) && !can[tx][ty] && ch[tx][ty]!='#' && ch[tx][ty]!='B' ) {can[tx][ty] = 1 ;q.push({tx,ty}) ;}}}
}
// n^2 个点
struct edg
{int lst , to ;
}E[4*N*N] ;// 边不多
int head[N*N] , tot = 1 ;
inline void add( int x , int y )
{E[++tot] = (edg){ head[x] , y } ;head[x] = tot ;
}
inline int get( int x , int y )
{return (x-1)*(m)+y ;
}
int tim , dfn[N*N] , low[N*N] , cnt ;
stack<int> s ;
vector<int> bel[N*N] ; // 节点 x 属于哪些点双
void tarjan( int x )
{dfn[x] = low[x] = ++tim ;s.push(x) ;for(int i = head[x] ; i ; i = E[i].lst ) {int t = E[i].to ;if( !dfn[t] ) {tarjan( t ) ;low[x] = min( low[x] , low[t] ) ;if( dfn[x] <= low[t] ) {cnt ++ ;int y ;do {y = s.top() ;s.pop() ;bel[y].push_back( cnt ) ;}while( y != t ) ;bel[x].push_back( cnt ) ;}}else low[x] = min( low[x] , dfn[t] ) ;}
}
int bt[N*N] ;
inline bool ok( int x , int y , int ax , int ay ) // 是否在一个点双
{int A = get(x,y) , B = get(ax,ay) ;for(int t : bel[A] ) {bt[t] ++ ;}bool fg = 0 ;for(int t : bel[B] ) {if( bt[t] ) {fg = 1 ;break ;}}for(int t : bel[A] ) {bt[t] = 0 ;}return fg ;
}int main()
{scanf("%d%d%d" , &n , &m , &Q ) ;for(int i = 1 ; i <= n ; i ++ ) {scanf("\n%s" , ch[i]+1 ) ;for(int j = 1 ; j <= m ; j ++ ){if( ch[i][j] == 'A' ) stx = i , sty = j ;else if( ch[i][j] == 'B' ) bx = i , by = j ;}}for(int i = 1 ; i <= n ; i ++ ) {for(int j = 2 ; j <= m ; j ++ ) {if( ch[i][j-1] != '#' && ch[i][j] != '#' ) {add( get(i,j-1) , get(i,j) ) ;add( get(i,j) , get(i,j-1) ) ;}}if( i != 1 ) {for(int j = 1 ; j <= m ; j ++ ) {if( ch[i][j] != '#' && ch[i-1][j] != '#' ) {add( get(i,j) , get(i-1,j) ) ;add( get(i-1,j) , get(i,j) ) ;}}}}for(int i = 1 ; i <= n ; i ++ ) {for(int j = 1 ; j <= m ; j ++ ) {if( ch[i][j] != '#' ) {if( !dfn[get(i,j)] ) {while( !s.empty() ) s.pop() ;tarjan( get(i,j) ) ;}}}}Get() ;for(int i = 0 ; i < 4 ; i ++ ) {int ax = bx+dx[i] , ay = by+dy[i] ;if( F(ax,ay) && can[ax][ay] ) {q.push({bx,by,i}) ;vis[bx][by][i] = 1 ;}}while( !q.empty() ) {int x = q.front().x , y = q.front().y , f = q.front().f ; q.pop() ;//箱 int ax = x+dx[f] , ay = y+dy[f] ;//人 // pushint tx = x+dx[f^1] , ty = y+dy[f^1] ;if( F(tx,ty) && ch[tx][ty]!='#' ) {if( !vis[tx][ty][f] ) {vis[tx][ty][f] = 1 ;q.push({tx,ty,f}) ;}}// turnif( f <= 1 ) {tx = x+dx[2] , ty = y+dy[2] ;if( F(tx,ty) && ch[tx][ty]!='#' && ok(ax,ay,tx,ty) ) {if( !vis[x][y][2] ) {vis[x][y][2] = 1 ;q.push({x,y,2}) ;}}tx = x+dx[3] , ty = y+dy[3] ;if( F(tx,ty) && ch[tx][ty]!='#' && ok(ax,ay,tx,ty) ) {if( !vis[x][y][3] ) {vis[x][y][3] = 1 ;q.push({x,y,3}) ;}}}else {tx = x+dx[0] , ty = y+dy[0] ;if( F(tx,ty) && ch[tx][ty]!='#' && ok(ax,ay,tx,ty) ) {if( !vis[x][y][0] ) {vis[x][y][0] = 1 ;q.push({x,y,0}) ;}}tx = x+dx[1] , ty = y+dy[1] ;if( F(tx,ty) && ch[tx][ty]!='#' && ok(ax,ay,tx,ty) ) {if( !vis[x][y][1] ) {vis[x][y][1] = 1 ;q.push({x,y,1}) ;}}}}int r , c ;for(int i = 1 ; i <= Q ; i ++ ) {scanf("%d%d" , &r , &c ) ;if( vis[r][c][0] || vis[r][c][1] || vis[r][c][2] || vis[r][c][3] || (r==bx&&c==by) ) {printf("YES\n") ;} else printf("NO\n") ;}return 0 ;
}
一定要注意点双的定义: 点双内不存在割点,点双内的点对间有两条 点集 不交的路径