2023.1.27 日寄

2023.1.27 日寄

一言

\(~~~~\) 我希望你们可以尽己所能,想方设法给自己挣到足够的钱,好去旅游,去无所事事,去思索世界的未来或过去,去看书、做梦或是在街头闲逛,让思考的鱼线深深沉入这条溪流中去。——伍尔夫《一间自己的房间》

理论复习内容:数据结构

「北大集训 2021」小明的树

题意

\(~~~~\) 一棵以 \(1\) 为根的有根树,按照一个 \(2\sim n\) 的排列的顺序点亮每个点。定义其美丽当且仅当所有点亮的点的子树内结点都没点亮,一颗美丽的树的权值为其点亮的点的连通块个数。求点亮过程中形成的所有美丽的树的权值和。同时 \(q\) 次询问,每次断开一条,连接另一条,每次修改后求答案。
\(~~~~\) \(1\leq n,q\leq 5\times 10^5\).

题解

\(~~~~\) 有一点点被骗了的感觉。

\(~~~~\) 用父子关系来刻画非常不方便,我们用边来刻画两种量。

\(~~~~\) 那么一棵树是美丽的事实上也就是没有点亮的点成一个连通块(因为 \(1\) 始终不会点亮),而连通块又可以用点减去边来刻画,所以我们发现美丽就是未点亮点数 \(-\) 白白边 \(=1\).

\(~~~~\) 然后注意到,权值事实上就是黑白边的数量,所以我们就可以去掉根的限制用边来刻画。

