HDU 4358 Boring counting(树的遍历+树状数组+离散化+离线处理)
分析:
首先读入所有的节点权重存在nodes[n]中,然后读入n-1条边,1为根节点.把整个图做成一个邻接表的形式.用vector<int> g[n],其中g[i]表示与i邻接的点都有哪些.然后对1号节点执行中序遍历.
首先本题是对整棵子树的查询,其实我们可以对该树执行一次后序遍历,把我们访问到的节点编号一一保存到nodes[i].index=j中 (i是这个节点原来的编号,j是表示我们中序遍历这棵树时,访问该点时是第j个访问的).
现在每个根节点的查询其实就正好对应了一个连续的区间查询.(想想是不是)并把每个根节点所对应的区间L[i]=j1和R[i]=j2记下来. (j1和j2是节点新的编号,是在执行初始i节点的后序遍历后,所有节点重新编号后,查询的区间是[j1,j2])
后序遍历后,给所有节点的权重重新编号后存在了a[i]中,我们如果要查询初始x节点的子树,只需要查询a[i]数组内在区间[L[x],R[x]]中符合要求的值有多少个.
接下来我们首先把a[i]的值重新映射集中(因为a[i]最大可能10亿,但却只有10W个a[i]值)到数组b[i]中.
离线处理每个查询,把每个查询按L从小到大排序.
预处理:接下来我们建一棵树状数组A[n],然后从1到n扫描b[i]数组,如果对于b[i]=x的值是正好第k次出现了,我们就add(i,1),且我们需要找到b[i]值正好出现了第k+1次的位置y,执行add(y,-1).扫描完b[n]之后,我们保证sum(R)的值就是对所有区间[1,R]的查询结果.
接下来我们需要查询所有[2,R]区间的结果,我们首先要消除b[1]对后续数列产生的影响,我们找到b[1]值第k次出现的位置y1和第k+1次出现的位置y2和第k+2次出现的位置y3(其实就是上一次针对b[1]的值执行add(,1)和add(,-1)的位置),所以我们执行add(y1,-1)和add(y2,2)和add(y3,-1). 消除影响后,b[1]就好像从来没有出现过一样.
完成上面那步,我们保证sum(R)的值就是对所有区间[2,R]的查询结果.
当我们查询完sum(R)表示区间[i,R]的查询结果后,我们需要找到当前d[i]值从i位置开始第k次出现的位置y1(包括i,且d[i]就算是第一次出现的位置),和d[i]值第k+1次出现的位置y2,和d[i]值第k+2次出现的位置y3,执行add(y1,-1) 和 add(y2,2) 和add(y3,-1)
原始代码:超时
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN =100000+100;
const int MAXM =100000+100;
struct node
{
int v;
int index;
bool operator <(const node&b)const
{
return v<b.v;
}
}nodes[MAXN],a[MAXN];//初始树的权值
struct command
{
int l,r;
int index;
bool operator <(const command&b)const
{
return l<b.l;
}
}coms[MAXM];//查询
int L[MAXN],R[MAXN],cnt;
int b[MAXN];
int ans[MAXM];
int cur[MAXN];//cur[i]表示b[j]=i值当前处理阶段第k次出现的位置
int first[2*MAXN],next[2*MAXN],u[2*MAXN],v[2*MAXN];
void add(int i,int j,int num)
{
u[num]=i;
v[num]=j;
next[num]=first[u[num]];
first[i]=num;
}
int dfs(int fa,int a)//后序访问以u为根节点(fa是u的父节点)的树,并返回该树的最最左下角的点的新编号
{
int min_num=cnt;
for(int e=first[a];e!=-1;e=next[e])if(v[e]!=fa)
{
dfs(a,v[e]);
}
L[a]=min_num;
R[a]=cnt;
nodes[a].index=cnt++;
return L[a];
}
struct HASHMAP
{
int head[MAXN];
int next[MAXN];//把所有相同的b[i]值从前到后串联起来
void init()
{
memset(head,-1,sizeof(head));
}
int insert(int i,int v)
{
int num=1;//表示插入的这个b[i]是值为v的第num次出现了
next[i]=-1;
if(head[v]==-1)
{
head[v]=i;
}
else
{
int j=head[v];
num++;
while(next[j]!=-1)
{
j=next[j];
num++;
}
next[j]=i;
}
return num;
}
int find(int i)//找到b[i]值相同的后继元素的下标
{
if(i==-1)
return -1;
return next[i];
}
}hm;
int c[MAXN];
int lowbit(int x)
{
return x&(-x);
}
int sum(int x)
{
int res=0;
while(x)
{
res +=c[x];
x-=lowbit(x);
}
return res;
}
void add(int x,int v)
{
while(x<MAXN)
{
c[x]+=v;
x+=lowbit(x);
}
}
int main()
{
int T,kase=1;
scanf("%d",&T);
while(T--)
{
printf("Case #%d:\n",kase++);
int n,K;
scanf("%d%d",&n,&K);
for(int i=1;i<=n;i++)//读权重
{
scanf("%d",&nodes[i].v);
}
memset(first,-1,sizeof(first));
for(int i=1;i<n;i++)//读边
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v,i*2-1);
add(v,u,i*2);
}
cnt=1;//记录当前递归到了第几个点
dfs(-1,1);
for(int i=1;i<=n;i++)
{
a[nodes[i].index].v = nodes[i].v;
a[nodes[i].index].index = nodes[i].index;
}
/*
for(int i=1;i<=n;i++)
{
printf("%d %d %d\n",a[nodes[i].index],L[i],R[i]);
}
*/
sort(a+1,a+n+1);
int max_num=1;
b[a[1].index]=1;
for(int i=2;i<=n;i++)
{
if(a[i].v==a[i-1].v)
b[a[i].index]=b[a[i-1].index];
else
b[a[i].index]=++max_num;
}
hm.init();
memset(cur,-1,sizeof(cur));
for(int i=1;i<=n;i++)
{
int num = hm.insert(i,b[i]);
if(num==K)
cur[b[i]]=i;//b[i]值第K次出现的位置是i
}
/*
for(int i=1;i<=n;i++)
printf("%d ",b[i]);
printf("\n");
for(int i=1;i<=n;i++)
{
printf("%d %d\n",L[i],R[i]);
}
printf("***************\n");
*/
int Q;
scanf("%d",&Q);
for(int i=1;i<=Q;i++)
{
int root;
scanf("%d",&root);
coms[i].l=L[root];
coms[i].r=R[root];
coms[i].index=i;
}
sort(coms+1,coms+Q+1);
for(int i=1;i<=n;i++)//预处理
{
int y1=cur[i];
int y2=hm.find(y1);
if(y1>-1)
{
add(y1,1);
if(y2>-1)
add(y2,-1);
}
}
int j=1;//表示当前处理排序后的第j条命令
for(int i=1;i<=n;i++)
{
while(coms[j].l==i)
{
ans[coms[j].index] = sum(coms[j].r);
j++;//别忘了
}
if(j>Q)
break;
int y1=cur[b[i]];
int y2=hm.find(y1);
int y3=hm.find(y2);
if(y1>0)
{
add(y1,-1);
if(y2>0)
{
add(y2,2);
if(y3>0)
{
add(y3,-1);
}
}
}
cur[b[i]]=y2;//这一句别忘了,更新d[i]下一个第k次出现的位置
}
for(int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
}
return 0;
}
现在换另一种做法,前面几步都一样,知道预处理那里,现在不预处理了.而是将所有询问按照R从小到大排序(如果R相同,则按L从小到大排序).
我们从1到n读入每一个b[i]的值,并且用一个vector<int> vec[MAXN]记录vec[b[i]][k]=j表示b[i]值已经出现了k次且第k次出现在j位置.
如果当前size = vec[b[i]].size()-1正好等于k(因为vec[x][0]=0,顶掉了这个0位,所以多存了一个0),那么执行add( vec[b[i]][size-k]+1,1 ); 和add( vec[b[i]][size-k+1]+1,-1 );
此时我们保证sum(L)就是所有区间[L,i]的查询值(其中L是可变的,但是i是固定的也就是说只有当前查询存在R==i时才查询,如果没有R==i,就继续读入下一个。)
如果当前size = vec[b[i]].size()-1正好大于k(因为vec[x][0]=0,顶掉了这个0位,所以多存了一个0),那么执行add( vec[b[i]][size-k-1]+1,-1 ); 和add( vec[b[i]][size-k]+1,2 ); 和 add( vec[b[i]][size-k+1]+1,-1 );
(自己在数轴上画图验证一下看看是不是)
1.下次如果需要离散化的数据,尽量先进行离散化在做其他操作。
2.由于没有初始化c数组,导致WA1小时,真是郁闷。以后全局变量和数组都在最前面统一初始化。
3.如果栈可能溢出,记得栈中的变量尽量少就不会溢出,否则就用自己手写的栈。
AC代码:765ms。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
//HDU开栈外挂,本代码不用也可以AC
#pragma comment(linker, "/STACK:102400000,102400000")
const int MAXN =100000+5000;
const int MAXM =100000+5000;
vector<int> vec[MAXN];
struct node
{
int v;
int index;
bool operator <(const node&b)const
{
return v<b.v;
}
} nodes[MAXN],a[MAXN]; //初始树的权值
struct command
{
int l,r;
int index;
bool operator <(const command&b)const
{
return r<b.r ;
}
} coms[MAXM]; //查询
int L[MAXN],R[MAXN],cnt;
int b[MAXN];
int ans[MAXM];
int cur[MAXN];//cur[i]表示b[j]=i值当前处理阶段第k次出现的位置
int first[2*MAXN],next[2*MAXN],u[2*MAXN],v[2*MAXN];
void add(int i,int j,int num)
{
u[num]=i;
v[num]=j;
next[num]=first[u[num]];
first[i]=num;
}
void dfs(int fa,int a)//后序访问以u为根节点(fa是u的父节点)的树,并返回该树的最最左下角的点的新编号
{
L[a]=cnt;
for(int e=first[a]; e!=-1; e=next[e])if(v[e]!=fa)
{
dfs(a,v[e]);
}
R[a]=cnt;
nodes[a].index=cnt++;
}
int c[MAXN];
int lowbit(int x)
{
return x&(-x);
}
int sum(int x)
{
int res=0;
while(x)
{
res +=c[x];
x-=lowbit(x);
}
return res;
}
void add(int x,int v)
{
while(x<MAXN)
{
c[x]+=v;
x+=lowbit(x);
}
}
int main()
{
int T;
scanf("%d",&T);
for(int kase=1; kase<=T; kase++)
{
memset(first,-1,sizeof(first));
memset(c,0,sizeof(c));
cnt=1;//记录当前递归到了第几个点
int n,K;
scanf("%d%d",&n,&K);
for(int i=1; i<=n; i++) //读权重
{
scanf("%d",&nodes[i].v);
}
for(int i=1; i<n; i++) //读边
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v,i*2-1);
add(v,u,i*2);
}
dfs(-1,1);
for(int i=1; i<=n; i++)
{
a[nodes[i].index].v = nodes[i].v;
a[nodes[i].index].index = nodes[i].index;
}
sort(a+1,a+n+1);
int max_num=1;
b[a[1].index]=1;
for(int i=2; i<=n; i++)
{
if(a[i].v==a[i-1].v)
b[a[i].index]=b[a[i-1].index];
else
b[a[i].index]=++max_num;
}
int Q;
scanf("%d",&Q);
for(int i=1; i<=Q; i++)
{
int root;
scanf("%d",&root);
coms[i].l=L[root];
coms[i].r=R[root];
coms[i].index=i;
}
sort(coms+1,coms+Q+1);
for(int i=1; i<=n; i++)
{
vec[i].clear();
vec[i].push_back(0);
}
int j=1;//表示当前处理排序后的第j条命令
for(int i=1; i<=n; i++)
{
vec[b[i]].push_back(i);
int size = vec[b[i]].size()-1;
if(size>=K)
{
if(size==K)
{
add( vec[b[i]][size-K]+1,1 );
add( vec[b[i]][size-K+1]+1,-1 );
}
else if(size>K)
{
add( vec[b[i]][size-K-1]+1,-1 );
add( vec[b[i]][size-K]+1,2 );
add( vec[b[i]][size-K+1]+1,-1 );
}
}
while(coms[j].r==i && j<=Q)
{
ans[coms[j].index] = sum( coms[j].l );
j++;
}
if(j>Q)
break;
}
printf("Case #%d:\n",kase);
for(int i=1; i<=Q; i++)
printf("%d\n",ans[i]);
if(kase<T)
printf("\n");
}
return 0;
}