zoukankan      html  css  js  c++  java
  • [Codeforce526F]:Pudding Monsters(分治)

    题目传送门


    题目描述

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

    经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个$N imes N$的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。
    在大本营中,每有一个$k imes 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++

  • 相关阅读:
    \r,\n,\r\n的区别
    \r,\n,\r\n的区别
    C# TextBox 换行 滚动到最后一行
    C# TextBox 换行 滚动到最后一行
    C# Textbox 始终保持最后最后一行
    C# Textbox 始终保持最后最后一行
    踩坑之mongodb配置文件修改
    踩坑之mongodb配置文件修改
    开启mongodb 的web
    开启mongodb 的web
  • 原文地址:https://www.cnblogs.com/wzc521/p/11199205.html
Copyright © 2011-2022 走看看