jzoj5629 【NOI2018模拟4.4】Map (业界毒瘤仙人掌缩环,线段树合并)

86 篇文章 0 订阅
28 篇文章 0 订阅

Description

Rin是个特别好动的少女。
一天Rin来到了一个遥远的都市。这个都市有N个建筑,编号从1到N,其中市中心编号为1,这个都市有M条双向通行的街道,每条街道连接着两个不同的建筑,其中某些街道首尾相连连接成了一个环。Rin通过长时间的走访,已经清楚了这个都市的两个特点:
从市中心出发可以到达所有的建筑物。
任意一条街道最多存在与一个简单环中。令Rin心花怒放的是,每个建筑物都会有拉面售卖。拉面有很多不同的种类,但对于Rin而言只有油腻程度的不同,因此我们把油腻程度相同的拉面看做同一种拉面。由于不同建筑物的拉面的油腻程度可能不同,我们用一个正整数来表示拉面的油腻程度。
要知道,拉面可是Rin的最爱,但是现在到了下班高峰期,都市的交通变得非常的堵塞。Rin只能通过没有被堵死的街道通行,去品尝所在建筑物的拉面。
现在Rin想知道,如果她正在编号为x的建筑物,那么在从市中心到x的所有简单路径经过的街道都被堵死的情况下,Rin可以品尝到的拉面中(注意没有出现的拉面是不能算在里面的):
油腻程度<=y且品尝次数为奇数次的拉面有多少种?
油腻程度<=y且品尝次数为偶数次的拉面有多少种?
n,m 1e5

???

看了个把小时并没有发现这是仙人掌。
先将一个环中,从1开始最先走到的的设为环顶(由仙人掌的定义显然唯一,1所在的环设1为根就行)。缩成仙人掌树
LL的高级缩法:求出low与dfn,按原顺序dfs,对于一条边(a,b),若dfn[a]<=low[b],那么a是一个割点,可以将下面的点fa设为到a.
若dfn[a]>low[b],那么b会有一条向上的边。 此时a,b应在同一个环中,所以fa[b]=fa[a]。

可以发现,查询x点的答案,就是x子树的答案。
考虑线段树合并。从下往上做,搞完这个点的询问就把它合并到父亲那里去。
合并的时间复杂度与两颗线段树公共点成线性。
但据说总时间是nlogn的,证明不详
不过观察代码可以发现。
我们不妨设想为将所有儿子都合并到第一个儿子上,当x,y都不为空时,就要继续往下做。 这时候y这个点相当于被废弃。而初始总点数不超过nlogn,因此可以发现往下做的次数不超过nlogn.因此可以认为时间复杂度就是O(nlogn) (可能会带较大常数,至少是2吧)

void merge(int &x,int y,int l,int r) {
    if (y==0) return;
    if (x==0) x=y; else {
        if (l==r) {
            int cs=T[x][1]+T[y][1];
            T[x][0]=T[x][1]=0; T[x][cs&1]=1;
            return;
        }
        merge(c[x][0],c[y][0],l,l+r>>1);
        merge(c[x][1],c[y][1],(l+r>>1)+1,r);
        update(x);
    }
}

因此复杂度为 O(nlogn) O ( n l o g n )
这一题就开权值线段树存一下,种类x的奇贡献,偶贡献。 合并时求和,注意叶子特判即可。

Code

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int M=1.5e5+10,N=1e5+10;

int to[M*2],nex[M*2],final[N],tot,d[N];
int qy[N][2],qnex[N],qfinal[N],loc[N],qtot,ans[N];
int fa[N];

int n,m,q,a[N];
void link(int x,int y) {
    to[++tot]=y,nex[tot]=final[x],final[x]=tot;
}
int low[N],dfn[N],stm;
void form(int x) {
    low[x]=dfn[x]=++stm;
    for (int i=final[x];i;i=nex[i]) {
        int y=to[i]; if (!dfn[y]) {
            form(y);
            low[x]=min(low[x],low[y]);
        } else low[x]=min(low[x],dfn[y]);
    }
}
void build(int x) {
    for (int i=final[x]; i; i=nex[i]) {
        int y=to[i]; if (dfn[y]>dfn[x] && fa[y]==0) {
            if (dfn[x]<=low[y]) fa[y]=x,d[x]++; else fa[y]=fa[x],d[fa[x]]++;
            build(y);
        }
    }
}
int qu,Q[N],L,R,T[2700000][2],c[2700000][2],fft,root[N],mx;
void update(int x) {
    T[x][0]=T[c[x][0]][0]+T[c[x][1]][0];
    T[x][1]=T[c[x][0]][1]+T[c[x][1]][1];
}
void query(int x,int l,int r,int qw,int qloc,int &sum) {
    if (!x || l>qloc) return;
    if (r<=qloc) {
        sum+=T[x][qw];
        return;
    }
    query(c[x][0],l,l+r>>1,qw,qloc,sum);
    query(c[x][1],(l+r>>1)+1,r,qw,qloc,sum);
}

void merge(int &x,int y,int l,int r) {
    if (y==0) return;
    if (x==0) x=y; else {
        if (l==r) {
            int cs=T[x][1]+T[y][1];
            T[x][0]=T[x][1]=0; T[x][cs&1]=1;
            return;
        }
        merge(c[x][0],c[y][0],l,l+r>>1);
        merge(c[x][1],c[y][1],(l+r>>1)+1,r);
        update(x);
    }
}
void change(int &x,int l,int r,int tg) {
    if (x==0) x=++fft;
    if (l==r) {
        if (T[x][0]+T[x][1]==0) T[x][1]=1;
        else T[x][0]^=1,T[x][1]^=1;
        return;
    }
    if (tg<=(l+r>>1)) change(c[x][0],l,l+r>>1,tg);
    else change(c[x][1],(l+r>>1)+1,r,tg);
    update(x);
}

int main() {
    freopen("map.in","r",stdin);
    freopen("map.out","w",stdout);
    cin>>n>>m;
    for (int i=1; i<=n; i++) scanf("%d",&a[i]),mx=max(a[i],mx);
    for (int i=1; i<=m; i++) {
        int x,y; scanf("%d %d",&x,&y); link(x,y),link(y,x);
    }
    cin>>qu;
    for (int i=1; i<=qu; i++) {
        int ty,x,y; scanf("%d %d %d",&ty,&x,&y);
        qy[++qtot][0]=ty,qy[qtot][1]=y;
        qnex[qtot]=qfinal[x]; qfinal[x]=qtot; loc[qtot]=i;
    }
    form(1);
    build(1);
    for (int i=1; i<=n; i++) if (d[i]==0) Q[++R]=i;
    while (L<R) { 
        int x=Q[++L];
        change(root[x],1,mx,a[x]);
        for (int i=qfinal[x];i;i=qnex[i]) {
            query(root[x],1,mx,qy[i][0],qy[i][1],ans[loc[i]]);
        }
        if (fa[x]) merge(root[fa[x]],root[x],1,mx);
        if (x && --d[fa[x]]==0) Q[++R]=fa[x];
    }
    for (int i=1; i<=qu; i++) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值