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

猜你喜欢

转载自www.cnblogs.com/Tiw-Air-OAO/p/11335177.html