7.29 模拟赛总结 平面图欧拉定理

复盘

7:40 开题

开题失败,由于前一天有 cf,模拟赛移到下午了

13:45 开题

看 T1,题意很抽象,理清后发现:这直接 dj 不就行了?不会错吧不会错吧,看着 n = 1000 n=1000 n=1000 的数据范围还是不确定,往后看看先

看 T2,以为会了,应该就是个斜率什么的,维护凸包+二分

看 T3 ,隐隐感觉有点眼熟?但一细想又不会左

T4,首先题面比较抽象,但感觉是和以前模拟赛考过的可能有点像,最后看吧

决定先写比较确定的 T2,推了推发现斜率不太好搞,不知道那个是不是单调的,没关系我回分数规划

只用了 O ( 1 ) s O(1)s O(1)s 就写完了,直接过掉了大样例,14:30

决定对拍,发现很快就出错了,调完小错误后仍没过拍,看了看输出发现是精度问题,而且用计算器验证发现是暴力里面由于做除法出错了

最后尝试改 long double,不拍了,先放,已经 15:00 了

回来写 T1,反复审题后笃定 dj 没错,想了想有些细节处理,写写写,细节不少,16:00 才过大样例

时间还多,优势在我,看 T3

发现还是不会,在走廊上胡出了一个 树套数做法,想了想数据范围 5 e 4 5e4 5e4 还觉得很对,但是竟然需要给一个 等腰三角形区域修改操作,做不了做不了,但在这里还是得到了同种数字找区间交、并的想法

回来手玩样例,突然发现这不就是在教我怎么做吗?想了想这个做法,虽然不会证,但构造几组数据都 hack 不掉

17:10 了,决定开写,剩 1h

先用暴力跑出了大样例的答案,更加确信做法是对的,17:45 写完,把 s e t set set 做法和暴力拍了拍

最后二十分钟,看了看 T4,推完样例感觉有思路,编了个 O ( n 6 ) O(n^6) O(n6) 做法,最后细节挺多没写完,遗憾离场

最后 100+95+100+0 = 295 , rk O ( n ) O(\sqrt n) O(n ),还行

感觉 noip 模拟赛还是得抢时间的,难度偏简单的场得快速过简单题,最后去磕难题的高分

ps:T2 挂 5 分不是因为精度,而是有个 ans=0 ,初值赋小了!!

题解

T1

在这里插入图片描述
直接跑 dj,需要快速处理 从某一时刻 t t t 开始,最早的 x x x y y y 同色时间

然后简单讨论一下发现 由于是一整段一整段的,可以直接 O ( 1 ) O(1) O(1) 得到

T2

在这里插入图片描述
应该算是道套路题

化式子,等价于求 S n − S r + S l n − r + l \large\frac{S_n-S_r+S_l}{n-r+l} nr+lSnSr+Sl 最大,由于是分数,我们设 原式 ≤ k \leq k k,二分 k k k,判断是否可以成立

通分得到: S n − n k ≤ ( S r − r k ) − ( S l − l k ) S_n-nk\leq (S_r-rk)-(S_l-lk) Snnk(Srrk)(Sllk),那么我们可以把问题转化:

将原序列中每个数 − k -k k 后,能否找到一段区间使得 区间和 大于等于某一定值?

维护前缀 Min 即可

#include<bits/stdc++.h>
using namespace std ;

typedef long long LL ;
const int N = 5e5+100 ;

int n , a[N] ;
int Max ;
namespace Part2
{
	const long double eps = 1e-6 ;
	bool check( long double x )
	{
		long double Min = 1e10 , ans = -1e11 , sum = 0 ;
		for(int i = 1 ; i <= n ; i ++ ) {
			sum += a[i]-x ;
			if( i > 1 && i < n ) {
				ans = max( ans , sum-Min ) ; 
			}
			Min = min( Min , sum ) ;
		}
		if( ans >= sum ) return 1 ;
		return 0 ;
	}
	void solve()
	{
		long double l = 0 , r = Max , mid ;
		while( l+eps < r ) {
			mid = (l+r)/2 ;
			if( check(mid) ) {
				r = mid ;
			}
			else {
				l = mid ;
			}
		}
		if( check(l) ) printf("%.3Lf" , l+eps ) ;
		else printf("%.3Lf" , r+eps ) ;
	}
}

