NOIP2017小结



title: NOIP2017小结

tags:

---

Day2

T3

题意十分的简洁,就是需要弄个数据结构去维护一个矩阵中,删除一个位置,然后把这一行左移一个单位,再把最后一列前移一个单位。反正,我一看就觉得是线段树,直接建\(n+1\)个线段树,就可以直接搞了,二看就觉得空间分分钟炸掉。于是就没啥办法了。

\(30pts\)

考虑\(30pts\)的数据,暴力模拟就好了。

\(50pts\)

然后还有一个\(20pts\)的数据,\(q\)比较小,似乎有一种\(q^2\)的做法,反正我是不会。

但是后面还有一个\(20pts\)的数据,保证\(n=1\),那么就好办了,直接暴力维护一个线段树,感觉会被卡常,我就写了个树状数组。其实就是这么维护的,我维护了2个树状数组,然后,一个维护现在这个位置的原编号,一个维护前缀和。然后删除一个点,就是把两个树状数组都弄个后缀修改,最后再在最后一个位置加回来就好了,一个简单的差分思想。也就是说,我们把删除的那个位置直接空余出来,然后再最后的位置上再添加信息,貌似2016年的那个蚯蚓,我考场上也是用线段树这么写的,好套路的东西啊

\(60pts\)

既然可以维护一个\(n=1\)的特殊情况,那么\(x=1\)的,就弄4个树状数组就好了,分别是维护第一行的标号与前缀和,维护最后一列的标号与前缀和,但是有点麻烦,而且只有10分,考场上我又没时间写了。

1.把第一行的这个位置删掉,输出结果,然后把最后一列的第二行加进来。

2.把最后一列的第一行删掉,把输出的结果加到最后一列的最后一行。

可能用树状数组写的真有点麻烦,所以可以直接建2个线段树,那就好写一点了。

\(60-100pts\)

这仅仅是个思路,尽管我在想到树状数组后马上就想到了,但是由于思路很混乱,我并没敢在考场上写。

