纪念神九发射作业(1)

今天rabbithu和陈独秀……给我们留了八道提高+~NOI难度的题目……蒟蒻今天只做出来三道……还是别人给讲的……

不管怎么说,写题解记录下来……不然以后忘了这种题怎么做咋整……

C题:CQOI2018 异或序列 

这道题可以看出来是莫队的板子题,不过一开始还是看得我一脸懵逼……

由于异或的逆运算是其本身,那么我们就可以想办法解决这道题中最关键的一点——如何在分块之后对于每个访问的区间都能O(1)的求出其内部异或和等于k的子区间有多少个。

基础的莫队是用桶来记录一个数出现了多少次,而且他是每次移动一个点,单点修改的,既然如此,那我们也可以仿照这种思路来写。

首先是如何处理一些不与访问区间端点相连的子区间的问题,这个仔细想想发现很容易,因为我们是一个一个点修改的,这样我们会记录下来所有的其异或前缀和^k符合条件的点。

再者,对于序列内任意一个数a[l],a[l] = sum[l-i]^sum[l],其中sum记录异或前缀和。再根据异或的逆运算是其本身的性质,我们直接在每次更新莫队的时候,把当前这个点的异或前缀和压到桶中,并且在总和上记录该异或前缀和再异或k的值在当前的莫队中有多少个即可。这样每次增加一个点,我们就相当于增加了一段异或区间和等于k的区间。删除与之道理相同。

最后还有一点,一开始的桶要初始化,把cnt[0]设为1,因为你在计算右端点为l的访问区间的时候,必须要用到l-1,所以我们先手压入一个0以正常工作。

上代码啦。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm> 
#include<cmath>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
const int M = 100005;
const int B = ceil(sqrt(M));
#define bel(x) ((x - 1) / B + 1)//分块
typedef long long ll;
struct node
{
    int l,r,id;
    bool operator  < (const node &b) const//重载运算符进行排序
    {
        return bel(l) == bel(b.l) ? r < b.r : l < b.l;
    }
}q[M];
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
int n,m,k,a[M],cnt[M],sum,ans[M];
void insert(int x)//插入和删除
{
    sum += cnt[x^k];
    cnt[x]++;
}
void del(int x)
{
    cnt[x]--;
    sum -= cnt[x^k];
}
int main()
{
    n = read(),m = read(),k = read();
    rep(i,1,n) a[i] = read(),a[i] ^= a[i-1];
    rep(i,1,m) q[i].l = read(),q[i].r = read(),q[i].id = i;
    sort(q+1,q+1+m);
    int kl = 1,kr = 0;cnt[0] = 1;//这里是为了计算以1为开头的区间的前缀和 
    rep(i,1,m)//正常莫队操作,注意顺序
    {
        while(kr < q[i].r) insert(a[++kr]);
        while(kr > q[i].r) del(a[kr--]);
        while(kl < q[i].l) del(a[kl-1]),kl++;
        while(kl > q[i].l) kl--,insert(a[kl-1]);
        ans[q[i].id] = sum;
    }
    rep(i,1,m) printf("%d\n",ans[i]);
    return 0;
}

F题:luogu3801 红色的幻想乡

rabbithu说D题最简单……然而分明是F题最简单……

来了个贼强的无限放雾的dalao。这题一开始想到二维线段树(然而根本就不会),不过后来发现并不需要。因为这姐们每次从一个点放雾是向横,纵向无限长放雾,那么我们在维护某一向的值得时候,就不用维护这一向的每一点在另一方向上的长度了。

而且,她每次放雾是自己所在的位置没雾,而且两股雾在一起会抵消哦,也就是相当于她自己先在纵向上放一列,又在横向上放一行,结果自己内个位置因为放过两次抵消了,这样就可以用异或操作修改了。

到这里思路已经很清晰了,建两棵线段树,一棵维护纵向,一颗维护横向,那么怎么计算呢?

对于其给定的每一个矩形区域,是可以保证区域中只要有红雾的行、列一定充满了红雾,我们只要先query一遍这个矩形长方向上的红雾和,在query一遍纵向的,之后把横向上的乘以矩形纵向长,纵向乘以矩形横向长,最后再用容斥原理,减去二倍的交点数即可。(注意这里一定不要乘反!我就是乘反才爆零)

本题数据到10^5,别以为其平方小于2147483647就无忧了,你在相加的过程中是可能溢出的,要开longlong。之后就轻松A了。