int main()
{
	scanf("%d" , &n ) ;
	for(int i = 1 ; i <= n ; i ++ ) {
		scanf("%d" , &a[i] ) ;
		Max = max( Max , a[i] ) ;
	}
	Part2::solve() ;
	return 0 ; 
}

一个小技巧,最后答案 +eps,抹平微小的误差

T3

在这里插入图片描述
首先我们可以发现:对于同种数字来说,区间交是比较 重要的

如果某个数出现过但区间交为空,显然无解

反之第 i i i 个数的交区间 [ L , R ] [L,R] [L,R] 意味着 “ i i i 一定会在这个区间中出现恰好一次”

不妨只考虑比 i i i 大的数对 i i i 的限制,对于一次询问 ( l , r , x ) (l,r,x) (l,r,x) ,可以看作是在区间 [ l , r ] [l,r] [l,r] 中加上 ≥ x \geq x x 的限制

那么求出所有比 i i i 大的数的区间 ,若这个 并 包含了 i i i 的交区间 [ L , R ] [L,R] [L,R],就无解了

反之一定能在并区间外的某个位置把 i i i 给放进去,这样符合所有现有条件

同时在这之后要用 i i i 把区间并更新,那么 i i i 的放置对之后操作也是没有影响的

具体实现,由于要在值域上做,不得不离线了,二分最早矛盾的询问,把这些询问排序后从大到小处理,用 s e t set set 或者 并查集 维护连续段(并区间)

