【HAOI2016】地图

rin在一个拥有多个建筑和双向街道的都市中,想要了解在特定建筑,当所有从市中心到该建筑的街道被堵时,能品尝到的拉面种类。题目涉及奇偶性查询、油腻程度限制以及数据结构与算法的应用,如仙人掌图、圆方树、DFS序、莫队算法和分块处理。

时间限制 : 1000 MS 空间限制 : 265536 KB

问题描述

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

输入格式

第一行两个正整数n,m,含义如题所示第二行一共N个正整数,第i个数Ai表示第i个建筑物出售的拉面的油腻程度。
接下来M行,每行两个正整数x,y,表示在建筑物x,y之间有一条双向通行的街道。数据保证1<=x< y<=N
接下来一行一个正整数Q,表示询问个数。
接下来Q行每行三个非负整数ty,x,y,x表示询问的建筑物编号,y表示油腻程度的限制,ty=0时表示询问偶数,ty=1表示询问奇数。
N<=100000,M<=150000,Q<=100000,Ai<=10^6
提示:请注意数据范围中的<=,特殊条件中提到的y均为询问中的y,对于所有的数据,有y<=10^6

输出格式

一共Q行,对于每个询问输出一个答案。

样例输入

10 12
1 10 4 5 2 10 1 8 4 8
1 2
1 3
1 4
2 5
4 6
4 7
7 8
2 9
8 10
1 6
8 10
4 7
10
0 3 9
1 7 6
0 5 2
1 10 9
0 5 7
1 7 4
0 7 3
1 2 7
0 3 4
0 3 8

样例输出

0
1
0
1
0
1
0
2
0
0

题解

显然,原图是一棵以1号点为根的仙人掌(背后一凉,以为会出现可怕的操作),每次要查询的即为子仙人掌的各种数据(如果不熟悉仙人掌,可自行百度一下)。这样就很水了,我们只需要按照处理仙人掌的套路——建立圆方树,就可以把仙人掌上的问题转化到树上。因此查询子仙人掌就变为了查询圆方树的子树。对于处理子树信息,我们首选用DFS序解决,这样又把树上的问题转化到了序列之上。
经过一系列转化之后,原题变为求一段区间中出现奇数或偶数次且小于等于某数的数字的个数。求区间中数字出现次数的题目,优先考虑使用莫队算法解决。然而本题还有小于等于某数的约束条件,套个线段树或树状数组?肯定不行,莫队算法算法常常只能支持O(1)O(1)修改操作。我们发现,对序列的维护、修改是套在莫队算法中一起执行的,然而查询操作却是与莫队并行的(两操作的复杂度用加号连接),也就是说修改只能O(1)O(1),但查询暴力一点O(logn)O(logn)甚至O(n)O(n)都可以。这样的数据结构很容易想到分块,因此我们可以对“油腻度”分块,再用莫队算法维护。
还有一个细节需要注意,虽然油腻度不超过一百万,但数据接近极限时分块还是会被卡,因此要对油腻度离散化……

