我要嗝了
我经过一系列努力,寻找了一系列,各种复杂度的方法。
1>纯暴力
复杂度:$Theta(N^5)$
不多解释,上代码:
空间复杂度无法承受,如果考试偏要写这个不妨动态开数组:
例:
#include<iosteam> using namespace std; int n; int *Array;//开一个指针 int main(){ cin>>n; Array=new int[n];//像这样 }
全码
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #define N 5000 using namespace std; int le,ans; bool ma[N][N]; int main(){ //freopen("raid.in","r",stdin); //freopen("raid.out","w",stdout); int a,b; scanf("%d",&le); for (int i=1;i<=le;i++) scanf("%d%d",&a,&b),ma[a][b]=1; for (int k=0;k<le;k++){ for (int i=1;i<=le;i++){ for (int j=1;j<=le;j++){ int cnt=0; for (int x=1;x<=le;x++){ for (int y=1;y<=le;y++){ if(ma[x][y])cnt++; } } if(cnt==k+1)ans++; } } } cout<<ans<<endl; return 0; }
2>一般人想的到的暴力
复杂度:$Theta(N^4)$
枚举长度$Theta(N)$,开头$Theta(N^2)$,再判断$Theta(N)$。
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #define N 50001 using namespace std; int le,x[N],y[N],ans; int main(){ //freopen("raid.in","r",stdin); //freopen("raid.out","w",stdout); int a,b; scanf("%d",&le); for (int i=1;i<=le;i++) scanf("%d%d",&x[i],&y[i]); for (int k=0;k<le;k++){ for (int i=1;i<=le;i++){ for (int j=1;j<=le;j++){ int num=0; for (int t=1;t<=le;t++){ if(i<=x[t]&&j<=y[t]&&x[t]<=i+k&&y[t]<=j+k){ num++; } } if(num==k+1)ans++; } } } cout<<ans<<endl; return 0; }
3>一棵二维线段树(我疯了)
复杂度:$Theta(N^3 log_4 N+Nlog_4N)$
常数巨大,T到飞起……9分好成绩(我猜有的点输入时就直接T飞)
插入一个点后这个树就变成这样(每一个矩形,包括已经被切开的点都是一个节点)
一个节点有四个儿子(左上,左下,右上,右下),如果不动态开点原地MLE,如果动态开点T飞
UPD:
这种写法操作容易被卡成$Theta(N)$,有另一种写法可以保证$Theta(log^2 N)$的复杂度。
其实就是在线段树的叶节点上再开线段树。
请大家自行百度:<链接>
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> //#include "debug.h" #define N 50001 using namespace std; int le; struct XDS{ int dat; XDS* rd,*ru,*ld,*lu; XDS(){ dat=0; rd=ru=lu=ld=NULL; } }*root; void add(XDS *&rt,int x,int y,int xl,int xr,int yl,int yr){ if(rt==NULL)rt=new XDS(); if(xl==xr&&yl==yr){ rt->dat++; return ; } int xmid=(xl+xr)>>1,ymid=(yl+yr)>>1; if (x>xmid &&y>ymid ) add(rt->rd,x,y,xmid+1,xr ,ymid+1,yr); else if(x>xmid &&y<=ymid) add(rt->ru,x,y,xmid+1,xr ,yl ,ymid); else if(x<=xmid&&y>ymid ) add(rt->ld,x,y,xl ,xmid,ymid+1,yr); else add(rt->lu,x,y,xl ,xmid,yl ,ymid); int sum=0; if(rt->rd!=NULL) sum+=rt->rd->dat; if(rt->ru!=NULL) sum+=rt->ru->dat; if(rt->ld!=NULL) sum+=rt->ld->dat; if(rt->lu!=NULL) sum+=rt->lu->dat; rt->dat=sum; } int ask(XDS *rt,int axl,int axr,int ayl,int ayr,int xl,int xr,int yl,int yr){ if(rt==NULL)return 0; if(axl<=xl&&axr>=xr&&ayl<=yl&&ayr>=yr) return rt->dat; int none=0; int xmid=(xl+xr)>>1,ymid=(yl+yr)>>1; none+=ask(rt->rd,axl,axr,ayl,ayr,xmid+1,xr ,ymid+1,yr); none+=ask(rt->ru,axl,axr,ayl,ayr,xmid+1,xr ,yl ,ymid); none+=ask(rt->ld,axl,axr,ayl,ayr,xl ,xmid,ymid+1,yr); none+=ask(rt->lu,axl,axr,ayl,ayr,xl ,xmid,yl ,ymid); return none; } int ans=0; int main(){ int a,b; scanf("%d",&le); for (int i=1;i<=le;i++){ scanf("%d%d",&a,&b); add(root,a,b,1,le,1,le); } for (int k=1;k<=le;k++){ for (int i=1;i<=le;i++){ if(i+k-1>le)continue; for (int j=1;j<=le;j++){ if(j+k-1>le)continue; if(ask(root,i,i+k-1,j,j+k-1,1,le,1,le)==k)ans++; } } } cout<<ans<<endl; return 0; }
4>二维前缀和
复杂度:$Theta(N^3)$
空间惊人,开不了那么大,考试想到了就可着部分分去吧
#include <iostream> #include <cstdio> #include <cstring> //#include "debug.h" #define N 1000 using namespace std; int n; bool x[N][N]; int pre[N][N]; int ans=0; int main(){ int a,b; cin>>n; for (int i=1;i<=n;i++){ scanf("%d%d",&a,&b); x[a][b]=1; } for (int i=1;i<=n;i++){ for (int j=1;j<=n;j++){ pre[i][j]=x[i][j]+pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]; } } for (int k=1;k<=n;k++){ for (int i=1;i<=n;i++){ if(i+k-1>n)continue; for (int j=1;j<=n;j++){ if(j+k-1>n)continue; cout<<i<<" "<<j<<endl; if(pre[i+k-1][j+k-1]-pre[i-1][j+k-1]-pre[i+k-1][j-1]+pre[i-1][j-1]==k)ans++; } } } //pour(pre,1,n,1,n,3,"pre"); cout<<ans<<endl; return 0; }
5>线段树优化一拨
我们在这里发现了这个题的真实题面:给定$N$个数的一个排列,问这个序列中有多少个子区间的数恰好是连续的。
进一步可以化为:有多少种情况使得,相邻的$k$个数中最大值和最小值的差小于等于$k-1$。
复杂度:$Theta(N^2 log N)$
还不错,没试能得几分
#include <iostream> #include <cstdio> #include <cstring> //#include "debug.h" #define N 50500 using namespace std; int x[N],n; struct XDS{ int maxn,minn; }tr[4*N]; void build(int id,int l,int r){ if(l==r){ tr[id].maxn=tr[id].minn=x[l]; return ; } int mid=(l+r)>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); tr[id].maxn=max(tr[id*2].maxn,tr[id*2+1].maxn); tr[id].minn=min(tr[id*2].minn,tr[id*2+1].minn); } int askmax(int id,int l,int r,int al,int ar){ if(l>=al&&r<=ar)return tr[id].maxn; int val,mid=(l+r)>>1; val=0; if(mid>=al) val=max(val,askmax(id*2 ,l ,mid,al,ar)); if(mid<ar) val=max(val,askmax(id*2+1,mid+1,r ,al,ar)); return val; } int askmin(int id,int l,int r,int al,int ar){ if(l>=al&&r<=ar)return tr[id].minn; int val,mid=(l+r)>>1; val=0x7fffffff; if(mid>=al) val=min(val,askmin(id*2 ,l ,mid,al,ar)); if(mid<ar) val=min(val,askmin(id*2+1,mid+1,r ,al,ar)); return val; } int ans=0; int main(){ int a,b; cin>>n; for (int i=1;i<=n;i++){ scanf("%d%d",&a,&b); x[a]=b; } build(1,1,n); for (int i=1;i<=n;i++){ for (int j=1;j<=n;j++){ if(i<=j){ if(askmax(1,1,n,i,j)-askmin(1,1,n,i,j)==j-i)ans++; } else{ if(askmax(1,1,n,j,i)-askmin(1,1,n,j,i)==j-i)ans++; } //puts("DDD"); } } cout<<ans<<endl; return 0; }
6>(借的)别人的暴力(?)
复杂度:$Theta(N^2)$
很优秀了,蒟蒻OTZ
#include<cstdio> #include<ctime> using namespace std; #define rus register unsigned short inline unsigned short max(const rus a,const rus b){return a>b?a:b;} inline unsigned short min(const rus a,const rus b){return a<b?a:b;} inline unsigned short read(){ rus a=0;register char ch=getchar(); while(ch<48||ch>57)ch=getchar(); while(ch>=48&&ch<=57)a=(a<<3)+(a<<1)+ch-48,ch=getchar(); return a; } int ans; unsigned short pos[50005],n; int main(){ n=read(); for(rus i=1,x,y;i<=n;++i) x=read(),y=read(), pos[x]=y; for(rus i=1,maxx=0,minn=50006;i<=n;++i,maxx=0,minn=50006){ if(clock()>990000){printf("%d ",ans+n-i+1);return 0;} for(rus j=i;j<=n;++j){ maxx=max(maxx,pos[j]); minn=min(minn,pos[j]); if(maxx-minn==j-i)ans++; } } printf("%d ",ans);//printf("%ld ",clock()); } //考场上DeepinC的n2卡常,应该能看出来
7>终于到了正解%%%
复杂度:使用 reverse(); $Theta(Nlog^2N)$
不用:$Theta(Nlog N)$
这个正解我好像解释不清楚~~
我尽量
首先我们可以想到分治,
把区间分成两部分,当前的区间答案就可以表示为$ans_{[l,mid]}+ans_{[mid+1,r]}+$跨区间方案
你很好想前两部分如何写(递归)
问题就出在跨区间这里
有两种,
最值在一边:
我纳过闷,最值在一边,怎么跑另一边????
但是:可以这样:
O | X | X | X |
X | X | O | X |
X | X | X | O |
X | O | X | X |
表示成一维:${1,4,2,3}$
发现可以延伸到那边
我怎么算?
要用这个条件:$egin{array}{cc}max{A_i cdots A_{mid}} & > & max{A_{mid+1} cdots A_j } \ min{A_i cdots A_{mid}} & < & min{A_{mid+1} cdots A_j}end{array}$其中$i-j=max-min$
最值在两边:单调栈思想?桶
解释不清楚~
上关键代码:
/*下面是判最大最小在左右的*/ int ll=mid+1,rr=mid+1;//两个指针记录可行区间,每次我们去搜小的 for(int k=mid;k>=l;k--)//左小右大 { while(minn[rr]>minn[k]&&rr<=r) check(maxn[rr]-rr)++,rr++;//把一段合法区间都标记一下 while(maxn[ll]<maxn[k]&&ll<rr) check(maxn[ll]-ll)--,ll++;//把非法的情况剪掉 ans+=check(minn[k]-k); } while(ll<rr) check(maxn[ll]-ll)--,ll++;//好像????是恢复初始状态 ll=mid,rr=mid; for(int k=mid+1;k<=r;k++)//左大右小 { while(minn[rr]>minn[k]&&rr>=l) check(maxn[rr]+rr)++,rr--;//同上 while(maxn[ll]<maxn[k]&&ll>rr ) check(maxn[ll]+ll)--,ll--; ans+=check(minn[k]+k); } while(ll>rr) check(maxn[ll]+ll)--,ll--; return ans;
终于让我水了它……
#include <iostream> #include <cstdio> #define N 50505 #define check(i) tong[(i)+50000] #define Inf 0x7fffffff using namespace std; int n,arr[N],Ans=0; int maxn[N],minn[N]; int tong[3*N]; int divide(int l,int r){//分治 if(l==r)return 1;//递归边界 int ans=0,mid=(l+r)>>1;//二分 ans+=divide(l,mid);//左右的分区间 ans+=divide(mid+1,r); maxn[mid]=minn[mid]=arr[mid];//处理i到mid之间的最大最小值 maxn[mid+1]=minn[mid+1]=arr[mid+1]; for (int i=mid+2;i<=r;i++){ //处理 maxn[i]=max(arr[i],maxn[i-1]); minn[i]=min(arr[i],minn[i-1]); } for (int i=mid-1;i>=l;i--){ maxn[i]=max(arr[i],maxn[i+1]); minn[i]=min(arr[i],minn[i+1]); } for (int i=mid;i>=l;i--){ //处理中间到左边的最值 int j=maxn[i]-minn[i]+i; //利用maxn-minn=j-i推的j if(/*1*/j>mid&& //合法区间 /*2*/j<=r&& /*3*/maxn[j]<maxn[i]&& //满足maxn-minn /*4*/minn[j]>minn[i]){ ans++; } } for (int i=mid+1;i<=r;i++){ int j=i-maxn[i]+minn[i]; //推的第二个 maxn-minn=i-j if(/*1*/j>=l&& /*2*/j<=mid&& /*3*/maxn[j]<maxn[i]&& //要满足maxn和minn /*4*/minn[j]>minn[i]){ ans++; } } /*下面是判最大最小在左右的*/ int ll=mid+1,rr=mid+1;//两个指针记录可行区间,每次我们去搜小的 for(int k=mid;k>=l;k--)//左小右大 { while(minn[rr]>minn[k]&&rr<=r) check(maxn[rr]-rr)++,rr++;//把一段合法区间都标记一下 while(maxn[ll]<maxn[k]&&ll<rr) check(maxn[ll]-ll)--,ll++;//把非法的情况剪掉 ans+=check(minn[k]-k); } while(ll<rr) check(maxn[ll]-ll)--,ll++;//好像????是恢复初始状态 ll=mid,rr=mid; for(int k=mid+1;k<=r;k++)//左大右小 { while(minn[rr]>minn[k]&&rr>=l) check(maxn[rr]+rr)++,rr--;//同上 while(maxn[ll]<maxn[k]&&ll>rr ) check(maxn[ll]+ll)--,ll--; ans+=check(minn[k]+k); } while(ll>rr) check(maxn[ll]+ll)--,ll--; return ans; } int main(){ int a,b; scanf("%d",&n); for (int i=0;i<n;i++){ scanf("%d%d",&a,&b); arr[a]=b; } printf("%d ",divide(1,n)); return 0; }