[Codeforce526F]:Pudding Monsters(分治)

题目传送门


题目描述

由于各种原因,桐人现在被困在Under World(以下简称UW)中,而UW马上要迎来最终的压力测试——魔界入侵。
唯一一个神一般存在的Administrator被消灭了,靠原本的整合骑士的力量 是远远不够的。所以爱丽丝动员了UW全体人民,与整合骑士一起抗击魔族。
UW的驻地可以隐约看见魔族军队的大本营。整合骑士们打算在魔族入侵前发动一次奇袭,袭击魔族大本营!
为了降低风险,爱丽丝找到了你,一名优秀斥候,希望你能在奇袭前对魔族 大本营进行侦查,并计算出袭击的难度。

经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个$N \times N$的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。
在大本营中,每有一个$k \times k$(1≤k≤N)的子网格图包含恰好k支军队,我们袭 击的难度就会增加1点。
现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。


输入格式:

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


输出格式:

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


样例:

样例输入:

5
1 1
3 2
2 4
5 5
4 3

样例输出:

10


数据范围与提示:

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


注意题中一句话,得语文者得天下:

保证每一行和每一列都恰有一只军队,即每一个$X_i$和每一个$Y_i$都是不一样的。

(然而当时的我并没有看见……)


题解:

$O( N^5 )$:

考虑暴力枚举,$N^2$枚举左下角位置,$O(N)$枚举正方形边长,$N^2$暴力求和,与边长比较,统计答案。

期望得分:27分。

$O( N^4 )$:

考虑上面红色字的含义,即可把这道题转化为:给定N个书的一个排列,问这个序列中有多少个子区间的数恰好是连续的。

那么,我们就可以将$O( N^5 )$算法压一维,同样是$N^2$枚举左右端点位置,$O(N)$枚举正方形边长,但是暴力求和的时候只需要$O(N)$把这一段区间扫一遍即可。

期望得分:27分。

$O(N^3)$:

又不用考虑上面红色字的含义了,维护一下$O( N^5 )$算法的前缀和,就能将$N^2$暴力求和压掉,就做到了$O(N^3)$

期望得分:27分。

$O(N^2)$:

又得考虑上面红色字的含义了认真想一想,题目就转化成了:有多少种情况使得相邻的k个数中最大值和最小值的差为k-1

那么,我们可以维护区间的最大值和最小值,然后进行处理,还是$N^2$枚举左右端点位置,但是直接用这段区间的最大值减去最小值,将它与k做比较,相同则ans++

至于最大值最小值,可以在枚举左端点的时候清空,然后在枚举右端点的时候暴力更新;也可以使用ST算法,$N \log N$预处理,$O(1)$查询,其实没必要,主要是说一下这种思维。

期望得分:

  暴力更新:64分。

  ST算法:55分。

$O(N^2)Pro$:

考虑对$O(N^2)$算法进行优化,如果要满足当前长度为k的区间里所有的数都是连续的k个数,那么如果当前枚举右端点的时候扫到的点不是当前区间的最小值,而比它大1的数却在左端点左边,那么以后的一定都不能满足了,直接break掉,枚举下一个左端点就好了,这个点不是当前区间的最大值时同理。

期望得分:91分。

$O(N^2)Pro+$

考虑卡常,使用registerfread快读。

期望得分:91分。

$O(N \log N)||O(N \log^2 N)$:

两种解法:分治和线段树。

在这里主要讲一下分治:

对于当前分治的区间[l,r],设其中点为mid

当前区间的答案即为:

$ans[l,r]=ans[l,mid]+ans[mid+1,r]+$跨过中点的合法方案数。

计算跨过中点的合法方案数时,分一下四种情况:

  1.最大值和最小值都在左侧。

  2.最大值和最小值都在右侧。

  3.最小值在左侧,最大值在右侧。

  4.最小值在右侧,最大值在左侧。

然后我们发现,情况1和情况3对称,情况2情况4对称,所以下面我们只考虑情况1情况3

对于情况1,我们枚举左边界,然后可以计算出右边界的位置,再判断是否合法,统计答案,时间复杂度$O(N)$

