【bzoj3681】Arietta

题面

所有的 n 个音符形成一棵由音符 C ( 1 号节点) 构成的有根树,每一个音符有一个音高 Hi 。
Arietta 有 m 个力度,第 i 个力度能弹出 Di 节点的子树中,音高在 [Li,Ri] 中的任意一个音符。
为了乐曲的和谐,Arietta 最多会弹奏第 i 个力度 Ti 次。
Arietta 想知道她最多能弹出多少个音符。
1 ≤ n, m ≤ 10000 。
链接戳这里!

分析

网络流模型是显然的,重点在于考虑如何优化建图。
一个点希望向一颗树的子树中权值范围在 [ L , R ] [L, R] [L,R]的点连边。一个容易想到的想法是dfs序在 [ l , r ] [l, r] [l,r]权值在 [ L , R ] [L, R] [L,R],并利用二维线段树优化建图。但是二维线段树的空间(以及可能有时间)都是无法承受的。考察第二种思路,将点按照dfs序插入,利用主席树来进行优化建图。然而有一个问题是建图是没有办法进行作差操作的,于是前缀和也没有用武之地。似乎没有办法只把一个点插入一次,尝试能否插入log次。联想到Dsu on Tree的思路,仍然利用主席树,每个节点copy其重子的主席树,并把其他儿子的节点插入进去。这样我们一共有 O ( n ∗ l o g n 2 ) O(n*log n^2) O(nlogn2)条边。

思路

注意事项

  1. 所有的叶子节点都连向一个单独的音符节点,以防止每个音符被匹配多次
  2. 每个新建的叶子节点要连向前一个版本的叶子,否则前一个版本相当于白建了。
  3. 连边的时候不能按照每个节点去dfs,像这样
void ass(int u, int l, int r) {
	if(l == r) return ;
	if(t[u].ls) adde(u+n, t[u].ls+n, MAXN), ass(t[u].ls, l, mid);
	if(t[u].rs) adde(u+n, t[u].rs+n, MAXN), ass(t[u].rs, mid+1, r);
}
for(int i = 1; i <= n; ++i) ass(root[i], 1, MAXN);

我们使用的是主席树,有大量的节点是相同的,这样连边会出现大量重边导致炸空间,相当于主席树白用了。
像下面这样就是对的

for(int i = 1; i <= tcnt; ++i) {
	if(t[i].ls) adde(i+n, t[i].ls+n, n);
	if(t[i].rs) adde(i+n, t[i].rs+n, n);
}

代码

#include <bits/stdc++.h>
#define il inline
#define ri register int
#define pb push_back
#define fir first
#define sec second
#define MAXN 10050
#define MAXM
#define MOD
#define INF (1<<25)
#define eps (1e-6)
#define mid ((l+r)>>1)
using namespace std;
typedef long long LL;
typedef long double LD;
int n, m, tcnt, ecnt, S, T, root[MAXN], size[MAXN], val[MAXN], q[MAXN*150], level[MAXN*150], vis[MAXN], son[MAXN];
vector <int> adj[MAXN];
struct node {
	int v, c; node *next, *rev;
}pool[MAXN*300], *h[MAXN*80], *cur[MAXN*80];
struct Node {
	int ls, rs;
}t[MAXN*150];
il void adde(int u, int v, int c) {
	node *p = &pool[ecnt++], *q = &pool[ecnt++];
	*p = node {v, c, h[u], q}, h[u] = p;
	*q = node {u, 0, h[v], p}, h[v] = q;
}
void ins(int &u, int l, int r, int x, int p) {
	int tmp = u; u = ++tcnt, t[u] = t[tmp];
	if(l == r) {
		if(tmp) adde(u+n, tmp+n, MAXN);
		adde(u+n, p, MAXN);
		return ;
	}
	if(x <= mid) ins(t[u].ls, l, mid, x, p); 
	else ins(t[u].rs, mid+1, r, x, p);
}
void link(int u, int l, int r, int tl, int tr, int p) {
	if(!u || l > tr || r < tl) return ;
	if(tl <= l && r <= tr) return adde(p+n, u+n, MAXN);
	link(t[u].ls, l, mid, tl, tr, p), link(t[u].rs, mid+1, r, tl, tr, p);
}
void add(int u, int rt) {
	ins(root[rt], 1, MAXN, val[u], u);
	for(ri i = 0; i < adj[u].size(); ++i) if(!vis[adj[u][i]]) add(adj[u][i], rt);
}
void dfs(int u) {
	size[u] = 1;
	for(ri i = 0; i < adj[u].size(); ++i) {
		dfs(adj[u][i]), size[u] += size[adj[u][i]];
		if(size[adj[u][i]] > size[son[u]]) son[u] = adj[u][i];
	}
}
void dsu_on_tree(int u) {
	for(ri i = 0; i < adj[u].size(); ++i) dsu_on_tree(adj[u][i]);
	root[u] = root[son[u]], vis[son[u]] = 1, add(u, u), vis[son[u]] = 0;
}
bool bfs() {
	int front = 0, rear = 1;
	for(ri i = S; i <= T; ++i) level[i] = 0;
	q[0] = S, level[S] = 1;
	while(front < rear) {
		int u = q[front++];
		for(node *p = h[u]; p; p = p->next) {
			if(p->c && !level[p->v]) {
				level[p->v] = level[u]+1, q[rear++] = p->v;
				if(p->v == T) return 1; 
			}
		}
	}
	return 0;
}
int dfs(int u, int f) {
	if(u == T) return f;
	int F, flow = 0;
	for(node *&p = cur[u]; p; p = p->next) {
		if(p->c && level[p->v] == level[u]+1) {
			F = dfs(p->v, min(f-flow, p->c));
			p->c -= F, p->rev->c += F, flow += F;
			if(flow == f) return flow;
		}
	}
	return flow;
}
int Dinic() {
	int ret = 0;
	while(bfs()) {
		for(ri i = S; i <= T; ++i) cur[i] = h[i];
		ret += dfs(S, INF);
	}
	return ret;
}
int main() {
	int p, l, r, d, o;
	scanf("%d%d", &n, &m);
	for(ri i = 2; i <= n; ++i) scanf("%d", &p), adj[p].pb(i);
	for(ri i = 1; i <= n; ++i) scanf("%d", &val[i]);
	dfs(1), dsu_on_tree(1);
	for(ri i = 1; i <= tcnt; ++i) {
		if(t[i].ls) adde(i+n, t[i].ls+n, n);
		if(t[i].rs) adde(i+n, t[i].rs+n, n);
	}
	while(m--) {
		scanf("%d%d%d%d", &l, &r, &d, &o);
		++tcnt, adde(S, tcnt+n, o);
		link(root[d], 1, MAXN, l, r, tcnt);
	}
	T = ++tcnt+n;
	for(ri i = 1; i <= n; ++i) adde(i, T, 1);
	printf("%d", Dinic());
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值