7.24 模拟赛总结 [dp 专场] + tarjan

复盘

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))posiji1 ,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] ;//人 
		// push
		int 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}) ;
			}
		}
		// turn
		if( 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 ; 
}

一定要注意点双的定义: 点双内不存在割点,点双内的点对间有两条 点集 不交的路径

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值