@bzoj - 3681@ Arietta
@description@
所有的 n 个音符形成一棵由音符 C ( 1 号节点) 构成的有根树,每一个音符有一个音高 Hi 。
Arietta 有 m 个力度,第 i 个力度能弹出 Di 节点的子树中,音高在 [Li,Ri] 中的任意一个音符。
为了乐曲的和谐,Arietta 最多会弹奏第 i 个力度 Ti 次。
Arietta 想知道她最多能弹出多少种音符。
Input
输入共 m + 3 行。
第一行两个整数 n, m ,意义如题目所述。
第二行 n - 1 个整数 Pi ,表示节点 i ( i = 2 . . . n ) 的父亲节点的编号。
第三行 n 个整数 Hi 。
接下来的 m 行,每行四个整数 Li,Ri,D,Ti
Output
输出一个整数表示 Arietta 最多能弹奏多少音符。
Sample Input
5 2
1 1 2 2
5 3 2 4 1
1 3 2 1
3 5 1 4
Sample Output
4
HINT
第一个力度弹奏音符5,第二个力度弹奏音符1,2,4。
数据范围与约定
对于 100% 的数据,1 ≤ n, m ≤ 10000 。
对于所有数据1<=Hi,Ti,Pi<=N,1<=Li<=Ri<=N
@solution@
颇有点像资源分配问题。
分配 m 种资源,每种资源有 Ti 个,且必须要分配给满足一些特定条件的点,问最后有多少点得到资源。
非常显然的网络流,但暴力建边是 O(n^2) 的,所以这道题的问题主要集中在如何优化建边。
一个比较显然的思路:我们将树转为 dfs 序,则子树转为 dfs 序上的区间,等于是往一个二维矩阵内连边。用些树套树就可以最终变为 O(nlog^2n) 的连边。
还有一种 O(nlog^2n) 的连边方法,即使用启发式合并。重子树通过建可持久化线段树连边,轻子树的点暴力向重子树的线段树加。每个点被加 O(logn) 次,再套上线段树的 O(logn) 即 O(nlog^2n)。
我没试过 log^2n 能不能过,不过网络流跑 10^6 还是比较形而上学的。。。
我们 O(nlogn) 的连边方法通过上述的启发式合并延伸得到,即使用可持久化线段树合并代替启发式合并。
其他的和普通线段树合并是一致的,只是叶子合并时需要特殊处理:将新叶子连向原来的两个叶子。
虽然网络流跑 10^5 依然非常形而上学。。。
@accepted code@
#include<cstdio>
#include<vector>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 10000;
const int MAXV = 50*MAXN;
const int MAXE = 200*MAXN;
const int INF = (1<<30);
typedef pair<int, int> pii;
struct FlowGraph{
struct edge{
int to, cap, flow;
edge *nxt, *rev;
}edges[MAXE + 5], *adj[MAXV + 5], *ecnt;
int s, t, d[MAXV + 5], vd[MAXV + 5];
FlowGraph() {ecnt = &edges[0];}
void addedge(int u, int v, int c) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->cap = c, p->flow = 0;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->cap = 0, q->flow = 0;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
// printf("! %d %d %d\n", u, v, c);
}
void get_dist() {
for(int i=s;i<=t;i++)
d[i] = t + 5, vd[t + 5]++;
queue<int>que;
d[t] = 0, vd[0]++, que.push(t);
while( !que.empty() ) {
int f = que.front(); que.pop();
for(edge *p=adj[f];p;p=p->nxt)
if( p->cap == p->flow ) {
if( d[f] + 1 < d[p->to] ) {
vd[d[p->to]]--;
d[p->to] = d[f] + 1;
vd[d[p->to]]++;
que.push(p->to);
}
}
}
}
int aug(int x, int tot) {
if( x == t ) return tot;
int sum = 0, mind = t + 5;
for(edge *p=adj[x];p;p=p->nxt) {
if( p->cap > p->flow ) {
if( d[x] == d[p->to] + 1 ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot || d[s] >= t + 5 ) return sum;
}
mind = min(mind, d[p->to]);
}
}
if( sum == 0 ) {
vd[d[x]]--;
if( vd[d[x]] == 0 ) {
d[s] = t + 5;
return sum;
}
d[x] = mind + 1;
vd[d[x]]++;
}
return sum;
}
int max_flow(int _s, int _t) {
s = _s, t = _t, get_dist();
int flow = 0;
while( d[s] < t + 5 )
flow += aug(s, INF);
return flow;
}
}G;
struct edge{
edge *nxt; int to;
}edges[MAXN + 5], *adj[MAXN + 5], *ecnt = &edges[0];
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
}
vector<pair<pii, int> >v[MAXN + 5];
int H[MAXN + 5];
int n, m, s = 0, t;
int ch[2][30*MAXN + 5], pos[MAXN + 5], ncnt = 0;
int new_tree(int l, int r, int ps, int k) {
int p = (++ncnt);
if( l == r ) {
G.addedge(p+n+m, k+m, INF);
return p;
}
int mid = (l + r) >> 1;
if( ps <= mid ) {
ch[0][p] = new_tree(l, mid, ps, k);
G.addedge(p+n+m, ch[0][p]+n+m, INF);
}
else {
ch[1][p] = new_tree(mid + 1, r, ps, k);
G.addedge(p+n+m, ch[1][p]+n+m, INF);
}
return p;
}
int merge(int rt1, int rt2, int l, int r) {
if( !rt1 ) return rt2;
if( !rt2 ) return rt1;
int p = (++ncnt);
if( l == r ) {
G.addedge(p+n+m, rt1+n+m, INF);
G.addedge(p+n+m, rt2+n+m, INF);
return p;
}
int mid = (l + r) >> 1;
ch[0][p] = merge(ch[0][rt1], ch[0][rt2], l, mid);
if( ch[0][p] ) G.addedge(p+n+m, ch[0][p]+n+m, INF);
ch[1][p] = merge(ch[1][rt1], ch[1][rt2], mid + 1, r);
if( ch[1][p] ) G.addedge(p+n+m, ch[1][p]+n+m, INF);
return p;
}
void link_edge(int rt, int l, int r, int ql, int qr, int p) {
if( ql > r || qr < l || (!rt) ) return ;
if( ql <= l && r <= qr ) {
G.addedge(p, rt+n+m, INF);
return ;
}
int mid = (l + r) >> 1;
link_edge(ch[0][rt], l, mid, ql, qr, p);
link_edge(ch[1][rt], mid + 1, r, ql, qr, p);
}
void dfs(int x) {
pos[x] = new_tree(1, n, H[x], x);
for(edge *p=adj[x];p;p=p->nxt) {
dfs(p->to);
pos[x] = merge(pos[x], pos[p->to], 1, n);
}
for(int i=0;i<v[x].size();i++)
link_edge(pos[x], 1, n, v[x][i].first.first, v[x][i].first.second, v[x][i].second);
}
int main() {
scanf("%d%d", &n, &m);
for(int i=2;i<=n;i++) {
int p; scanf("%d", &p);
addedge(p, i);
}
for(int i=1;i<=n;i++)
scanf("%d", &H[i]);
for(int i=1;i<=m;i++) {
int L, R, D, T; scanf("%d%d%d%d", &L, &R, &D, &T);
G.addedge(s, i, T);
v[D].push_back(make_pair(make_pair(L, R), i));
}
dfs(1); t = n + m + ncnt + 1;
for(int i=1;i<=n;i++)
G.addedge(i+m, t, 1);
printf("%d\n", G.max_flow(s, t));
}
@details@
可持久化线段树合并需要 2nlogn 的空间。
写 Isap 头一次加了预处理距离标号,怕被卡常。。。