上代码。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm> 
#include<cmath>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
const int M = 100005;
typedef long long ll;
struct seg
{
    ll v;
}t1[M<<2],t2[M<<2];
ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
ll n,m,q,op,x,y,kx,ky,sx,sy,l,w,k1,k2,d;
void modify1(ll p,ll l,ll r,ll x)//这题真的只需要单点修改,modify2照抄
{
    if(l == r)
    {
        t1[p].v ^= 1;
        return;
    }
    ll mid = (l+r) >> 1;
    if(x <= mid) modify1(p<<1,l,mid,x);
    else modify1(p<<1|1,mid+1,r,x);
    t1[p].v = t1[p<<1].v + t1[p<<1|1].v;
}
void modify2(ll p,ll l,ll r,ll x)
{
    if(l == r)
    {
        t2[p].v ^= 1;
        return;
    }
    ll mid = (l+r) >> 1;
    if(x <= mid) modify2(p<<1,l,mid,x);
    else modify2(p<<1|1,mid+1,r,x);
    t2[p].v = t2[p<<1].v + t2[p<<1|1].v;
}
ll query1(ll p,ll l,ll r,ll kl,ll kr)//query与普通线段树相同,query2照抄
{
    if(l == kl && r == kr) return t1[p].v;
    ll mid = (l+r) >> 1;
    if(kr <= mid) return query1(p<<1,l,mid,kl,kr);
    else if(kl > mid) return query1(p<<1|1,mid+1,r,kl,kr);
    else return query1(p<<1,l,mid,kl,mid) + query1(p<<1|1,mid+1,r,mid+1,kr);
}
ll query2(ll p,ll l,ll r,ll kl,ll kr)
{
    if(l == kl && r == kr) return t2[p].v;
    ll mid = (l+r) >> 1;
    if(kr <= mid) return query2(p<<1,l,mid,kl,kr);
    else if(kl > mid) return query2(p<<1|1,mid+1,r,kl,kr);
    else return query2(p<<1,l,mid,kl,mid) + query2(p<<1|1,mid+1,r,mid+1,kr);
}
int main()
{
//    freopen("a.in","r",stdin);
    n = read(),m = read(),q = read();
    rep(i,1,q)
    {
        op = read();
        if(op == 1) 
        {
            x = read(),y = read();
            modify1(1,1,n,x);
            modify2(1,1,m,y);
        }
        if(op == 2)
        {
            kx = read(),ky = read(),sx = read(),sy = read();
            l = sx-kx+1,w = sy-ky+1;//计算矩形长宽
            k1 = query1(1,1,n,kx,sx);
            k2 = query2(1,1,m,ky,sy);
//            printf("%d %d %d %d\n",l,w,k1,k2);
            d = k1 * w + k2 * l - 2 * k1 * k2;//用容斥原理计算被红雾覆盖块数
            printf("%lld\n",d);
        }
    }
    return 0;
}

H题:HEOI/TJOI2016 排序

这道题真的对我来说很有难度……如果不是听人讲的话真的做不出来……算法思路很好理解……不过有许多坑要注意。

听说这是一道套路题emm?

不管啦,直接开讲。这道题如果直接暴力模拟sort的话,是可以拿到30,那我们再想想,sort之所以不行的原因,是因为其每次操作nlogn,再乘以sort的次数必然不行,那么有什么方法能更快地排序呢……

显然普通方法是不可能的,那我们就有别的方法了……这是一个极好的套路,也是一个极为值得学习的方法——把所有数字转化为01串!

这样排序的问题就迎刃而解了。我们只需要先手统计出你要sort的区间有多少个1(k个),之后直接对起始~末尾-k和末尾-k+1~末尾做区间修改即可,全改成0或者1,贼舒服,升降序全搞定。每次sort只需要2logn的时间。

那么,我们接下来该干嘛呢……我们如何能通过01串来确定你要访问的位置上的数是多少,这是个关键。我们想,不过怎么排序,因为题目给定的排序只有一种顺序,照着他所说的排完你要的位置自然是答案,所以无论你是转化为01串还是什么其他的串,那个点上应该有的数是不会变的!那我们怎么用01串确定呢?

二分答案!每次把大于等于当前二分值得数设为1,小于的为0,这样的话sort结束之后如果要访问的位置如果是0的话那就说明你的数取大了(因为0映射一个小于当前二分值的数,而实际上那一位应该是1),否则就是取小或者正好等于,那么直接这么二分下去就可以了。算法思路十分清晰,二分答案之后每次新建一棵树,之后对其按照要求进行多次区间修改,最后返回要访问的那一位的值即可。二分logn,每次建树+区间修改mlogn,在n,m <= 10^5的情况下可以过。

