【NOIP2016提高A组8.12】奇袭

44 篇文章 0 订阅
6 篇文章 0 订阅

Description

由于各种原因,桐人现在被困在Under World(以下简称UW)中,而UW马上要迎来最终的压力测试——魔界入侵。
唯一一个神一般存在的Administrator被消灭了,靠原本的整合骑士的力量是远远不够的。所以爱丽丝动员了UW全体人民,与整合骑士一起抗击魔族。
在UW的驻地可以隐约看见魔族军队的大本营。整合骑士们打算在魔族入侵前发动一次奇袭,袭击魔族大本营!
为了降低风险,爱丽丝找到了你,一名优秀斥候,希望你能在奇袭前对魔族大本营进行侦查,并计算出袭击的难度。
经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个N×N的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。
在大本营中,每有一个k×k(1≤k≤N)的子网格图包含恰好k支军队,我们袭击的难度就会增加1点。
现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。

Input

第一行,一个正整数N,表示网格图的大小以及军队数量。
接下来N行,每行两个整数,Xi,Yi,表示第i支军队的坐标。
保证每一行和每一列都恰有一只军队(即每一个Xi和每一个Yi都是不一样的)。

Output

一行,一个整数表示袭击的难度。

Sample Input

5
1 1
3 2
2 4
5 5
4 3

Sample Output

10
【样例解释】
显然,分别以(2,2)和(4,4)为左上,右下顶点的一个子网格图中有3支军队,这为我们的难度贡献了1点。
类似的子网格图在原图中能找出10个。
##Data Constraint
对于30%的数据,N ≤ 100
对于60%的数据,N ≤ 5000
对于100%的数据,N ≤ 50000

Solution

乍一看以为是一道数论题
但是看一个条件
保证每一行和每一列都恰有一只军队(即每一个Xi和每一个Yi都是不一样的)
这说明我们可以把图压扁,变成一维(被歌者降维了)
意思是原来是 a [ x , y ] = 1 a[x,y]=1 a[x,y]=1变成 a [ x ] = y a[x]=y a[x]=y
之后变成数列上的问题。
怎样判断一个状态是否合法呢?

给定一个l和r,l和r代表的就是列,而a[l]和a[r]就是行,**如果 m a x ( l , r ) − m i n ( l , r ) = l − r max(l,r)-min(l,r)=l-r max(l,r)min(l,r)=lr(max(l,r)表示l~r之间的a的最大值,min是最小值)**那就ans++

直接暴力枚举就有60分了
100分怎么做?
有两种做法,线段树和分治,这里考虑分治。
每次通过特殊的方法找max和min来判断是否要ans++
对于当前的一段l~r,还有它的中点m,已中点分开,如果找到的两个左右边界在同侧,那就分治下去,只考虑左右边界跨越了中点
分为四种情况考虑
1、max和min同在m左侧
2、max和min同在m右侧
3、min在左,max在右
4、min在右,max在左
为了解决问题,定义 m a [ i ] ma[i] ma[i]表示从中点到i之间的最大值,显然从中间往两边分别单调递增
同样定义 m i [ i ] mi[i] mi[i]为最小值,显然从中点往两边分别单调递减
mi和ma直接在每次分治时暴力处理出
左右边界分别设为i和j
先从m向l枚举i

第一种情况

对于i算出对应的j
j = m a [ i ] − m i [ i ] + i j=ma[i]-mi[i]+i j=ma[i]mi[i]+i
然后判断j必须在右侧,且没有出界,并且 m a [ j ] < m i [ i ] ma[j]<mi[i] ma[j]<mi[i] m i [ j ] > m a [ i ] mi[j]>ma[i] mi[j]>ma[i],因为max与min都在左侧,那右边就不能有比左边最大大的和比左边最小小的。

第二种

与第一种对称就行了

第三种