( 然鹅我不会并查集维护连续段

#include<bits/stdc++.h>
using namespace std ;

typedef long long LL ;
const int N = 1e6+100 , M = 3e4+10 ;

int n , Q ;
struct nn
{
	int l , r , x ;
}a[M] , b[M] ;
bool cmp( nn x , nn y )
{
	return x.x > y.x ;
}
struct node
{
	int l , r ;
	friend bool operator < ( node x , node y ) {
		return x.r < y.r ;
	}
};
set<node> s ;
bool query( int l , int r ) // 查询 [l,r] 是否被包含 
{
	if( s.empty() ) return 0 ;
	auto it = s.lower_bound({0,r}) ;
	if( it != s.end() && it->l <= l ) return 1 ;
	return 0 ;
}
void Insert( int l , int r )
{
	if( s.empty() ) {
		s.insert({l,r}) ;
		return ;
	} 
	int L = l , R = r ;
	auto it = s.lower_bound({0,l-1}) ;
	auto iit = it ;
	for( ; iit != s.end() ; iit ++ ) {
		if( iit->l <= r+1 ) {
			L = min( L , iit->l ) ;
			R = max( R , iit->r ) ;
		}
		else break ;
	}
	s.erase( it , iit ) ;
	s.insert({L,R}) ;
}
bool check( int x ) // 只考虑前 x 次询问 
{
	for(int i = 1 ; i <= x ; i ++ ) b[i] = a[i] ;
	sort( b+1 , b+x+1 , cmp ) ;
	s.clear() ;
	int L = b[1].l , R = b[1].r , lst = 1 ;
	for(int i = 2 ; i <= x ; i ++ ) {
		if( b[i].x == b[i-1].x ) {
			L = max( L , b[i].l ) ;
			R = min( R , b[i].r ) ;
		}
		else { // 处理上一次 
			if( L>R ) return 1 ;// 交集为空,gg 
			if( query(L,R) ) return 1 ; // 被完全覆盖,gg
			for(int j = lst ; j < i ; j ++ ) {
				Insert(b[j].l,b[j].r) ;
			}
			lst = i , L = b[i].l , R = b[i].r ;
		}
	}
	if( L>R ) return 1 ;
	if( query(L,R) ) return 1 ;
	return 0 ;
}

int main()
{
	scanf("%d%d" , &n , &Q ) ;
	for(int i = 1 ; i <= Q ; i ++ ) {
		scanf("%d%d%d" , &a[i].l , &a[i].r , &a[i].x ) ;
	}
	int l = 1 , r = Q , mid ;
	while( l+1 < r ) {
		mid = (l+r)>>1 ;
		if( check(mid) ) {
			r = mid ;
		}
		else {
			l = mid ;
		}
	}
	if( check(l) ) printf("%d" , l ) ;
	else if( check(r) ) printf("%d" , r ) ;
	else printf("0") ; 
	return 0 ; 
}

T4


(又臭又长的题面

通过手玩样例,我们可以得到猜到一个做法:

从小到大加入新点,每次看新加入的点能不能连出来一个新的 “山谷”,如果能加到答案里

这样做很对

接下来唯一的问题是怎么判断 连通形成的新山谷没有“洞”

一个 trick:平面图的欧拉定理

在这里插入图片描述
边不交叉!

V V V 为点数, E E E 为边数, F F F 为面数(指的是被边分割成的区域个数),欧拉定理为:

这张平面图连通的等价条件是 V − E + F = 2 V-E+F=2 VE+F=2

推论:

一张平面图的连通块数 C C C 可以这么计算: V − E + F = C + 1 V-E+F=C+1 VE+F=C+1

经常用在网格图中,把 染色点 看作点,相邻点对(四联通) 看作边

此时 F F F 为 4相邻块数 + 空腔块数 + 1

在这里插入图片描述
来说说本题怎么用

考虑对于一个连通块,怎么 c h e c k check check

洞可以看作是一个环,且不是由四相邻的点构成

那么合并过程中维护出 连通块的 点数 V V V,相邻对数 E E E,四相邻对数 F F F

然后只需 c h e c k check check 是否有: V − E + F + 1 = 2 V-E+F+1=2 VE+F+1=2,意思就是没有其它的环,即空腔

代码很好写

#include<bits/stdc++.h>
using namespace std ;

typedef long long LL ;
const int N = 760 ;
// 从低到高加点,只需判是否有洞 
// 利用平面图欧拉定理判环 
 
int n , h[N][N] ;
struct nn
{
	int x , y , h ;
}a[N*N] ;
int len ;
bool cmp ( nn x , nn y )
{
	if( x.h^y.h ) return x.h<y.h ;
	if( x.x^y.x ) return x.x<y.x ;
	return x.y<y.y ;
}
int bin[N*N] , V[N*N] , E[N*N] , F[N*N] ; 
inline int get( int x , int y )
{
	return (x-1)*n+y ;
}
int Find( int x )
{
	if( x == bin[x] ) return x ;
	return bin[x] = Find(bin[x]) ;
}
int dx[4] = {0,0,1,-1} ;
int dy[4] = {1,-1,0,0} ;
bool vis[N][N] ;
inline int T ( int x , int y )
{
	return (vis[x][y]&&vis[x-1][y]&&vis[x-1][y-1]&&vis[x][y-1])+
		   (vis[x][y]&&vis[x-1][y]&&vis[x][y+1]&&vis[x-1][y+1])+
		   (vis[x][y]&&vis[x+1][y]&&vis[x+1][y-1]&&vis[x][y-1])+
		   (vis[x][y]&&vis[x+1][y]&&vis[x+1][y+1]&&vis[x][y+1]) ; 
}

int main()
{
	scanf("%d" , &n ) ;
	for(int i = 1 ; i <= n ; i ++ ) {
		for(int j = 1 ; j <= n ; j ++ ) {
			scanf("%d" , &h[i][j] ) ;
			a[++len] = (nn){i,j,h[i][j]} ;
			bin[get(i,j)] = get(i,j) ;
		}
	}
	sort( a+1 , a+len+1 , cmp ) ;
	LL ans = 0 ;
	for(int i = 1 ; i <= len ; i ++ ) {
		int x = a[i].x , y = a[i].y ;
		int ne = 0 , nv = 1 ;
		for(int j = 0 ; j < 4 ; j ++ ) {
			int tx=x+dx[j] , ty=y+dy[j] ;
			if( tx<1||tx>n||ty<1||ty>n ) continue ;
			if( !vis[tx][ty] ) continue ;
			int fx = Find(get(tx,ty)) ;
			if( fx != get(x,y) ) {
				V[get(x,y)] += V[fx] , E[get(x,y)] += E[fx] , F[get(x,y)] += F[fx] ;
				bin[fx] = get(x,y) ;
			}
			ne ++ ;
		}
		vis[x][y] = 1 ;
		E[get(x,y)] += ne ; V[get(x,y)] += nv ; F[get(x,y)] += T(x,y) ;
		if( V[get(x,y)]-E[get(x,y)]+F[get(x,y)]+1 == 2 ) { // 没有除四联通以外的环 
			ans += V[get(x,y)] ;
		}
	}
	printf("%lld" , ans ) ;
	return 0 ; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值