\(~~~~\) 那么对每条边就直接找到两个端点点亮的时间,都点亮之前其贡献白白边数量,一个点亮另一个没点亮贡献黑白边(权值)。由于最后肯定是美丽的,所以我们可以用最大值来记录权值和。线段树维护即可。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
ll n,m,N,ord[500005];
struct SegmentTree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson p<<1,l,mid
	#define rson p<<1|1,mid+1,r
	//n-1处的Maxn必然为0,所以最后统计的都是0部分的Val 
	ll Con[2000005],Maxn[2000005],Val[2000005];
	ll EdgeTag[2000005],ValTag[2000005];
	inline void pushUp(ll p)
	{
		Con[p]=Val[p]=0;
		Maxn[p]=max(Maxn[ls],Maxn[rs]);	
		if(Maxn[p]==Maxn[ls]) Con[p]+=Con[ls],Val[p]+=Val[ls];
		if(Maxn[p]==Maxn[rs]) Con[p]+=Con[rs],Val[p]+=Val[rs]; 
	}
	void AddEdge(ll p,ll k){Maxn[p]+=k;EdgeTag[p]+=k;}
	void AddVal(ll p,ll k){Val[p]+=Con[p]*k;ValTag[p]+=k;}
	void pushDown(ll p)
	{
		if(EdgeTag[p]) AddEdge(ls,EdgeTag[p]),AddEdge(rs,EdgeTag[p]),EdgeTag[p]=0;
		if(ValTag[p])  AddVal(ls,ValTag[p]),AddVal(rs,ValTag[p]),ValTag[p]=0;
	}
	void Build(ll p,ll l,ll r)
	{
		if(l==r){Maxn[p]=-(N-l);Con[p]=1;return;}
		ll mid=(l+r)>>1;
		Build(lson);Build(rson);
		pushUp(p);
	} 
	void ModifyEdge(ll p,ll l,ll r,ll lx,ll rx,ll val)
	{
		if(lx>rx||l>rx||r<lx) return;
		if(lx<=l&&r<=rx){AddEdge(p,val);return;}
		ll mid=(l+r)>>1; pushDown(p);
		if(lx<=mid) ModifyEdge(lson,lx,rx,val);
		if(mid<rx)  ModifyEdge(rson,lx,rx,val);
		pushUp(p);
	}
	void ModifyVal(ll p,ll l,ll r,ll lx,ll rx,ll val)
	{
		if(lx>rx||l>rx||r<lx) return;
		if(lx<=l&&r<=rx){AddVal(p,val);return;}
		ll mid=(l+r)>>1; pushDown(p);
		if(lx<=mid) ModifyVal(lson,lx,rx,val);
		if(mid<rx)  ModifyVal(rson,lx,rx,val);
		pushUp(p);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Seg;
struct Edge{
	ll u,v;
}E[500005];
void Update(ll u,ll v,ll k)
{
	if(ord[u]>ord[v]) swap(u,v);
	Seg.ModifyEdge(1,1,N,1,ord[u]-1,k);
	Seg.ModifyVal(1,1,N,ord[u],ord[v]-1,k);
}
int main() {
	read(n);read(m);N=n-1;
	for(ll i=1;i<n;i++) read(E[i].u),read(E[i].v);
	for(ll i=1,x;i<n;i++) read(x),ord[x]=i;ord[1]=n;
	Seg.Build(1,1,N);
	for(ll i=1;i<n;i++) Update(E[i].u,E[i].v,1);
	printf("%lld\n",Seg.Val[1]);
	for(ll i=1,u,v,x,y;i<=m;i++)
	{
		read(u);read(v);read(x);read(y);
		Update(u,v,-1); Update(x,y,1);
		printf("%lld\n",Seg.Val[1]);
	}
	return 0;
}
/*
生命的意义是时间的尺度决定的。
在只能活三个季的蚱蜢眼里,秋季就是生命的尽头,后面是地狱般的寒冬,活着定是无尽的痛苦。
在只能活一天的蜉蝣眼里,池塘之大就是彼之沧海,朝生暮死,不知晦朔,世间值得已全部看尽。
*/


「BalkanOI 2018 Day1」Election

题意

\(~~~~\) 仅由 C,T 组成的字符串。\(q\) 次询问,每次询问一个区间的字串删去最少多少字符可以使得任何前缀和后缀的 C 数量不小于 T 数量。
\(~~~~\) \(1\leq n,q\leq 5\times 10^5\).

题解

\(~~~~\) 很显然的贪心思路是对每个前缀和后缀把 C 看做 \(1\)T看作 \(-1\) ,然后扫前缀或后缀的时候能删就删。

\(~~~~\) 那么就相当于找到不交的前缀和后缀,使得两者的和最小,这些都是要删掉的。答案自然是这个和的相反数。

\(~~~~\) 这个限制不是很好满足,但这个值可以转化成整个序列和减去其最大子段和,那剩下部分就是满足要求的。

\(~~~~\) 所以我们只需要维护序列的最大子段和然后区间查找。

代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
char S[500005];
struct node{
	int L,R,S,D;
	node(){}
	node(int l,int r,int s,int d){L=l,R=r,S=s,D=d;}
};
node Merge(node a,node b){return node(max(a.L,a.S+b.L),max(b.R,b.S+a.R),a.S+b.S,max(max(a.D,b.D),a.R+b.L));}
struct SegmentTree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson p<<1,l,mid
	#define rson p<<1|1,mid+1,r
	int L[2000005],R[2000005],Sum[2000005],det[2000005];
	inline void pushUp(int p)
	{
		L[p]=max(max(L[ls],Sum[ls]+L[rs]),0);
		R[p]=max(max(R[rs],Sum[rs]+R[ls]),0);
		Sum[p]=Sum[ls]+Sum[rs];
		det[p]=max(max(det[ls],det[rs]),L[rs]+R[ls]);
	}
	void Build(int p,int l,int r)
	{
		if(l==r){Sum[p]=(S[l]=='C'?1:-1),L[p]=R[p]=det[p]=(S[l]=='C'?1:0);return;}
		int mid=(l+r)>>1;
		Build(lson); Build(rson);
		pushUp(p);
	}
	node query(int p,int l,int r,int lx,int rx)
	{
		if(lx<=l&&r<=rx) return node(L[p],R[p],Sum[p],det[p]);
		int mid=(l+r)>>1;
		if(lx<=mid&&mid<rx) return Merge(query(lson,lx,rx),query(rson,lx,rx));
		if(lx<=mid) return query(lson,lx,rx);
		return query(rson,lx,rx); 
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson 
}Seg;
int main() {
	int n,m;read(n);scanf("%s",S+1);
	Seg.Build(1,1,n);
	read(m);
	for(int i=1,l,r;i<=m;i++)
	{
		read(l);read(r);
		node res=Seg.query(1,1,n,l,r);
		printf("%d\n",res.D-res.S);
	}
	return 0;
}
/*
生命的意义是时间的尺度决定的。
在只能活三个季的蚱蜢眼里,秋季就是生命的尽头,后面是地狱般的寒冬,活着定是无尽的痛苦。
在只能活一天的蜉蝣眼里,池塘之大就是彼之沧海,朝生暮死,不知晦朔,世间值得已全部看尽。
TTCCTT
*/


「HNOI2011」括号修复

题意

\(~~~~\) 三种操作(推平,翻转,取反),询问一个括号串的子区间最小修改多少括号可以合法。保证询问有解。
\(~~~~\) \(1\leq n,q\leq 10^5\).

题解

\(~~~~\) 先考虑求答案我们需要些什么,那我们一定会把靠左的不匹配的右括号和靠右的不匹配的左括号各自改一半(向上取整)。

\(~~~~\) 那么套路地,记 (\(-1\))\(1\) ,就会发现我们要求一个串的最大前缀和和最小后缀和。类似于线段树的套路同时为了配合三种操作,还要额外维护最小前缀和,最大后缀和,区间和。因为有翻转那就只能放在平衡树上做了。具体可以看代码。

代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int n,m,rt;
struct node{
	int ch[2];
	int Siz,Val,Sum,Pri;
	int Pmin,Pmax,Smin,Smax;
	int TagInv,TagCov,TagSwa;
};
struct FHQ_Treap{
	#define ls tr[p].ch[0]
	#define rs tr[p].ch[1]
	int tot;
	node tr[2000005];
	FHQ_Treap(){tot=0;}
	int NewNode(int x)
	{
		int p=++tot;
		tr[p].Sum=tr[p].Val=x;
		if(x==1)  tr[p].Pmax=tr[p].Smax=x;
		if(x==-1) tr[p].Pmin=tr[p].Smin=x;
		tr[p].Siz=1; tr[p].Pri=rand();
		return p;
	}
	void pushUp(int p)
	{
		tr[p].Sum=tr[ls].Sum+tr[rs].Sum+tr[p].Val;
		tr[p].Siz=tr[ls].Siz+tr[rs].Siz+1;
		tr[p].Pmin=min(tr[ls].Pmin,tr[ls].Sum+tr[rs].Pmin+tr[p].Val);
		tr[p].Pmax=max(tr[ls].Pmax,tr[ls].Sum+tr[rs].Pmax+tr[p].Val);
		tr[p].Smin=min(tr[rs].Smin,tr[rs].Sum+tr[ls].Smin+tr[p].Val);
		tr[p].Smax=max(tr[rs].Smax,tr[rs].Sum+tr[ls].Smax+tr[p].Val);
	}
	void PushInv(int p)
	{
		tr[p].Val*=-1; tr[p].Sum*=-1;
		tr[p].Pmax*=-1; tr[p].Pmin*=-1;
		tr[p].Smax*=-1; tr[p].Smin*=-1;
		swap(tr[p].Pmax,tr[p].Pmin); swap(tr[p].Smax,tr[p].Smin);
		tr[p].TagInv^=-1; tr[p].TagCov*=-1;
	}
	void PushCov(int p,int k)
	{
		tr[p].Val=k; tr[p].Sum=k*tr[p].Siz;
		if(k==1)  tr[p].Pmax=tr[p].Smax=tr[p].Siz*k,tr[p].Pmin=tr[p].Smin=0;
		if(k==-1) tr[p].Pmax=tr[p].Smax=0,          tr[p].Pmin=tr[p].Smin=tr[p].Siz*k;
		tr[p].TagCov=k;
	}
	void PushSwa(int p)
	{
		swap(tr[p].Pmax,tr[p].Smax);
		swap(tr[p].Pmin,tr[p].Smin);
		swap(ls,rs);
		tr[p].TagSwa^=1;
	}
	void pushDown(int p)
	{
		if(tr[p].TagInv) ls?PushInv(ls):void(),rs?PushInv(rs):void(),tr[p].TagInv=0;
		if(tr[p].TagCov) ls?PushCov(ls,tr[p].TagCov):void(),rs?PushCov(rs,tr[p].TagCov):void(),tr[p].TagCov=0;
		if(tr[p].TagSwa) ls?PushSwa(ls):void(),rs?PushSwa(rs):void(),tr[p].TagSwa=0;
	}
	void Split(int p,int k,int &x,int &y)
	{
		if(!p){x=y=0;return;}
		pushDown(p);
		if(tr[ls].Siz+1<=k) x=p,Split(rs,k-tr[ls].Siz-1,tr[x].ch[1],y);
		else y=p,Split(ls,k,x,tr[y].ch[0]);
		pushUp(p);
	}
	int Merge(int x,int y)
	{
		if(!x||!y) return x|y;
		pushDown(x); pushDown(y);
		if(tr[x].Pri<tr[y].Pri) {tr[x].ch[1]=Merge(tr[x].ch[1],y);pushUp(x);return x;}
		else {tr[y].ch[0]=Merge(x,tr[y].ch[0]);pushUp(y);return y;}
	}
	void DEBUG(int p)
	{
		if(ls) DEBUG(ls);
		printf("%c",tr[p].Val==-1?'(':')');
		if(rs) DEBUG(rs);
	}
}FHQ;
char op[20];
char S[100005];
inline int Abs(int x){return x>=0?x:-x;}
int main() {
	int n,m;read(n);read(m);
	scanf("%s",S+1);
	for(int i=1;i<=n;i++) rt=FHQ.Merge(rt,FHQ.NewNode(S[i]=='('?-1:1));
	for(int qwq=1,x,y,z,l,r;qwq<=m;qwq++)
	{
		scanf("%s",op+1);read(l);read(r);
		if(op[1]=='R')
		{
			FHQ.Split(rt,r,y,z); FHQ.Split(y,l-1,x,y);
			scanf("%s",op+1);
			if(op[1]=='(') FHQ.PushCov(y,-1);
			else FHQ.PushCov(y,1);
			rt=FHQ.Merge(FHQ.Merge(x,y),z);
		}
		if(op[1]=='S')
		{
			FHQ.Split(rt,r,y,z); FHQ.Split(y,l-1,x,y);
			FHQ.PushSwa(y);
			rt=FHQ.Merge(FHQ.Merge(x,y),z);
		}
		if(op[1]=='I')
		{
			FHQ.Split(rt,r,y,z); FHQ.Split(y,l-1,x,y);
			FHQ.PushInv(y);
			rt=FHQ.Merge(FHQ.Merge(x,y),z);
		}
		if(op[1]=='Q')
		{
			FHQ.Split(rt,r,y,z); FHQ.Split(y,l-1,x,y);
			printf("%d\n",(FHQ.tr[y].Pmax+1)/2+(Abs(FHQ.tr[y].Smin)+1)/2);
			rt=FHQ.Merge(FHQ.Merge(x,y),z);
		}
//		FHQ.DEBUG(rt);puts("");
	}
	return 0;
}
/*
西风吹老洞庭波,一夜湘君白发多。
醉后不知天在水,满船清梦压星河。
*/

接水果

是个坑,并且是之前就没填的坑,那就继续不填吧


「Ynoi2010」Fusion tree/融合树

题意

\(~~~~\) \(n\) 个结点的树,初始有权值。有三种操作:将某个点相邻的点 \(+1\);将某个点减 \(v\) (保证不会出现负数);查询某个点相邻的点的异或和。
\(~~~~\) \(1\leq n,m\leq 5\times 10^5\).

题解

\(~~~~\) 想一想,为什么针对更多点的加法只做 \(+1\),并且让你查异或呢?

\(~~~~\) 在二进制下,\(+1\) 实际上就是将一段后缀 \(1\) 改成 \(0\),将最后一个 \(0\) 改成 \(1\)

\(~~~~\) 明白这点过后我们来看,维护异或不难想到用 01Trie 树,为了方便做 \(+1\) ,这次我们倒过来建树。那么维护 \(+1\) 就只需要相当于做异或的方法了。

\(~~~~\) 当然相邻这个限制很烦,但是在树上我们可以拆成维护统一子结点,单独维护父结点。因为只有一个父亲,显然这是可以维护的。

\(~~~~\) 那么单点减法就只需要暴力把这个点删掉再插回父亲的 Trie 里面就好了。

代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
template<typename T>void print(T x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
vector <int> G[500005];
int arr[500005],fa[500005];
int rt[500005],Val[500005];
struct Trie{
	#define ls ch[p][0]
	#define rs ch[p][1]
	int tot,dep[15000005],ch[15000005][2],res[15000005],Num[15000005];
	Trie(){tot=0;}
	int NewNode(int x)
	{
		tot++; dep[tot]=x;
		return tot;
	}
	void pushUp(int p)
	{
		Num[p]=res[p]=0;
		if(ls) Num[p]+=Num[ls],res[p]^=res[ls];
		if(rs)
		{
			Num[p]+=Num[rs],res[p]^=res[rs];
			if(Num[rs]&1) res[p]|=1<<dep[p];	
		}
	}
	void Insert(int p,int val)
	{
		if(dep[p]>=20) Num[p]++;
		else
		{
			int To=(val>>dep[p])&1;
			if(!ch[p][To]) ch[p][To]=NewNode(dep[p]+1);
			Insert(ch[p][To],val);
			pushUp(p);
		}
	}
	void erase(int p,int val)
	{
		if(dep[p]>=20) Num[p]--;
		else erase(ch[p][(val>>dep[p])&1],val),pushUp(p);
	}
	void Modify(int p)
	{
		swap(ls,rs);
		if(ls) Modify(ls);
		pushUp(p);
	}
	#undef ls
	#undef rs
}Trie;
void dfs(int u,int Fa)
{
	fa[u]=Fa;
	for(int i=0;i<G[u].size();i++){if(G[u][i]!=Fa) dfs(G[u][i],u);}
}
int main() {
//	freopen("4.in","r",stdin);
//	freopen("4.out","w",stdout);
	int n,m;read(n);read(m);
	for(int i=1,u,v;i<n;i++)
	{
		read(u);read(v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++) read(arr[i]),rt[i]=Trie.NewNode(0);
	for(int i=2;i<=n;i++) Trie.Insert(rt[fa[i]],arr[i]);
	for(int i=1,op,x,y;i<=m;i++)
	{
		read(op);read(x);
		if(op==1)
		{
			Val[x]++; Trie.Modify(rt[x]);
			if(x!=1)
			{
				if(fa[x]!=1) Trie.erase(rt[fa[fa[x]]],arr[fa[x]]+Val[fa[fa[x]]]);
				arr[fa[x]]++;
				if(fa[x]!=1) Trie.Insert(rt[fa[fa[x]]],arr[fa[x]]+Val[fa[fa[x]]]);
			}
		}
		if(op==2)
		{
			read(y);
			if(x!=1) Trie.erase(rt[fa[x]],arr[x]+Val[fa[x]]);
			arr[x]-=y;
			if(x!=1) Trie.Insert(rt[fa[x]],arr[x]+Val[fa[x]]);
		}
		if(op==3)
		{
			if(x!=1)
			{
				if(fa[x]!=1) print(Trie.res[rt[x]]^(arr[fa[x]]+Val[fa[fa[x]]]));
				else print(Trie.res[rt[x]]^arr[fa[x]]);
			}
			else print(Trie.res[rt[x]]);
			puts("");
		}
	}
//	cerr<<Trie.tot<<endl;
	return 0;
}
/*
西风吹老洞庭波,一夜湘君白发多。
醉后不知天在水,满船清梦压星河。
*/

「HNOI 2018 省队集训 Day 5」party

题意

\(~~~~\) 一个朝向根结点 \(1\)\(n\) 个结点的内向树。\(q\) 次询问,每次给定若干个关键点 (\(c\in[2,5]\) 个),让这些关键点移动到最近的点,同时可以收集路径上的颜色,使得每个点收集的颜色数相同,且颜色种类各不相同。求最多的颜色种类。(共 \(m\) 种颜色 )
\(~~~~\) \(2\leq n\leq 300000,1\leq m\leq 1000,0\leq q\leq 50000\).

题解

\(~~~~\) 个人觉得绝妙,虽然被剧透的话估计就没什么好做的了。

\(~~~~\) 分成几个部分,显然关键点最后是聚拢到它们的LCA处的,所以我们需要处理出每个点到LCA可以搜集哪些颜色。那可以树剖预处理每个点到它的 Top 所能搜集颜色的 Bitset,同时线段树维护最后一段上的 Bitset,虽然空间看起来很大但其实没问题。复杂度:建立 \(\mathcal{O(n \frac{m}{\omega} \log n)}\),询问单次 \(\mathcal{O(c \frac{m}{\omega} \log n)}\)

\(~~~~\) 然后处理完了过后怎么求答案才是最关键的。可以发现最后的答案一定是一个 \(c\times x\) 状物。而且 \(x\) 是可以二分的,那我们来考虑怎么判定合法的 \(x\)。我们建立 \(x\) 组点,每个点连向其可以搜集的颜色,那我们只需要求每个点能不能都匹配一个颜色,但是复杂度升天了。

\(~~~~\) 注意到这其实是一个完美匹配的问题,那我们往 Hall 定理 去思考。我们发现若一个小的关键点的子集 \(S\) 若其中的点可以搜集的颜色集合为 \(f(S)\) 。我们注意到由定义,\(f(S)\) 中一定每个点都和 \(S\) 中的点都连了边,那我们肯定有 \(|S|\times x\leq |f(S)|\) ,而这样就使得全集肯定与另一边相连了。对于 Hall 定理中任意 \(k\) 个的限制,我们也一定可以通过删去上面的一些点来满足,所以 \(x\leq \frac{|f(S)|}{|S|}\)。而最后要取的 \(x\) 就是所有 \(S\) 求出来的这个值的最小值。

\(~~~~\) 上面的 \(f(S)\) 你随便用直接求和递推都可以,复杂度都不会爆。单次复杂度 \(\mathcal{O(2^c\frac{m}{\omega})}\)

\(~~~~\) 然后我们就解决了这道题了。

代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int n,m,q;
vector <int> G[300005];
int Fa[300005],dep[300005];
int Col[300005],Siz[300005],Son[300005];
void dfs1(int u,int fa)
{
	Siz[u]=1; Fa[u]=fa; dep[u]=dep[fa]+1;
	for(int i=0;i<(int)G[u].size();i++)
	{
		int v=G[u][i];
		if(v==fa) continue;
		dfs1(v,u); Siz[u]+=Siz[v];
		if(Siz[v]>Siz[Son[u]]) Son[u]=v;
	}
}
bitset<1003>S[300005];
int dfn[300005],To[300005],Top[300005],Times;
void dfs2(int u,int T)
{
	dfn[u]=++Times;To[Times]=u; Top[u]=T; 
	if(Son[u]) S[Son[u]]=S[u],S[Son[u]][Col[Son[u]]-1]=1,dfs2(Son[u],T);
	for(int i=0;i<(int)G[u].size();i++)
	{
		int v=G[u][i];
		if(v==Fa[u]||v==Son[u]) continue;
		S[v][Col[v]-1]=1;
		dfs2(v,v);
	}
}
int LCA(int x,int y)
{
	while(Top[x]!=Top[y])
	{
		if(dep[Top[x]]<dep[Top[y]]) swap(x,y);
		x=Fa[Top[x]];	
	}
	return dep[x]<dep[y]?x:y;	
}
struct SegmentTree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson p<<1,l,mid
	#define rson p<<1|1,mid+1,r
	bitset<1003>tr[1200005];
	inline void pushUp(int p){tr[p]=tr[ls]|tr[rs];}
	void Build(int p,int l,int r)
	{
		if(l==r){tr[p][Col[To[l]]-1]=1;return;}
		int mid=(l+r)>>1;
		Build(lson); Build(rson);
		pushUp(p);
	}
	bitset<1003> Query(int p,int l,int r,int lx,int rx)
	{
		if(lx<=l&&r<=rx) return tr[p];
		int mid=(l+r)>>1;
		if(lx<=mid&&mid<rx) return Query(lson,lx,rx)|Query(rson,lx,rx);
		if(lx<=mid) return Query(lson,lx,rx);
		return Query(rson,lx,rx);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Seg;
int Beg[6],Pc[33];
bitset <1003> Gain[6];
bitset<1003> Get(int x,int y)
{
	bitset<1003>res;
	while(Top[x]!=Top[y]) res|=S[x],x=Fa[Top[x]];
	if(dep[x]>dep[y]) swap(x,y);
	return res|Seg.Query(1,1,n,dfn[x],dfn[y]); 
}
int main() {
	for(int i=1;i<=32;i++) Pc[i]=Pc[i>>1]+(i&1);
	read(n);read(m);read(q);
	for(int i=2,p;i<=n;i++) read(p),G[p].push_back(i);
	for(int i=1;i<=n;i++) read(Col[i]);
	dfs1(1,0); dfs2(1,1); Seg.Build(1,1,n);
	for(int qwq=1,c;qwq<=q;qwq++)
	{
		read(c);int Tmp=-1;
		for(int i=1;i<=c;i++)
		{
			read(Beg[i]);
			if(Tmp==-1) Tmp=Beg[i];
			else Tmp=LCA(Tmp,Beg[i]);
		}
		for(int i=1;i<=c;i++) Gain[i]=Get(Beg[i],Tmp);
		int Ans=1e9;
		for(int i=1;i<(1<<c);i++)
		{
			bitset <1003> P;
			for(int j=1;j<=c;j++)
				if((i>>j-1)&1) P|=Gain[j];
			Ans=min(Ans,(int)P.count()/Pc[i]*c);
		}
		printf("%d\n",Ans);
	}
	return 0;
}
/*
西风吹老洞庭波,一夜湘君白发多。
醉后不知天在水,满船清梦压星河。
*/
posted @ 2023-01-27 22:33  Azazеl  阅读(25)  评论(0编辑  收藏  举报