zoukankan      html  css  js  c++  java
  • 暑假考试题3:baritone 上低音号与星星(链表+矩形统计)

     

    题目:

    n,r,c<=3000

    分析:先枚举左边界   然后将点从高到矮连链表   再统计从每一个点开始含括k个点的矩形   能够上下延伸得到的多少个矩形。
    然后枚举右边界删点    利用大矩形原有的信息修改后    去累加新的左右宽度较小的矩形的贡献。

    这种算法的优势: 每一次缩小矩形的时候 可以不用重新计算小矩形的贡献 只需要通过大矩形原有的贡献进行修改
    修改方式:通过枚举右边界缩小矩形 删掉超出右边界范围内的那一列的点
    删点时重新统计贡献 :也就是对被影响的点重新计算一下(通过跳链表找到受影响的点)

    ys的图与详解

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define N 4005
    int zong[N],x[N],y[N],r,c,n,k,q[N],val[N],nex[N],to[N],pre[N],num[N];
    ll sum,ans=0;
    vector<int>v[N];
    bool cmp(int a,int b)
    {
        if(q[a]==q[b]) return zong[a]<zong[b];
        return q[a]<q[b];
    }
    void del(int t)
    {
        nex[pre[t]]=nex[t];//通过链表连边的方式删去t点 
        pre[nex[t]]=pre[t];
        sum-=val[t];//删掉这个点 就应该把其贡献去掉 
        int x=nex[t],y=nex[t];//跳nex是因为影响了下面一个点 因为下面一个点的上界是通过t来算的 t变了  
        for(int i=1;i<=k-1;i++) y=nex[y];
        for(int i=1;i<=k+1;i++){
            int v=(q[x]-q[pre[x]])*(r-q[y]+1);
            //这个点原来的val就是这个点向右包含恰好k个点 然后向上下延伸得到的矩形个数 
            sum+=v-val[x];// val贡献也应随着改变 所以应该重新统计一下x这个点的贡献 
            val[x]=v;
            x=pre[x],y=pre[y];//因为t被删去了 所以前面通过t这个点向右延伸达到k个的val值都应该改变 
        }
    }
    int main()
    {
        freopen("baritone.in", "r", stdin);
        freopen("baritone.out", "w", stdout);
        int len=0;
        scanf("%d%d%d%d",&r,&c,&n,&k);
        for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]),v[y[i]].push_back(i);
        for(int i=1;i<=c;i++){//枚举左边界 
            sum=0; len=0;
            for(int j=0;j<=20;j++)//防止越界 加入40个虚点 使得前面有20个点 后面有20个点 
             q[++len]=0,q[++len]=r+1;
            for(int j=1;j<=n;j++)
            if(y[j]>=i){//如果这个点在枚举的左边界之内 就放入队列 维护相关信息 
                q[++len]=x[j];//q是存的横坐标!! 
                zong[len]=y[j];
                to[j]=len;//to是在链表中的编号 
            }
            for(int j=1;j<=len;j++) num[j]=j;//num是每一个点的标号数组 便于对前len个在队列中的点 按横坐标为第一关键字排序 
            sort(num+1,num+1+len,cmp); 
            for(int j=1;j<=len-1;j++)
            nex[num[j]]=num[j+1],pre[num[j+1]]=num[j];//求每一个点的前驱和后继
            //for(int j=1;j<=len;j++) printf("%d ",pre[j]);
            nex[num[len]]=num[len];
            pre[num[1]]=num[1];
            for(int j=1;j<=len;j++){//接下来是答案的统计 
                int now=j;
                for(int p=1;p<=k-1;p++)//跳k-1次恰好跳到的点 使得j和now之间恰好有k个点(包含它们本身) j和now是上下关系 
                now=nex[now];
                val[j]=(q[j]-q[pre[j]])*(r-q[now]+1);
                //从pre[j]到 j表示 j可以向上延伸的长度 从q[now]到r是now向下可以延伸的长度 
                //根据乘法原理累计答案 +1是因为有一方可以不延伸
                sum+=val[j]; 
            } //printf("ans:%lld
    ",ans);
            for(int j=c;j>=i;j--){
                ans+=sum;
                for(int p=0;p<v[j].size();p++)//右边界向左移动 删去在这一列的所有点 因为下一次它们就超出范围了
                del(to[v[j][p]]);//找到第j列的所有点在链表中的编号 然后删掉 更新答案 
            }
            //当右边界向左边界延伸的时候 其实是为了得到左右宽度更小的矩形答案 
            
        }
        printf("%lld
    ",ans);
    }
    /*
    3 2 3 3
    1 1 
    3 1 
    2 2 
    */
  • 相关阅读:
    汉字机内码的特点
    while(~scanf(..))的用法
    【C语言】八进制转十进制
    【C语言】按字典顺序排序
    【C语言】矩阵相乘
    【C语言】魔方阵
    【C语言】统计候选人的得票数
    【C语言】对输入的字符串中C关键词的查找统计
    20201231《信息安全导论》第十二周学习总结
    20201231《信息安全导论》第十一周学习总结
  • 原文地址:https://www.cnblogs.com/mowanying/p/11405847.html
Copyright © 2011-2022 走看看