对与情况3,如果一个区间合法的话就一定满足:

  $\max (a[mid+1]...a[r])- \min (a[l]...a[mid])=r-l$

移项得:

  $\max (a[mid+1]...a[r]-r= \min (a[l]...a[mid])-l$

然后利用单调栈和桶来完成这些操作,代码实现较为复杂,时间复杂度$O(N)$

考虑上二分区间的时候的$\log N$,总的时间复杂度即为$O(N \log N)$

因为情况1和情况3对称,情况2情况4对称,为了降低码长我们可以使用algorithm库里的reverse函数,在$\log N$的时间内翻转区间,时间复杂度为$O(N \log^2 N)$

线段树的思路也是利用单调栈,用线段书进行区间修改和查询,统计答案,时间复杂度$O(N \log N)$

期望得分:100分。


代码时刻:

$O( N^5 )$:

#include<bits/stdc++.h>
using namespace std;
int n,a[5001][5001];
int ans;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x][y]=1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)//枚举端点
			for(int len=1;len<=min(n-i+1,n-j+1);len++)//枚举长度
			{
				int sum=0;
				for(int k=0;k<len;k++)
					for(int l=0;l<len;l++)//暴力统计答案
						if(a[i+k][j+l])sum++;
				if(sum==len)ans++;
			}
	printf("%d",ans);
	return 0;
}

$O( N^4 )$:

#include<bits/stdc++.h>
using namespace std;
int n,a[50001],ans;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x]=y;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)//枚举端点
			for(int len=1;len<=min(n-i+1,n-j+1);len++)//枚举长度
			{
				int sum=0;
				for(int pos=0;pos<len;pos++)//暴力统计答案
				{
					if(a[i+pos]<=j+len-1&&a[i+pos]>=j)sum++;
					if(sum>len)break;
				}
				if(sum==len)ans++;
			}
	printf("%d",ans);
	return 0;
}

$O(N^3)$:

#include<bits/stdc++.h>
using namespace std;
int ans;
int Map[5001][5001];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Map[x][y]=1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			Map[i][j]=Map[i][j]+Map[i-1][j]+Map[i][j-1]-Map[i-1][j-1];//计算前缀和
	ans=n;
	for(int k=2;k<=n;k++)//枚举长度
		for(int i=0;i<=n-k;i++)
			for(int j=0;j<=n-k;j++)//枚举左右端点
				if(Map[i+k][j+k]-Map[i+k][j]-Map[i][j+k]+Map[i][j]==k)ans++;//统计答案
	printf("%d",ans);
	return 0;
}

$O(N^2)$:

暴力求最大值和最小值:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[50001];
int ans;
int maxn,minn;
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x]=y;
	}
	for(int i=1;i<=n;i++)//枚举左端点
	{
		maxn=0;
		minn=1<<30;//初始值
		for(int j=i;j<=n;j++)//枚举右端点
		{
			maxn=max(maxn,a[j]);
			minn=min(minn,a[j]);//更新最大值和最小值
			if(maxn-minn==j-i)ans++;//统计答案
		}
	}
	printf("%d",ans);
	return 0;
}

利用ST算法最大值和最小值:

#include<bits/stdc++.h>
using namespace std;
int n;
int maxn[50001][20],minn[50001][20];
int ans;
void st(int x)
{
    for(int i=1;i<=16;i++)
        for(int j=1;j+(1<<i)<=x+1;j++)
        {
            maxn[j][i]=max(maxn[j][i-1],maxn[j+(1<<(i-1))][i-1]);
            minn[j][i]=min(minn[j][i-1],minn[j+(1<<(i-1))][i-1]);
        }
}
pair<int,int> query(int l,int r)
{
    int k=log2(r-l+1);
    return make_pair(max(maxn[l][k],maxn[r-(1<<k)+1][k]),min(minn[l][k],minn[r-(1<<k)+1][k]));
}
int main()
{
	memset(minn,0x3f,sizeof(minn));
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		maxn[x][0]=minn[x][0]=y;//直接存入ST表
	}
	st(n);//ST表初始化
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
		{
			pair<int,int> flag=query(i,j);//取出当前区间的最大值和最小值
			if(flag.first-flag.second==j-i)ans++;//统计答案
		}
	printf("%d",ans);
	return 0;
}

