zoukankan      html  css  js  c++  java
  • @bzoj


    @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
    ", 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
    ", G.max_flow(s, t));
    }
    

    @details@

    可持久化线段树合并需要 2nlogn 的空间。

    写 Isap 头一次加了预处理距离标号,怕被卡常。。。

  • 相关阅读:
    2019-8-31-C#-性能分析-反射-VS-配置文件-VS-预编译
    2018-8-10-WPF-鼠标移动到列表上-显示列表图标
    C语言对齐、补齐
    main函数前后
    Ubuntu安装telnet
    Ubuntu安装rpm
    extern c 解释
    gcc和g++编译器
    原子操作
    linux内核信号量
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11335177.html
Copyright © 2011-2022 走看看