但是这个题坑贼多。

1.你打lazy标记的时候,初始值要设为-1,因为你是直接赋值修改,而不是加,所以不想在加法操作中lazy'为0就可以随便操作,这里是不行的!

2.你modify的时候,由于query出来的1 的个数可能很多,有可能导致操作越界。

这时候你想,啊,那我直接去区间端点和要修改的端点的极值不就行了吗。不!你这样确实是不会RE,但是再想,比如你长度为4 的区间有四个1,你如果取端点,相当于你还是把一个端点值改为0,但你实际上是不应该改的!

所以我们的方法是在函数里加特判,其他啥也不要动!

3.线段树别写跪了……

就这些……然后就A了人生中第3道紫题。而且这个题的思路是十分值得学习的,这次理解了,也不知道以后能不能用出来……

上代码。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm> 
#include<cmath>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
const int M = 100005;
typedef long long ll;
struct seg
{
    int v;
}t[M<<2];
struct opera
{
    int l,r,op;
}o[M]; 
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
int n,m,a[M],op,dl,dr,q,lazy[M<<2],mi,ans;
void build(int p,int l,int r,int x)//建树
{
    if(l == r) 
    {
        t[p].v = (a[l] >= x)? 1:0;//按大小来确定01
        return;
    }
    int mid = (l+r) >> 1;
    build(p<<1,l,mid,x);
    build(p<<1|1,mid+1,r,x);
    t[p].v = t[p<<1].v + t[p<<1|1].v;
}

void down(int p,int l,int r)//注意lazy标记下放过程!
{
    int mid = (l+r) >> 1;
    if(lazy[p] == -1) return;
    lazy[p<<1] = lazy[p];
    lazy[p<<1|1] = lazy[p];
    t[p<<1].v = lazy[p] * (mid-l+1);
    t[p<<1|1].v = lazy[p] * (r - mid);
    lazy[p] = -1;
}

int query(int p,int l,int r,int kl,int kr)//正常查询
{
    if(l == kl && r == kr) return t[p].v;
    down(p,l,r);
    int mid = (l+r) >> 1;
    if(kr <= mid) return query(p<<1,l,mid,kl,kr);
    else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
    else return query(p<<1,l,mid,kl,mid) + query(p<<1|1,mid+1,r,mid+1,kr);
}

void modify(int p,int l,int r,int kl,int kr,int val)//正常修改
{
    if(kl > kr || kl < l || kr > r) return;//这句话贼重要!少了就RE!
    if(l == kl && r == kr)
    {
        t[p].v = val * (r-l+1);
        lazy[p] = val;
        return;
    }
    down(p,l,r);
    int mid = (l+r) >> 1;
    if(kr <= mid) modify(p<<1,l,mid,kl,kr,val);
    else if(kl > mid) modify(p<<1|1,mid+1,r,kl,kr,val);
    else modify(p<<1,l,mid,kl,mid,val),modify(p<<1|1,mid+1,r,mid+1,kr,val);
    t[p].v = t[p<<1].v + t[p<<1|1].v;
}
bool check(int x)
{
    memset(t,0,sizeof(t));
    memset(lazy,-1,sizeof(lazy));
    build(1,1,n,x);//先手建树
    rep(i,1,m)
    {
        int k = query(1,1,n,o[i].l,o[i].r);//查询区间中1的个数
        if(!o[i].op)//升序排列
        {
            modify(1,1,n,o[i].l,o[i].r-k,0);
            modify(1,1,n,o[i].r-k+1,o[i].r,1);//这里注意不要乱改端点值,加特判即可
        }
        else//降序
        {
            modify(1,1,n,o[i].l,o[i].l+k-1,1);
            modify(1,1,n,o[i].l+k,o[i].r,0);
        }
    }
    return query(1,1,n,q,q);//返回单点查询值
}
int main()
{
    n = read(),m = read();
    rep(i,1,n) a[i] = read();
    rep(i,1,m) o[i].op = read(),o[i].l = read(),o[i].r = read();//离线操作
    q = read();
    dl = 1,dr = n;
    while(dl <= dr)//二分解决
    {
        mi = (dl + dr) >> 1;
        if(check(mi)) dl = mi+1,ans = mi;
        else dr = mi-1;
    }
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2018-06-17 01:02  CaptainLi  阅读(232)  评论(0编辑  收藏  举报