zoukankan      html  css  js  c++  java
  • 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+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;
    }
    
    Miemeng真的蒻
  • 相关阅读:
    ECMAScript 6 字符串的扩展
    iOS蓝牙开发
    PhotoKit type类型
    HealthKit详解
    苹果证书签名机制
    小程序事件传递
    小程序跳转界面传可变参数
    小程序获取openId
    小程序发起post请求回调成功没有数据
    主干发布和分支发布
  • 原文地址:https://www.cnblogs.com/kalginamiemeng/p/Exam20190716T3.html
Copyright © 2011-2022 走看看