考虑一下我们在\(60\)分做法中尽管效率\(O(qlogn)\)是可以过的,但是我们的做法扩展到\(n*m\)上来就被卡空间了,所以我想了一个这种的办法:暴力分块(阔怕

我们可以对于一行,把\(\sqrt{n}\)列化作一块,然后对于最后一列,再把\(\sqrt{n}\)行化作一块,对于一个块,我们维护2个信息:块的大小和块内的前缀和。

然后删去一个点时,我们找到这一行,然后再这一行中,用二分查找确定这个点所在的块,直接把这个点删掉,然后把最后一列的对应行的加到这一行的最后一个块中,然后把这个点加到最后一列的最后一个块中。

等等,貌似还是会被卡空间哈。因为我们没有能避免建出一个\(n*m\)的矩阵。

但是,把这个思路用到线段树中,再加一些技巧就可以A了,那就是\(100\)分做法了。

\(100pts\)

基本原理

首先有线段树做法:

线段树的问题,常常都很容易被卡空间,然后就要弄一个叫动态开点的思想,就是避免一开始把所有的数据全部都建到线段树中,而是对于每次操作,都单独进行一个点修改,同时据此建一个大小为\(logn\)的线段树,这好像也就是主席树的基本思想哈,严格来说应该叫可持久化啊,但是这也就有麻烦了,因为如果我们在这道题中不一开始就把线段树建好,我们就存在2个麻烦:

1.我们没有办法确定一个点在某一行中线段树中的具体位置,因为我们没有一开始就建好线段树,所以我们没有办法直接维护编号。

2.我们没有办法确定一个点的数值,因为我们没有一开始就建好线段树,所以我们没有办法维护一开始的数值(好熟悉的一句话),那么经过操作后就更没有办法维护了。

那么我们就单独考虑这两个麻烦。

我们沿用分块中维护块的大小的思想,我们在这个动态的线段树中维护一个size域,用来表示这个区间中存在的点的个数然后二分查找一下,确定询问点的位置。然后考虑一下如何在动态开点中维护一个size域。其实仔细想一下也是很简单的,如果我们这个区间之前一直没有访问过,说明这个区间内的点的个数是没有受到前面操作的影响的,那么点的个数就是区间长度了,如果这个区间是访问过得,那么我们在做删除操作的时候,size--,在做添加操作时,size++不就好了?需要注意一个细节,就是我们建的线段树应该是\(max(n,m)+q\)大小的啊,那么我们可能存在一开始就没有访问的区间,其右端点是大于\(max(n,m)\),那么在计算初始size域的时候,就不是区间长度了,而应该这么判断:如果其左端点小于\(max(n,m)\)那么初始的size域就是m-l,如果其左端点大于\(max(n,m)\)那么初始的size域就是0,这么做可能就会存在一个问题,也就是说,如果我们在n+1的位置添加了一个点,那么n+2的size域在初始化的时候是不是就会出现问题?庆幸的是,这是不会出现的。因为如果n+1和n+2是一个区间内的,那么这个区间就是访问过得了,那么size直接++--就行了,如果不是一个区间内的,那么n+2显然是0啊,因为我们维护的是区间内的总和,而不是整个的前缀和。因为如果维护整个的前缀和,我们在修改时,就避免不了\(O(n)\)的修改。这个麻烦就差不多了。

然后我们考虑一下具体数值的转移,在有上面的做法基础上,我们只需要在size域初始化的同时,直接搞一个赋为(x-1)*m+y就好了啊,然后单独弄个modify过程就好了,似乎就很简单了哈。

Tips

做法可能有点绕啊,所以考场上几乎想不出来呵,即使想出来了,也不一定码的出来呵。所以要想理好思绪再写。

Some expand

然后,好像有一个更好写,更高大上的做法(但是我太弱了,并不会写)——平衡树做法:

搞一个非旋treap(好像也是可持久化数据结构啊)或者就是splay去做类似线段树的操作,那样可以节省一点空间,但是常数就很玄学了,尤其是对于这类卡常变态级的题目,Luogu的机子可以过,但是觉着CCF的老爷就很蛋疼了哈。

参考程序:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define rint register int
using namespace std;
typedef long long ll;
const int M=1e7+3;
const int N=3e5+5;
struct pdt{
    int L,R,sum;
    ll val;
}T[M];
int n,m,q,totn,sz,flag;
int root[N],tot[N];
inline int read(){
    rint x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(‘0‘<=ch&&ch<=‘9‘)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
inline int getSum(int l,int r){
    if (flag>n){
        if (r<=n)return r-l+1;
        if (l<=n)return n-l+1;
        return 0;
    }
    if (r<m)return r-l+1;
    if (l<m)return m-l;
    return 0;
}
inline ll query(int&now,int k,int l=1,int r=totn){
    if (!now){
        now=++sz;
        T[now].sum=getSum(l,r);
        if (l==r){
            if (flag>n) T[now].val=(ll)l*m;
            else T[now].val=(ll)(flag-1)*m+l;
        }
    }
    --T[now].sum;
    if (l==r)return T[now].val;
    rint mid=(l+r)>>1,tt=T[now].L?T[T[now].L].sum:(mid-l+1);
    if (k<=tt)
        return query(T[now].L,k,l,mid);
    else
        return query(T[now].R,k-tt,mid+1,r);
}
inline void modify(int&now,int k,ll v,int l=1,int r=totn){
    if (!now){
        now=++sz;
        T[now].sum=getSum(l,r);
        if (l==r)T[now].val=v;
    }
    ++T[now].sum;
    if (l==r) return;
    rint mid=(l+r)>>1;
    if (k<=mid) modify(T[now].L,k,v,l,mid);
        else modify(T[now].R,k,v,mid+1,r);
}
int main(){
    freopen("phalanx.in","r",stdin);
    freopen("phalanx.out","w",stdout);
    n=read(),m=read(),q=read();
    totn=max(n,m)+q;
    for (rint i=1;i<=q;++i){
        ll ans;
        rint x=read(),y=read();
        if (y==m) flag=n+1,ans=query(root[n+1],x);
            else flag=x,ans=query(root[x],y);
        printf("%lld\n",ans);
        flag=n+1,modify(root[n+1],n+(++tot[n+1]),ans);
        if (y!=m){
            flag=n+1,ans=query(root[n+1],x);
            flag=x,modify(root[x],m-1+(++tot[x]),ans);
        }
    }
    return 0;
}
时间: 2024-10-29 10:53:55

NOIP2017小结的相关文章

使用Apache POI导出Excel小结--导出XLS格式文档

使用Apache POI导出Excel小结 关于使用Apache POI导出Excel我大概会分三篇文章去写 使用Apache POI导出Excel小结--导出XLS格式文档 使用Apache POI导出Excel小结--导出XLSX格式文档 使用Apache POI导出Excel--大数量导出 导出XLS格式文档 做企业应用项目难免会有数据导出到Excel的需求,最近在使用其,并对导出Excel封装成工具类开放出来供大家参考.关于Apache POI Excel基本的概念与操作我在这里就不啰嗦

【转载】小结一下linux 2.6内核的四种IO调度算法

在LINUX 2.6中,有四种关于IO的调度算法,下面综合小结一下: 1) NOOP NOOP算法的全写为No Operation.该算法实现了最最简单的FIFO队列,所有IO请求大致按照先来后到的顺序进行操作.之所以说“大致”,原因是NOOP在FIFO的基础上还做了相邻IO请求的合并,并不是完完全全按照先进先出的规则满足IO请求.NOOP假定I/O请求由驱动程序或者设备做了优化或者重排了顺序(就像一个智能控制器完成的工作那样).在有些SAN环境下,这个选择可能是最好选择.Noop 对于 IO

Android基础入门教程——8.1.3 Android中的13种Drawable小结 Part 3

Android基础入门教程--8.1.3 Android中的13种Drawable小结 Part 3 标签(空格分隔): Android基础入门教程 本节引言: 本节我们来把剩下的四种Drawable也学完,他们分别是: LayerDrawable,TransitionDrawable,LevelListDrawable和StateListDrawable, 依旧贴下13种Drawable的导图: 1.LayerDrawable 层图形对象,包含一个Drawable数组,然后按照数组对应的顺序来

Android基础入门教程——8.1.2 Android中的13种Drawable小结 Part 2

Android基础入门教程--8.1.2 Android中的13种Drawable小结 Part 2 标签(空格分隔): Android基础入门教程 本节引言: 本节我们继续来学习Android中的Drawable资源,上一节我们学习了: ColorDrawable:NinePatchDrawable: ShapeDrawable:GradientDrawable!这四个Drawable~ 而本节我们继续来学习接下来的五个Drawable,他们分别是: BitmapDrawable:Insert

安卓小结《1》

Activity的生命周期和启动模式的知识点小结: 1.如果Activity切换的时候,新Activity是透明,旧的不会走onStop方法. 2.新的Activity切换的时候,旧Activity  会先执行,onpause,然后才会启动新的activity. 3. Activity在异常情况下被回收时,onSaveInstanceState方法会被回调,回调时机是在onStop之前,当Activity被重新创建的时 候,onRestoreInstanceState方法会被回调,时序在onSt

date命令小结

在写linux shell脚本时,date是经常要用到的一个命令,这篇文章就此做个小结,以防自己用到时到处找 1.最基本的,显示当前的具体时期:直接敲入 date即可,如下, [email protected]:~/scripts$ date 2015年 01月 03日 星期六 21:46:49 CST 2.显示某个文件上次修改的时间:date -r file [email protected]:~/scripts$ date -r save.sh 2015年 01月 02日 星期五 23:29

java 小结2 多态问题。

面向对象这个东西,其实我们一直是不是都没有感觉到自己在用,以后我一定要用用.以前学c#时候认真的看过一次,最近一直研究java.随便再看看. 多态问题: 在java中多态分为(1)编译时多态和(2)运行时多态 (1)编译时多态比较容易理解:其实就是通过方法重载,就是方法的重载,同一个函数名但是可以参数不一样.这就是重载(so easy) (2)运行时多态:这个是通过方法覆盖实现的,就是子类在继承父类的时候,通过对某个方法的重写,覆盖父类方法. 简单的说:比如我们有个父类A,子类B通过Extend

php操作xml小结

<?php #php操作xml,SimpleXMLElement类小结 header('Content-type:text/html;charset=utf-8;'); //1.构造函数 /* $xmlstring=<<<XML <?xml version="1.0" encoding="utf-8"?> <note  xmlns:b="http://www.w3school.com.cn/example/&quo

网络安全解决之个人小结

方案分为安全技术部分和安全管理部分. 安全技术部分: 1.物理安全 需要建设独立的计算机机房,满足防水.防火.防静电等要求.机房设置门禁和视频监控. 2.网络安全 采用防火墙进行安全区域分割,把公司网络分为服务器区和办公区.设置不同的安全规则以防范黑客攻击.采用上网行为管理产品对网络行为和流量进行管控. 3.系统安全 采用终端安全管理系统,对客户端进行管控,重点管控网络行为.补丁升级和软件分发等.对服务器进行安全加固,保障服务器安全. 4.应用安全 对Web电子商务服务器进行漏洞扫描和加固,防范