[2018HN省队集训D5T2] party

[2018HN省队集训D5T2] party

题意

给定一棵 \(n\) 个点以 \(1\) 为根的有根树, 每个点有一个 \([1,m]\) 的权值.

\(q\) 个查询, 每次给定一个大小为 \(c\) 的点集, 点集中的每个点都可以选择若干从自身到所有点的LCA的路径上的点的权值. 要求所有点选取的权值之间都不能重复且每个点选择的权值种类数相等. 求最大的总种类数量.

\(n\le 3\times 10^5,m\le 1000, q\le 5\times 10^4,c\le5\).

题解

注意到 \(m\) 的范围比较小, 我们一点都不显然可以用 std::bitset 来维护某条路径上存在的权值集合.

然后我们如果要暴力判定的话, 可以二分答案/多次增广+Dinic来跑. 从这个过程中可以看出我们实际上要求的是满足一边有 \(c\times k\) 个点另一边有 \(m\) 个点的二分图存在完美匹配的最大的 \(k\).

涉及到完美匹配的判定, 我们有一个玄学定理叫霍尔定理. 大体内容是:

一个二分图 \(G\) 存在完美匹配, 当且仅当 \(X\) 中的任意 \(k\) 个点都至少与 \(Y\) 中的 \(k\) 个点邻接.

不难发现左部的 \(c\times k\) 个点中只有 \(c\) 种邻接关系不同的点, 所以我们 \(2^c\)枚举左部点的子集, 用 std::bitset 取并来计算邻接点个数, 则 \(k\) 的最大值即为邻接点个数与左部点子集大小的比值的最小值.

于是就这么跑就可以了. 代码极为好写.

不过查询路径的时候如果用普通树剖+线段树的话是 \(\log^2\) 的, 注意到我们只会求某个点的祖先到某个点的路径, 也就是说除了最浅的一条链之外其他的链都只取了一个前缀. 于是记录每个点到链顶的前缀和, 最后一次查询用线段树就可以把复杂度降到一个 \(\log\) 了. 然而犯懒没写...不加这个优化跑得也挺快的qwq

参考代码

#include <bits/stdc++.h>

const int MAXV=3e5+10;
const int MAXE=1e6+10;
typedef std::bitset<1024> bits;

struct Edge{
    int from;
    int to;
    Edge* next;
};
Edge E[MAXE];
Edge* head[MAXV];
Edge* topE=E;

struct Node{
    int l;
    int r;
    bits val;
    Node* lch;
    Node* rch;
    Node(int,int);
    bits Query(int,int);
};
Node* N;

int n;
int m;
int q;
int clk;
int t[10];
bits b[10];
int a[MAXV];
int dfn[MAXV];
int pos[MAXV];
int prt[MAXV];
int son[MAXV];
int top[MAXV];
int size[MAXV];
int deep[MAXV];

int LCA(int,int);
void DFS(int,int);
bits Query(int,int);
void Insert(int,int);
void DFS(int,int,int);

int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=2;i<=n;i++){
        int x;
        scanf("%d",&x);
        Insert(x,i);
    }
    for(int i=1;i<=n;i++)
        scanf("%d",a+i);
    DFS(1,0,0);
    DFS(1,1);
    N=new Node(1,n);
    while(q--){
        int c=0;
        scanf("%d",&c);
        scanf("%d",t);
        int lca=t[0];
        for(int i=1;i<c;i++){
            scanf("%d",t+i);
            lca=LCA(lca,t[i]);
        }
        for(int i=0;i<c;i++)
            b[i]=Query(lca,t[i]);
        int ans=INT_MAX;
        for(int s=1;s<(1<<c);s++){
            bits cur;
            int cnt=0;
            for(int i=0;i<c;i++){
                if((1<<i)&s){
                    ++cnt;
                    cur|=b[i];
                }
            }
            ans=std::min<int>(ans,cur.count()/cnt);
        }
        printf("%d\n",ans*c);
    }
    return 0;
}

int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]])
            std::swap(x,y);
        x=prt[top[x]];
    }
    if(deep[x]>deep[y])
        std::swap(x,y);
    return x;
}

bits Query(int x,int y){
    bits ans;
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]])
            std::swap(x,y);
        ans|=N->Query(dfn[top[x]],dfn[x]);
        x=prt[top[x]];
    }
    if(deep[x]>deep[y])
        std::swap(x,y);
    ans|=N->Query(dfn[x],dfn[y]);
    return ans;
}

Node::Node(int l,int r):l(l),r(r){
    if(l==r)
        this->val.set(a[pos[l]]);
    else{
        int mid=(l+r)>>1;
        this->lch=new Node(l,mid);
        this->rch=new Node(mid+1,r);
        this->val=this->lch->val|this->rch->val;
    }
}

bits Node::Query(int l,int r){
    if(l<=this->l&&this->r<=r)
        return this->val;
    else{
        if(r<=this->lch->r)
            return this->lch->Query(l,r);
        if(this->rch->l<=l)
            return this->rch->Query(l,r);
        return this->lch->Query(l,r)|this->rch->Query(l,r);
    }
}

void DFS(int root,int prt,int deep){
    ::prt[root]=prt;
    ::deep[root]=deep;
    ::size[root]=1;
    for(Edge* i=head[root];i!=NULL;i=i->next){
        if(i->to!=prt){
            DFS(i->to,root,deep+1);
            size[root]+=size[i->to];
            if(size[i->to]>size[son[root]])
                son[root]=i->to;
        }
    }
}

void DFS(int root,int top){
    ++clk;
    ::dfn[root]=clk;
    ::pos[clk]=root;
    ::top[root]=top;
    if(son[root])
        DFS(son[root],top);
    for(Edge* i=head[root];i!=NULL;i=i->next)
        if(i->to!=prt[root]&&i->to!=son[root])
            DFS(i->to,i->to);
}

inline void Insert(int from,int to){
    topE->from=from;
    topE->to=to;
    topE->next=head[from];
    head[from]=topE++;
}

o_68420377_p0.jpg

转载于:https://www.cnblogs.com/rvalue/p/10479709.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值