【树链剖分】【Hall定理】[JZOJ5824][BZOJ5404] Party

##Description
这里写图片描述
这里写图片描述
##Solution
我们发现颜色种类不多,只有1000种
因此我们可以用bitset来维护颜色集合。

我们将这棵树轻重链剖分,用线段树维护链的颜色集合(空间为O(N*1000/32)),若倍增的话空间要多乘上log。
当我们需要查询一条链时,由于没有修改,我们大可以直接记录每个点到所在重链顶的颜色集合,这样原本log^2的时间复杂度就变成log的了。

现在考虑我们求出了每个人的颜色集合后怎么做。
由于最终每个人带的特产数相等,答案可以写成t*c的形式

我们二分这个t(每个人带的特产数),然后将每个人拆成t个点,对应的向特产连边,要求必须完美匹配。
(用网络流的话就不用拆点直接改容量,判断是否满流即可)

然而我们有Hall定理
大概就是一个二分图有完美匹配必须满足左边任意选出若干个节点(设为K),跟这些节点有连边的右边节点的点数必须大于等于K

我们发现二分的这个t实际上就是将左边点集复制了t次,右边的连边是不变的。

因此我们2^c枚举左边的点集,答案就是 m i n ( F ( S ) ∣ S ∣ ) min({F(S)\over |S|}) min(SF(S)),其中S为我们选出的左边的点集,F为与这个点集有连边的右边点集合大小。

这个用我们刚才bitset直接求并,调用count函数即可。
复杂度是 O ( 1000 / 32 ∗ N log ⁡ n ) O(1000/32*N\log n) O(1000/32Nlogn)
##Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <bitset>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 300005
#define LL long long
using namespace std;
typedef bitset<1000> bt;
int n,m,q,m1,sz[N],dep[N],ft[N][20],fs[N],nt[N],dt[N],dfn[N],top[N],n1,w[N],t[2*N][2],dfw[N],d[N],ask[6],son[N],ans;
bt vl[N],bs[2*N],qs[6],now;
void link(int x,int y)
{
	nt[++m1]=fs[x];
	dt[fs[x]=m1]=y;
}
void dfs(int k)
{
	dep[k]=dep[ft[k][0]]+1;
	sz[k]=1;
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		dfs(p);
		sz[k]+=sz[p];
		son[k]=(sz[p]>sz[son[k]])?p:son[k];
	}
}
void build(int k,int l,int r)
{
	if(l==r) bs[k][w[dfw[l]]-1]=1;
	else
	{
		int mid=(l+r)>>1;
		t[k][0]=++n1,build(n1,l,mid);
		t[k][1]=++n1,build(n1,mid+1,r);
		bs[k]=bs[t[k][0]]|bs[t[k][1]];
	}
}
void make(int k)
{
	dfw[dfn[k]=++dfn[0]]=k;
	if(son[k])
	{
		top[son[k]]=top[k];
		vl[son[k]]=vl[son[k]]|vl[k];
		make(son[k]);
		for(int i=fs[k];i;i=nt[i])
		{
			int p=dt[i];
			if(p!=son[k])
			{
				top[p]=p;
				make(p);
			}
		}
	}
}
void find(int k,int l,int r,int x,int y)
{
	if(x>r||y<l||x>y) return; 
	if(x<=l&&r<=y) d[++d[0]]=k;
	else
	{
		int mid=(l+r)/2;
		find(t[k][0],l,mid,x,y),find(t[k][1],mid+1,r,x,y);
	}
}
void query(int i,int f)
{
	int k=ask[i];
	while(top[k]!=top[f])
	{
		qs[i]=qs[i]|vl[k];
		k=ft[top[k]][0];
	}
	d[0]=0;
	find(1,1,n,dfn[f],dfn[k]);
	fo(j,1,d[0]) qs[i]=qs[i]|bs[d[j]];
}
int lca(int x,int y)
{
	if(dep[y]<dep[x]) swap(x,y);
	for(int j=19;dep[y]>dep[x];)
	{
		while(j&&dep[ft[y][j]]<dep[x]) j--;
		y=ft[y][j];
	}
	for(int j=19;x!=y;)
	{
		while(j&&ft[y][j]==ft[x][j]) j--;
		x=ft[x][j],y=ft[y][j];
	}
	return x;
}
void get(int k,int m,int s)
{
	if(k>m) 
	{
		if(s) ans=min(ans,(int)now.count()/s);
	}
	else
	{
		get(k+1,m,s);
		bt ls=now;
		now=now|qs[k];
		get(k+1,m,s+1);
		now=ls;
	}
}
int main()
{
	freopen("party.in","r",stdin);
	freopen("party.out","w",stdout);
	cin>>n>>m>>q;
	n1=1;
	fo(i,2,n)
	{
		scanf("%d",&ft[i][0]);
		link(ft[i][0],i);
	}
	fo(i,1,n)
	{
		scanf("%d",&w[i]);
		vl[i][w[i]-1]=1;
	}
	dfs(1);
	top[1]=1,n1=1;
	make(1);
	build(1,1,n);
	fo(j,1,19) 
		fo(i,1,n) ft[i][j]=ft[ft[i][j-1]][j-1];
	fo(t,1,q)
	{
		int c,anc;
		scanf("%d",&c);
		fo(i,1,c) scanf("%d",&ask[i]);
		anc=ask[1];
		fo(i,2,c) anc=lca(anc,ask[i]);
		fo(i,1,c) qs[i].reset(),query(i,anc);
		ans=1000000;		
		get(1,c,0);
		printf("%d\n",ans*c);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值