复盘
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} n−r+lSn−Sr+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) Sn−nk≤(Sr−rk)−(Sl−lk),那么我们可以把问题转化:
将原序列中每个数 − 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 V−E+F=2
推论:
一张平面图的连通块数 C C C 可以这么计算: V − E + F = C + 1 V-E+F=C+1 V−E+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 V−E+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 ;
}