@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 头一次加了预处理距离标号,怕被卡常。。。

posted @ 2019-08-11 15:31  Tiw_Air_OAO  阅读(155)  评论(0编辑  收藏  举报