代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int maxn=2e5+5;
int a,b,n,m,q,vt,scc,tot,maxx,f[maxn],dfn[maxn],low[maxn],val[maxn],Hash[maxn];
int id[maxn],out[maxn],pre[maxn];
int l=1,r,sz1,bl1[maxn],ans[maxn];
int sz2,odd[1005],even[1005],bl2[maxn*10],cnt[maxn*10];
bool vis[maxn];
vector<int> G[maxn],map[maxn];
struct node
{
    int tl,tr,tx,ty,id;
}qu[maxn];
char buf[1<<20],*head,*tail;
#define GC (head==tail&&(tail=(head=buf)+fread(buf,1,1000000,stdin),head==tail)?0:*head++)
inline int input()
{
    char t=GC;int x=0;
    while(t<48||t>57) t=GC;
    for(;t>=48&&t<=57;t=GC) x=x*10+t-48;
    return x;
}
bool cmp(node x,node y)
{
    return bl1[x.tl]==bl1[y.tl]?x.tr<y.tr:bl1[x.tl]<bl1[y.tl];
}
void get_scc(int x,int y)
{
    scc++;
    while(y!=x)
    {
        map[scc].push_back(y),map[y].push_back(scc);
        y=f[y];
    }
    map[scc].push_back(x),map[x].push_back(scc);
}
void Tarjan(int x)
{
    dfn[x]=low[x]=++vt;
    for(int i=0;i<G[x].size();i++)
    {
        int y=G[x][i];
        if(y==f[x]) continue;
        if(!dfn[y]) f[y]=x,Tarjan(y),low[x]=min(low[x],low[y]);
        else low[x]=min(low[x],dfn[y]);
        if(low[y]>dfn[x]) map[x].push_back(y),map[y].push_back(x);
    }
    for(int i=0;i<G[x].size();i++)
    {
        int y=G[x][i];
        if(x!=f[y]&&dfn[x]<dfn[y]) get_scc(x,y);
    }
}
void DFS(int x,int fa)
{
    id[x]=++tot,pre[tot]=x,vis[x]=true;
    for(int i=0;i<map[x].size();i++)
    {
        int y=map[x][i];
        if(y==fa||vis[y]) continue;
        DFS(y,x);
    }
    out[x]=tot;
}
void modify(int x,int y)
{
    x=pre[x];
    if(x>n) return;
    x=val[x];
    if(~y)
    {
        if(cnt[x]&1) odd[bl2[x]]--,even[bl2[x]]++;
        else
        {
            if(cnt[x]) even[bl2[x]]--;
            odd[bl2[x]]++;
        }
    }
    else
    {
        if(!(cnt[x]&1)) even[bl2[x]]--,odd[bl2[x]]++;
        else
        {
            if(cnt[x]>1) even[bl2[x]]++;
            odd[bl2[x]]--;
        }
    }
    cnt[x]+=y;
}
int get_ans(int x,int y)
{
    int sum=0;
    for(int i=1;i<bl2[x];i++) sum+=y?odd[i]:even[i];
    for(int i=(bl2[x]-1)*sz2+1;i<=x;i++)
        if(y==(cnt[i]&1)&&cnt[i]) sum++;
    return sum;
}
int main()
{
    n=input(),m=input();
    for(int i=1;i<=n;i++) Hash[i]=val[i]=input();
    sort(Hash+1,Hash+1+n),maxx=unique(Hash+1,Hash+1+n)-Hash-1;
    for(int i=1;i<=n;i++) val[i]=lower_bound(Hash+1,Hash+1+maxx,val[i])-Hash;
    for(int i=1;i<=m;i++)
    {
        a=input(),b=input();
        G[a].push_back(b),G[b].push_back(a);
    }
    scc=n,Tarjan(1),DFS(1,0);
    sz1=sqrt(scc),sz2=sqrt(maxx),q=input();
    for(int i=1;i<=scc;i++) bl1[i]=(i-1)/sz1+1;
    for(int i=1;i<=maxx;i++) bl2[i]=(i-1)/sz2+1;
    for(int i=1;i<=q;i++)
    {
        qu[i].ty=input(),a=input(),qu[i].tx=input();
        qu[i].tx=upper_bound(Hash+1,Hash+1+maxx,qu[i].tx)-Hash-1;
        qu[i].tl=id[a],qu[i].tr=out[a],qu[i].id=i;
    }
    sort(qu+1,qu+1+q,cmp);
    for(int i=1;i<=q;i++)
    {
        while(r<qu[i].tr) modify(r+1,1),r++;
        while(l>qu[i].tl) modify(l-1,1),l--;
        while(r>qu[i].tr) modify(r,-1),r--;
        while(l<qu[i].tl) modify(l,-1),l++;
        ans[qu[i].id]=get_ans(qu[i].tx,qu[i].ty);
    }
    for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值