Count---学习报告

题目出处:Codeforces-375D
题面 】:
有一个大小为n且以1为根的树,树上每个点都有对应的颜色ci。现给出m次询问v, k,问以v为根的子树中有多少种颜色至少出现了k次。

输入格式
第一行两个数n,m表示树的大小以及询问的次数。
第二行n个数表示树上每个结点的颜色。
接下来的n-1行,每行两个数a, b表示树上的边。
接下来m行,每行两个数v, k表示询问。

输出格式
m行,每行一个数表示第i次询问的答案。

样例输入1
8 5
1 2 2 3 3 2 3 3
1 2
1 5
2 3
2 4
5 6
5 7
5 8
1 2
1 3
1 4
2 3
5 3

样例输出1
2
2
1
0
1

样例输入2
4 1
1 2 3 4
1 2
2 3
3 4
1 1
样例输出2
4

数据范围
2≤n≤100000
1≤m≤100000
1≤ci≤100000
1≤a, b≤n, a≠b
1≤v≤n, 1≤k≤100000
对于其中30%的数据保证n,m≤100且ci≤n
对于其中60%的数据保证n≤5000


考试历程】:立马想到暴力算法,设f[i][j]表示以i为根结点的子树中颜色为j的节点数量,然后树形DP由儿子传给爸爸就可以得到每一个点子树颜色信息,最后对于每一个询问暴力查询即可.
时间复杂度O(n2m*n)=O(n3m),数组只能开到5000 * 5000,然后就只能得到60分。
随后想到子树查询便想到了dfs序转换为区间问题,然后我想到了莫队貌似可做,然后就敲了一下。
emmmmm,ZZ的我不懂得变通,原来的莫队题目k是定制,这次是变了的,然后我还在用老方法,写到一半感觉又写不下去了,然后直接重敲,瞎搞一通,只拿了30分…

正解】:相信自己,正解就是莫队(或者分块,至于什么用启发式合并做的大佬emmm),其实对于每一个询问k,我们只需维护一个树状数组,下标表示颜色数量,值代表有多少颜色达到了k个,动态去维护一下即可。
code

#include<bits/stdc++.h>
using namespace std;
struct fuk
{
	int x,next;
}a[200011];
struct fuc
{
	int id,l,r,k;
}q[100011];
int top=0,cnt=0,first[100011],n,m,blo;
int dfn[100011],pos[100011],size[100011],to[100011];
int c[100011],b[100011],v[100011],t[100011],s,ans[100011];
void add(int x,int to)
{
	top++;
	a[top].x=to;
	a[top].next=first[x];
	first[x]=top;
}
void dfs(int x,int fa)
{
	dfn[x]=++cnt;
	pos[cnt]=x;
	size[x]=1;
	for(int i=first[x];i;i=a[i].next)
	{
		int y=a[i].x;
		if(y==fa) continue;
		dfs(y,x); 
		size[x]+=size[y];
	}
} 
bool mycmp(fuc a,fuc b)
{
	if(a.l/blo!=b.l/blo)
	  return a.l<b.l;
	else if((a.l/blo)&1)
	  return a.r<b.r;
	else return a.r>b.r;
}
void change(int x,int val)
{
  for(;x<=100001;x+=x&-x)
    c[x]+=val;	
} 
int ask(int x)
{
	int ans=0;
	for(;x;x-=x&-x)
	  ans+=c[x];
	return ans;
}
void insert(int x)
{
  if(x==0) return;
  to[b[x]]++;
  change(to[b[x]],-1);	//由于树状数组不能有下标为0,我们整体都+1
  change(to[b[x]]+1,1);
}
void remove(int x)
{
    if(x==0) return;
	to[b[x]]--;
	change(to[b[x]]+2,-1);
	change(to[b[x]]+1,1);
}
int main()
{ 
 freopen("count.in","r",stdin);
 freopen("count.out","w",stdout); 	 
 scanf("%d%d",&n,&m);
 blo=sqrt(n);
 for(int i=1;i<=n;i++)
   scanf("%d",&v[i]),t[i]=v[i];
 sort(t+1,t+n+1);
 s=unique(t+1,t+n+1)-(t+1);
 for(int i=1;i<=n;i++)
   v[i]=lower_bound(t+1,t+s+1,v[i])-t;
 for(int i=1;i<n;i++)
 {
 	int x,y;
 	scanf("%d%d",&x,&y);
 	add(x,y); add(y,x);
 }
 dfs(1,0);
 for(int i=1;i<=m;i++)
 {
 	int l,r,k;
 	scanf("%d%d",&l,&k);
 	q[i].id=i; q[i].l=dfn[l]; q[i].k=k; q[i].r=dfn[l]+size[l]-1; 
 }
 sort(q+1,q+m+1,mycmp);
 int l=0,r=0;
 for(int i=1;i<=n;i++)
   b[i]=v[pos[i]];
 for(int i=1;i<=m;i++)
 {  
 	int ql=q[i].l,qr=q[i].r,k=q[i].k;
    while(l>ql) insert(--l);
    while(r<qr) insert(++r);
	while(l<ql) remove(l++);
 	while(r>qr) remove(r--);
 	ans[q[i].id]=ask(100001)-ask(k);
 }
 for(int i=1;i<=m;i++)
   printf("%d\n",ans[i]);
}

