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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzj1054689699/article/details/81750263

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 | ) ,其中S为我们选出的左边的点集,F为与这个点集有连边的右边点集合大小。

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

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

猜你喜欢

转载自blog.csdn.net/hzj1054689699/article/details/81750263