没有传送门
题目大意
给你一棵 个结点的数,每个结点有一种特产,用 表示。总共有 种特产。有 个询问,每个询问形如:有 个人,分别在某些城市,现在他们要走到 LCA 处。他们能够买沿途的特产,要求:
- 每种特产要买只能买一件;
- 每个人买的特产数量要相等;
- 所有人买的特产都不能重复。
求最多能买多少种(件)特产。
, , , 。
Limited Constraint: , 。
考场上的思路
考虑做 Limited Constraint。求 LCA 无话可说, 倍增即可。每个询问暴力往上走,记录出现了哪些特产,现在的问题是如何求答案。
考虑网络流。把每种特产看作一个点,向汇点连一条容量为 的边,把每个人看成一个点,向能够买的特产连一条容量为 的边。不能够直接求最大流,考虑二分答案。如果二分答案后求最大流满流,说明可行,否则需要减少答案。
使用 Dinic 进行求解,可以认为每次求解的时间复杂度为 (我口胡的,不过你可以认为多路增广速度不错)。极限数据(两条链,所有特产都出现)要跑大约 3 sec,但出题人良心,直接让我过了 QAQ。
也想过树链剖分,但是感觉没卵用。
思路
考虑树链剖分 QAQ,不管怎么说把每个人能买的特产求出来再说。由于 比较小,又必须具体地求出每个人能买的特产种类(不然接下来怎么做 QAQ),因此考虑 bitset。
直接用树链剖分 + 线段树 + bitset 的时间复杂度是 的,算起来快 了,不太可过。考虑用预处理优化一下。预处理出每个结点到链顶的 bitset,查询时除了最后一段,其它的都直接用预处理后的值。预处理的时间复杂度是 的,单词查询的时间复杂度是 的。
(这里提醒一下,虽然 看上去蛮大的,但是 蛮小的。取 时,你可以认为它的大小仅为 )
不考虑接下来怎么做,目前我们能够在 的时间复杂度内求出每个人能买的特产,很优秀了。
接下来不要用网络流了,要用一个叫做 Hall 定理的东西。
Hall 定理
完全没有听说过啊 QAQ。
Hall 定理是跟二分图完美匹配有关的东西,先来一个定义:
完美匹配:若一个二分图的最大匹配数为 ,则称该二分图存在完美匹配。
Hall 定理:
二分图存在完美匹配当且仅当 中的任意 个点至少与 中 点有连边。
唉,我也不想学习怎么证明了,这里是王师傅的证明。一年前王师傅在我还在玩泥巴的时候就会了,太强辣!
继续怎么做呢?
直接把之前的网络流图搬过来肯定不行啊,那个不是匹配。不过考虑一个很笨的方法:如果之前我们不是通过修改源点到人的容量,而是加点呢?虽然在网络流中这个做法很傻,但是在不用网络流的这里就很聪明了——这个做法保证了求的东西是二分图的最大匹配。显然的是,如果一个答案合法,那么这个二分图就有完美匹配,也就是说任意 个点都至少能买 个特产(有买 个特产的机会)。
对于同一个人来说,根本不用检查都属于他的点:只要这个人能买 个特产,把他拆成 个点他还是能买 个特产。所以问题就在于不同的人身上。我们把不同的人组合起来,分别检查是否满足霍尔定理即可。检查的时间复杂度为 。
由于要满足霍尔定理,因此 ,所以答案为检查值 除以人数的下取整的最小值(因为要任意)。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef int INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0; bool positive = true;
char ch = getchar();
while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
if (ch == '-') { positive = false; ch = getchar(); }
while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20]; int length = 0;
if (x < 0) putchar('-'); else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
putchar('\n');
}
const int INF = (~(int(1) << (sizeof(int) * 8 - 1))) >> 1;
const int maxn = int(3e5) + 5;
const int maxm = int(1e3) + 5;
int n, m, q;
int a[maxn];
int LCA(int, int);
struct Query
{
int c[6];
int lca;
void read()
{
c[0] = readIn();
for (int i = 1; i <= c[0]; i++)
c[i] = readIn();
lca = LCA(c[1], c[2]);
for (int i = 3; i <= c[0]; i++)
lca = LCA(lca, c[i]);
}
} querys[maxn];
typedef std::vector<std::vector<int> > Graph;
Graph G;
int parent[maxn];
int depth[maxn];
int size[maxn];
int heavy[maxn];
void DFS1(int node)
{
depth[node] = depth[parent[node]] + 1;
size[node] = 1;
for (int i = 0; i < G[node].size(); i++)
{
int to = G[node][i];
DFS1(to);
size[node] += size[to];
if (!heavy[node] || size[to] > size[heavy[node]])
heavy[node] = to;
}
}
int stamp;
int dfn[maxn];
int seq[maxn];
int top[maxn];
void DFS2(int node, int t)
{
stamp++;
dfn[node] = stamp;
seq[stamp] = node;
if (!t)
t = node;
top[node] = t;
if (heavy[node])
DFS2(heavy[node], t);
for (int i = 0; i < G[node].size(); i++)
{
int to = G[node][i];
if (to == heavy[node]) continue;
DFS2(to, 0);
}
}
int LCA(int u, int v)
{
while (top[u] != top[v])
{
if (depth[top[u]] < depth[top[v]])
std::swap(u, v);
u = parent[top[u]];
}
return depth[u] < depth[v] ? u : v;
}
#define RunInstance(x) delete new x
struct brute
{
int stamp;
int vis[maxm];
int appear[6][maxm];
struct NetworkFlow
{
struct Edge
{
int from, to, cap, flow;
Edge() {}
Edge(int from, int to, int cap) : from(from), to(to), cap(cap), flow() {}
};
Graph G;
std::vector<Edge> edges;
int addEdge(int from, int to, int cap)
{
edges.push_back(Edge(from, to, cap));
edges.push_back(Edge(to, from, 0));
int i = edges.size();
G[from].push_back(i - 2);
G[to].push_back(i - 1);
return i - 2;
}
void operator=(const NetworkFlow& b)
{
G = b.G;
edges = b.edges;
s = b.s;
t = b.t;
}
int s, t;
int level[maxn];
std::vector<int> cur;
int Dinic(int node, int opt)
{
if (node == t || !opt) return opt;
int flow = 0;
for (int& i = cur[node]; i < G[node].size(); i++)
{
int t;
Edge& e = edges[G[node][i]];
if (e.flow < e.cap && level[node] + 1 == level[e.to] &&
(t = Dinic(e.to, std::min(opt, e.cap - e.flow))))
{
flow += t;
opt -= t;
e.flow += t;
edges[G[node][i] ^ 1].flow -= t;
if (!opt) break;
}
}
return flow;
}
struct Queue
{
int c[maxn];
int head, tail;
Queue() : head(), tail() {}
void clear() { head = tail = 0; }
void push(int x) { c[tail++] = x; }
void pop() { head++; }
int front() { return c[head]; }
bool empty() { return head == tail; }
} q;
int stamp;
int vis[maxn];
NetworkFlow() : stamp(), vis() {}
bool BFS()
{
q.clear();
stamp++;
vis[s] = stamp;
q.push(s);
level[s] = 0;
while (!q.empty())
{
int from = q.front();
q.pop();
for (int i = 0; i < G[from].size(); i++)
{
const Edge
& e = edges[G[from][i]];
if (e.flow < e.cap && vis[e.to] != stamp)
{
vis[e.to] = stamp;
level[e.to] = level[from] + 1;
q.push(e.to);
}
}
}
return vis[t] == stamp;
}
int maxflow()
{
int flow = 0;
while (BFS())
{
cur.clear();
cur.resize(G.size());
flow += Dinic(s, INF);
}
return flow;
}
} nf, backup;
brute() : stamp(), vis(), appear()
{
backup.G.resize(m + 7);
backup.s = 0;
backup.t = m + 1;
for (int i = 1; i <= m; i++)
backup.addEdge(i, backup.t, 1);
for (int i = 1; i <= q; i++)
{
const int* c = querys[i].c;
int lca = querys[i].lca;
for (int j = 1; j <= c[0]; j++)
{
stamp++;
appear[j][0] = 0;
int cnt = c[j];
while (true)
{
if (vis[a[cnt]] != stamp)
{
vis[a[cnt]] = stamp;
appear[j][++appear[j][0]] = a[cnt];
}
if (cnt == lca)
break;
cnt = parent[cnt];
}
}
int l = 0, r = m / c[0] + 1;
while (r - l > 1)
{
int mid = (l + r) >> 1;
nf = backup;
for (int j = 1; j <= c[0]; j++)
for (int k = appear[j][0]; k; k--)
nf.addEdge(m + 1 + j, appear[j][k], 1);
for (int j = 1; j <= c[0]; j++)
nf.addEdge(nf.s, m + 1 + j, mid);
if (nf.maxflow() == mid * c[0])
l = mid;
else
r = mid;
}
printOut(l * c[0]);
}
}
};
struct work
{
typedef std::bitset<1024> bitset;
class SegTree
{
static inline int code(int l, int r)
{
return (l + r) | (l != r);
}
bitset nodes[maxn * 2];
int g_L, g_R;
bitset query_(int l, int r)
{
if (g_L <= l && r <= g_R)
{
return nodes[code(l, r)];
}
int mid = (l + r) >> 1;
bitset ret;
if (g_L <= mid) ret |= query_(l, mid);
if (g_R > mid) ret |= query_(mid + 1, r);
return ret;
}
public:
SegTree() { build(1, n); }
void build(int l, int r)
{
if (l == r)
{
nodes[code(l, r)].set(a[seq[l]]);
return;
}
int mid = (l + r) >> 1;
build(l, mid);
build(mid + 1, r);
nodes[code(l, r)] = nodes[code(l, mid)] | nodes[code(mid + 1, r)];
}
bitset query(int l, int r)
{
g_L = l;
g_R = r;
return query_(1, n);
}
} st;
bitset topset[maxn];
void DFS3(int node)
{
if (node == top[node])
topset[node].set(a[node]);
else
(topset[node] = topset[parent[node]]).set(a[node]);
for (int i = 0; i < G[node].size(); i++)
{
int to = G[node][i];
DFS3(to);
}
}
bitset query(int u, int lca)
{
bitset ret;
while (top[u] != top[lca])
{
ret |= topset[u];
u = parent[top[u]];
}
ret |= st.query(dfn[lca], dfn[u]);
return ret;
}
work()
{
DFS3(1);
for (int i = 1; i <= q; i++)
{
bitset set[6];
const int* c = querys[i].c;
int lca = querys[i].lca;
for (int j = 1; j <= c[0]; j++)
set[j] = query(c[j], lca);
int ans = m;
int U = 1 << c[0];
for (int S = 1; S < U; S++)
{
int cnt = 0;
set[0].reset();
for (int j = 0; j < c[0]; j++)
if (S & (1 << j))
{
set[0] |= set[j + 1];
cnt++;
}
ans = std::min(ans, (int)set[0].count() / cnt);
}
printOut(ans * c[0]);
}
}
};
void run()
{
n = readIn();
m = readIn();
q = readIn();
if (!q)
return;
G.resize(n + 1);
for (int i = 2; i <= n; i++)
G[parent[i] = readIn()].push_back(i);
for (int i = 1; i <= n; i++)
a[i] = readIn();
DFS1(1);
DFS2(1, 0);
for (int i = 1; i <= q; i++)
querys[i].read();
if (n <= 3000 && q <= 3000)
RunInstance(brute);
else
RunInstance(work);
}
int main()
{
#ifndef LOCAL
freopen("party.in", "r", stdin);
freopen("party.out", "w", stdout);
#endif
run();
return 0;
}