$O(N^2)Pro$:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[50001],flag[50001];//flag存储对应位置
int ans;
int maxn,minn;
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x]=y;
		flag[y]=x;
	}
	for(int i=1;i<=n;i++)
	{
		maxn=0;
		minn=1<<30;
		for(int j=i;j<=n;j++)
		{
			maxn=max(maxn,a[j]);
			minn=min(minn,a[j]);
			if((a[j]!=minn&&flag[a[j]-1]<i)||(a[j]!=maxn&&flag[a[j]+1]<i))break;//剪枝
			if(maxn-minn==j-i)ans++;
		}
	}
	printf("%d",ans);
	return 0;
}

$O(N^2)Pro+$:

#include<bits/stdc++.h>
using namespace std;
const int L(1<<20|1);
char buffer[L],*S,*T;
#define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++)//调试时记得注释
int read()
{
    register int a=0,b=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+(ch-'0');ch=getchar();}
    return a*b;
}
int n;
int a[50001],flag[50001];
int ans;
int maxn,minn;
int main()
{
	register int n=read();
	for(register int i=1;i<=n;i++)
	{
		register int x=read(),y=read();
		a[x]=y;
		flag[y]=x;
	}
	for(register int i=1;i<=n;i++)
	{
		maxn=0;
		minn=1<<30;
		for(register int j=i;j<=n;j++)
		{
			maxn=max(maxn,a[j]);
			minn=min(minn,a[j]);
			if((a[j]!=minn&&flag[a[j]-1]<i)||(a[j]!=maxn&&flag[a[j]+1]<i))break;
			if(maxn-minn==j-i)ans++;
		}
	}
	printf("%d",ans);
	return 0;
}

$O(N \log^2 N)$:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[50001];
int lmin[50001],lmax[50001],rmin[50001],rmax[50001],barrel[100001];//分别存储左区间最小值,左区间最大值,右区间最小值,右区间最大值,桶。
int ans;
int wzc(int l,int r,int mid)
{
	lmin[mid  ]=lmax[mid  ]=a[mid  ];
	rmin[mid+1]=rmax[mid+1]=a[mid+1];
    for(int i=mid-1;i>=l;i--)
    {
        lmin[i]=min(lmin[i+1],a[i]);
        lmax[i]=max(lmax[i+1],a[i]);
    }
    for(int i=mid+2;i<=r;i++)
    {
        rmin[i]=min(rmin[i-1],a[i]);
        rmax[i]=max(rmax[i-1],a[i]);
    }
    int flag=0,flag1=mid+1,flag2=mid+1;
    for(int i=l;i<=mid;i++)
    {
    	int miao=lmax[i]-lmin[i]+i;
    	if(miao<=r&&mid<miao&&rmax[miao]<lmax[i]&&lmin[i]<rmin[miao])flag++;//最大值和最小值在同侧
    }
    while(flag1<=r&&lmin[l]<rmin[flag1])
    	barrel[rmax[flag1]-flag1+50000]++,flag1++;//最小值在左侧
    while(flag2<=r&&rmax[flag2]<lmax[l])
    	barrel[rmax[flag2]-flag2+50000]--,flag2++;//最大值在右侧
    for(int i=l;i<=mid;i++)
    {
    	while(flag1>mid+1&&rmin[flag1-1]<lmin[i])
    		flag1--,barrel[rmax[flag1]-flag1+50000]--;
    	while(flag2>mid+1&&lmax[i]<rmax[flag2-1])
    		flag2--,barrel[rmax[flag2]-flag2+50000]++;
    	flag+=barrel[lmin[i]-i+50000]>0?barrel[lmin[i]-i+50000]:0;
    }
    for(int i=mid+1;i<=r;i++)barrel[rmax[i]-i+50000]=0;
    return flag;
}
void dfs(int l,int r)//二分区间
{
	if(l==r)return;
	int mid=(l+r)>>1;
	dfs(l,mid);
	dfs(mid+1,r);
	ans+=wzc(l,r,mid);
	reverse(a+l,a+r+1);//翻转
	ans+=wzc(l,r,mid-((r-l+1)&1));//注意mid位置在反转之后会发生改变
	reverse(a+l,a+r+1);//记得翻回来
	return;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x]=y;
	}
	dfs(1,n);
	cout<<ans+n<<endl;//最后记得加n
	return 0;
}