设两个指针 z 1 z1 z1 z 2 z2 z2,作用等会说
与第一种一样,因为左小右大,所以必须保证 m a [ j ] > m a [ i ] ma[j]>ma[i] ma[j]>ma[i] m i [ j ] > m i [ i ] mi[j]>mi[i] mi[j]>mi[i]即左边不能比右边大,左边必须比右边小
那么就用z1维护在保证 m a [ z 1 ] > m a [ i ] ma[z1]>ma[i] ma[z1]>ma[i]时可取得最小值,因为 m a [ i ] ma[i] ma[i]是单调的,所以z1的取值也是随着i的减小而增大的
z2则维护保证 m i [ z 2 ] > m i [ i ] mi[z2]>mi[i] mi[z2]>mi[i]的最大值,同理,z2也是随着i的减小而增大的
那怎么计算数量呢,看张图
这里写图片描述
z1右边的所有点满足 m a [ z 1 ] > m a [ i ] ma[z1]>ma[i] ma[z1]>ma[i],z2左边的所有点满足 m i [ z 2 ] > m i [ i ] mi[z2]>mi[i] mi[z2]>mi[i]
那z1和z2之间的就是j的取值范围了
具体实现:
在z2往右走的时候在桶里+1,z1走的时候桶里-1最后就可以统计出数量了
桶的哪个位置改变呢?
要保证 m a ( j ) − m i ( i ) = j − i ma(j)-mi(i)=j-i ma(j)mi(i)=ji
移项得 m a ( j ) − j = m i ( i ) − i ma(j)-j=mi(i)-i ma(j)j=mi(i)i
而z1、z2就是为了找出j的范围,所以在桶里就是ma[z1]-z1或z2所在的位置相加减
a n s + = m i [ i ] − i ans+=mi[i]-i ans+=mi[i]i就行了
p.s: m i [ i ] − i mi[i]-i mi[i]i可能是负数,c++就加一个很打的数就行了

第四种

与第三种对称

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 51000
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
int a[N],n,ans=0,mi[N],ma[N],t[N*5];
void dg(int l,int r)
{
	if(l==r) return;
	int m=(l+r)/2;
	mi[m]=ma[m]=a[m];
	mi[m+1]=ma[m+1]=a[m+1];
	fd(i,m-1,l) mi[i]=min(a[i],mi[i+1]),ma[i]=max(a[i],ma[i+1]);
	fo(i,m+2,r) mi[i]=min(a[i],mi[i-1]),ma[i]=max(a[i],ma[i-1]);
	int z1=m+1,z2=m+1;
	fd(i,m,l) 
	{
		int j=ma[i]-mi[i]+i;
		if(j>m&&j<=r&&ma[j]<ma[i]&&mi[j]>mi[i]) ans++;
		while(z2<=r&&mi[z2]>=mi[i]) t[ma[z2]-z2+N]++,z2++;
		while(z1<=r&&ma[z1]<=ma[i]) t[ma[z1]-z1+N]--,z1++;
		ans+=max(t[mi[i]-i+N],0);
	}
	fo(i,l,r) t[ma[i]-i+N]=0;
	z1=m;z2=m;
	fo(i,m+1,r)
	{
		int j=i-ma[i]+mi[i];
		if(j<=m&&j>=l&&ma[j]<ma[i]&&mi[j]>mi[i]) ans++;
		while(z2>=l&&mi[z2]>=mi[i]) t[ma[z2]+z2+N]++,z2--;
		while(z1>=l&&ma[z1]<=ma[i]) t[ma[z1]+z1+N]--,z1--;
		ans+=max(t[mi[i]+i+N],0);
	}
	fo(i,l,r) t[ma[i]+i+N]=0;
	dg(l,m);dg(m+1,r);
}
int main()
{
	scanf("%d",&n);
	fo(i,1,n)
	{
		int x,y;scanf("%d%d",&x,&y);
		a[x]=y;
	}
	dg(1,n);
	printf("%d",ans+n);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值