20190716-T3-奇袭

我要嗝了

我经过一系列努力,寻找了一系列,各种复杂度的方法。

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+N\log_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\n",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\n",ans);//printf("%ld\n",clock());
}

//考场上DeepinC的n2卡常,应该能看出来

7>终于到了正解%%%

复杂度:使用 reverse(); $\Theta(N\log^2N)$

不用:$\Theta(N\log 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\}$

发现可以延伸到那边

我怎么算?

要用这个条件:$\begin{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\n",divide(1,n));
    return 0;
}

 

posted @ 2019-07-17 20:00  Miemeng_麦蒙  阅读(169)  评论(0编辑  收藏  举报

小麦在雨中,蒙蒙的雾气

麦蒙不想有人骚扰他,如果有必要 联系 QQ:1755601414

如果你嫌广告大,那就喷我吧,不是博客园的锅。