$O(N \log N)$:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[50001];
int lmin[50001],lmax[50001],rmin[50001],rmax[50001],barrel[200001];//桶要稍微开打点
int ans;
int wzc(int l,int r,int mid)
{
	lmin[mid  ]=lmax[mid  ]=a[mid  ];
	rmin[mid+1]=rmax[mid+1]=a[mid+1];
    for(int i=mid-1;i>=l;i--)
    {
        lmin[i]=min(lmin[i+1],a[i]);
        lmax[i]=max(lmax[i+1],a[i]);
    }
    for(int i=mid+2;i<=r;i++)
    {
        rmin[i]=min(rmin[i-1],a[i]);
        rmax[i]=max(rmax[i-1],a[i]);
    }
    int flag=0,flag1=mid+1,flag2=mid+1;
    for(int i=l;i<=mid;i++)
    {
    	int miao=lmax[i]-lmin[i]+i;
    	if(miao<=r&&mid<miao&&rmax[miao]<lmax[i]&&lmin[i]<rmin[miao])flag++;
    }
    for(int i=mid+1;i<=r;i++)
    {
    	int miao=rmin[i]-rmax[i]+i;
    	if(miao>=l&&mid>=miao&&rmax[i]>lmax[miao]&&lmin[miao]>rmin[i])flag++;
    }
    while(flag1<=r&&lmin[l]<rmin[flag1])
    	barrel[rmax[flag1]-flag1+50000]++,flag1++;
    while(flag2<=r&&rmax[flag2]<lmax[l])
    	barrel[rmax[flag2]-flag2+50000]--,flag2++;
    for(int i=l;i<=mid;i++)
    {
    	while(flag1>mid+1&&rmin[flag1-1]<lmin[i])
    		flag1--,barrel[rmax[flag1]-flag1+50000]--;
    	while(flag2>mid+1&&lmax[i]<rmax[flag2-1])
    		flag2--,barrel[rmax[flag2]-flag2+50000]++;
    	flag+=barrel[lmin[i]-i+50000]>0?barrel[lmin[i]-i+50000]:0;
    }
    for(int i=mid+1;i<=r;i++)barrel[rmax[i]-i+50000]=0;
    flag1=flag2=mid;
    while(flag1>=l&&lmin[flag1]>rmin[r])
    	barrel[lmax[flag1]+flag1+50000]++,flag1--;//最小值在右侧
    while(flag2>=l&&rmax[r]>lmax[flag2])
    	barrel[lmax[flag2]+flag2+50000]--,flag2--;//最大值在左侧
    for(int i=r;i>mid;i--)
    {
    	while(flag1<mid&&rmin[i]>lmin[flag1+1])
    		flag1++,barrel[lmax[flag1]+flag1+50000]--;
    	while(flag2<mid&&lmax[flag2+1]>rmax[i])
    		flag2++,barrel[lmax[flag2]+flag2+50000]++;
    	flag+=barrel[rmin[i]+i+50000]>0?barrel[rmin[i]+i+50000]:0;
    }
    for(int i=l;i<=mid;i++)barrel[lmax[i]+i+50000]=0;
    return flag;
}
void dfs(int l,int r)
{
	if(l==r)return;
	int mid=(l+r)>>1;
	dfs(l,mid);
	dfs(mid+1,r);
	ans+=wzc(l,r,mid);
	return;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x]=y;
	}
	dfs(1,n);
	printf("%d",ans+n);
	return 0;
} 

rp++

posted @ 2019-07-17 10:10  HEOI-动动  阅读(261)  评论(0编辑  收藏  举报