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; }