这里提醒一下大家如果要用树状数组,一定要注意下标和0。然后其实如果你用以上的代码将莫队while操作的第一项和第二项互换,你会发现TLE了。
为什么?假设我们将询问排序后第一个问题区间为[3,3],你的l为1,然后先执行remove操作导致原本就为0的to[b[i]]又-1,其实本来我们应该先把这一个节点加入考虑区间中先+1即r的增加导致目前区间变为[1,3]后再由l的增加来减除[1,2]区间带来的影响;不然直接这样下标就又变成-1,树状数组直接GG了,一定要注意

呵呵我不会说有更快的方法的【谁叫我一开始想到了树状数组】
code

#include<bits/stdc++.h>
using namespace std;
struct fuk
{
	int x,next;
}a[200011];
struct fuc
{
	int id,l,r,k;
}q[100011];
int top=0,cnt=0,first[100011],n,m,blo;
int dfn[100011],pos[100011],size[100011],to[100011];
int c[100011],b[100011],v[100011],t[100011],s,ans[100011],sum[100011];
void add(int x,int to)
{
	top++;
	a[top].x=to;
	a[top].next=first[x];
	first[x]=top;
}
void dfs(int x,int fa)
{
	dfn[x]=++cnt;
	pos[cnt]=x;
	size[x]=1;
	for(int i=first[x];i;i=a[i].next)
	{
		int y=a[i].x;
		if(y==fa) continue;
		dfs(y,x); 
		size[x]+=size[y];
	}
} 
bool mycmp(fuc a,fuc b)
{
	if(a.l/blo!=b.l/blo)
	  return a.l<b.l;
	else if((a.l/blo)&1)
	  return a.r<b.r;
	else return a.r>b.r;
}
void insert(int x)
{
  if(x==0) return;
  to[b[x]]++;
  sum[to[b[x]]]++;
}
void remove(int x)
{
    if(x==0) return;
	to[b[x]]--;
	sum[to[b[x]]+1]--;
}
int main()
{ 
 freopen("count.in","r",stdin);
 freopen("count.out","w",stdout); 	 
 scanf("%d%d",&n,&m);
 blo=sqrt(n);
 for(int i=1;i<=n;i++)
   scanf("%d",&v[i]),t[i]=v[i];
 sort(t+1,t+n+1);
 s=unique(t+1,t+n+1)-(t+1);
 for(int i=1;i<=n;i++)
   v[i]=lower_bound(t+1,t+s+1,v[i])-t;
 for(int i=1;i<n;i++)
 {
 	int x,y;
 	scanf("%d%d",&x,&y);
 	add(x,y); add(y,x);
 }
 dfs(1,0);
 for(int i=1;i<=m;i++)
 {
 	int l,r,k;
 	scanf("%d%d",&l,&k);
 	q[i].id=i; q[i].l=dfn[l]; q[i].k=k; q[i].r=dfn[l]+size[l]-1; 
 }
 sort(q+1,q+m+1,mycmp);
 int l=0,r=0;
 for(int i=1;i<=n;i++)
   b[i]=v[pos[i]];
 for(int i=1;i<=m;i++)
 {  
 	int ql=q[i].l,qr=q[i].r,k=q[i].k;
    while(l>ql) insert(--l);
    while(r<qr) insert(++r);
	while(l<ql) remove(l++);
 	while(r>qr) remove(r--);
 	ans[q[i].id]=sum[k];
 }
 for(int i=1;i<=m;i++)
   printf("%d\n",ans[i]);
}

sum数组下标和上面的树状数组含义相同,表示颜色数为k的颜色种类。
这里的sum数组运用的就很巧妙,因为一个颜色的数量假设由k1增加到k2,那么很明显数量为k1—k2的所有颜色数量都会+1,而一个颜色的减少只会让原先的颜色数表示的sum[]-1,那么对于每一个询问区间我们考虑完了所有的颜色,那么sum[k]就是我们的答案【因为一个颜色在该区间的数量>=k,必定都会使sum[k]这个数组+1】,这就是巧妙之处。

总结:要提升思想的深度,不局限于做过的题目,懂得举一反三是很重要的,要加深对算法的理解,灵活运用,要多刷题多思考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值