[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+$:
考虑卡常,使用register,fread快读。
期望得分: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++
[Codeforce526F]:Pudding Monsters(分治)的更多相关文章
- [Codeforces526F]Pudding Monsters 分治
F. Pudding Monsters time limit per test 2 seconds memory limit per test 256 megabytes In this proble ...
- 【CF526F】Pudding Monsters cdq分治
[CF526F]Pudding Monsters 题意:给你一个排列$p_i$,问你有对少个区间的值域段是连续的. $n\le 3\times 10^5$ 题解:bzoj3745 Norma 的弱化版 ...
- Codeforces 526F Pudding Monsters - CDQ分治 - 桶排序
In this problem you will meet the simplified model of game Pudding Monsters. An important process in ...
- CodeForces526F:Pudding Monsters (分治)
In this problem you will meet the simplified model of game Pudding Monsters. An important process in ...
- CF526F Pudding Monsters
CF526F Pudding Monsters 题目大意:给出一个\(n* n\)的棋盘,其中有\(n\)个格子包含棋子. 每行每列恰有一个棋子. 求\(k*k\)的恰好包含\(k\)枚棋子的子矩形个 ...
- 「CF526F」 Pudding Monsters
CF526F Pudding Monsters 传送门 模型转换:对于一个 \(n\times n\) 的棋盘,若每行每列仅有一个棋子,令 \(a_x=y\),则 \(a\) 为一个排列. 转换成排列 ...
- Pudding Monsters CodeForces - 526F (分治, 双指针)
大意: n*n棋盘, n个点有怪兽, 求有多少边长为k的正方形内恰好有k只怪兽, 输出k=1,...,n时的答案和. 等价于给定n排列, 对于任意一个长为$k$的区间, 若最大值最小值的差恰好为k, ...
- 奇袭 CodeForces 526F Pudding Monsters 题解
考场上没有认真审题,没有看到该题目的特殊之处: 保证每一行和每一列都恰有一只军队,即每一个Xi和每一个Yi都是不一样 的. 于是无论如何也想不到复杂度小于$O(n^3)$的算法, 只好打一个二维前缀和 ...
- 【CF526F】Pudding Monsters
题意: 给你一个排列pi,问你有对少个区间的值域段是连续的. n≤3e5 题解: bzoj3745
随机推荐
- mapper中通过resultMap自定义查询结果映射
mybatis中使用resultType做自动映射时,要注意字段名和pojo的属性名必须一致,若不一致,则需要给字段起别名,保证别名与属性名一致. 使用resultMap做自定义结果映射,字段名可以不 ...
- HDU-4219-Randomization?
题目描述 给定一棵\(n\)个节点的树,每条边的权值为\([0,L]\)之间的随机整数,求这棵树两点之间最长距离不超过\(S\)的概率. Input 第一行三个整数\(n,L,S\) 接下来n-1行, ...
- Thread 线程 1
Thread 常用方法: String getName() 返回该线程的名称. void setName(String name) 改变线程名称,使之与参数 name 相同. int getPrior ...
- RabbitMQ入门教程(六):路由选择Routing
原文:RabbitMQ入门教程(六):路由选择Routing 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog. ...
- vue、iview动态菜单(可折叠)
vue项目与iview3实现可折叠动态菜单. 菜单实现一下效果: 动态获取项目路由生成动态三级菜单导航 可折叠展开 根据路由name默认打开子目录,选中当前项 自动过滤需要隐藏的路由(例:登陆) 在手 ...
- 从POST与GET、REQUEST响应的php和asp写法对比谈数据过滤
<!DOCTYPE html><!--To change this license header, choose License Headers in Project Propert ...
- Solaris下truss的使用
Solaris下truss的使用 原文转载:http://blog.csdn.net/sunlin5000/article/details/6560736 在Solaris下面,如果需要跟踪系统的调用 ...
- 4、MySQL 申明变量给查询数据编号
摘自: https://www.cnblogs.com/qixuejia/archive/2010/12/21/1913203.html https://blog.csdn.net/arbben/ar ...
- 5月Linux市场Steam 份额在增长
随着新的一个月的开始,Valve公布了上个月的软件/硬件调查数据.在2019年5月,Steam Linux的使用率按百分比略微上升. 上个月,运行Linux的Steam用户比例(根据有争议的Steam ...
- 关于 Google 公司的一些趣闻
简评: 很少有科技公司能像 Google 一样象征着这个数字时代,你知道 Google,但不一定知道以下这些有趣数据.这些来自 VizionOnline 的数据概述了不为人知的 Google 趣闻,分 ...