【BZOJ3681】Arietta,主席树优化网络流

传送门
思路:
题目来源于Contest Hunter
红太阳mrazer和我说的
第一次做这种数据结构优化网络流的题目之前只是停留在口胡的地步
网上的题解不多,官方题解给出的比较标准的做法是线段树合并+可持久化
不过,蒟蒻表示并没有看懂这种做法,而且好像后来有人发现如果点权相同,64MB的内存根本开不下?
回到题目,建图是显而易见的,关键在如何优化 O(nm) 的边数
暴力的想法是每个节点开一颗权值线段树维护子树内的信息,往区间代表点连边,叶子节点往每个树节点的代表点连边,空间复杂度和时间复杂度都不科学
借助Yveh博客中dsu on tree的思想,我们可以把节点x重儿子的线段树作为节点x的线段树,然后暴力轻儿子所在子树中的信息,这样做可知每个节点的信息最多被暴力统计 logn 次(也就是其到根路径上重链的数量),空间复杂度 O(nlog2n)
每插入一个点,新建的点要向前一个对应的区间代表点连边,流量inf
说白了就是可持久化权值线段树+启发式合并,应该也是题解中给出的一种做法
连边的时候找对应的权值区间,连边即可,叶子节点也要向外连边
感觉程序有bug,如果有人能hack掉请在下面评论,谢谢
代码:

#include<cstdio>
#include<vector>
#include<iostream>
#include<queue>
#define inf 1e9
#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1] 
#define M 10005
using namespace std;
int in()
{
    int t=0;char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch>='0'&&ch<='9') t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return t;
}
int n,m,cnt;
int h[M],siz[M],son[M],root[M];
queue<int>q;
struct node{
    int l,r,d,t;
}Q[M];
struct tree{
    int ch[2];
}tr[130*M];
vector<int>e[M];
namespace network
{
    int s,t,tot=1,first[M*130],cur[M*130],dis[M*130];
    struct edge{
        int v,w,next;
    }e[M*300];
    void add(int x,int y,int z)
    {
        e[++tot].v=y;e[tot].w=z;e[tot].next=first[x];first[x]=tot;
        e[++tot].v=x;e[tot].w=0;e[tot].next=first[y];first[y]=tot;
    } 
    bool bfs()
    {
        for (int i=0;i<=t;++i) dis[i]=0,cur[i]=first[i];
        int x;
        dis[s]=1;
        for (q.push(s);!q.empty();q.pop())
        {
            x=q.front();
            for (int i=first[x];i;i=e[i].next)
            if (!dis[e[i].v]&&e[i].w)
                dis[e[i].v]=dis[x]+1,
                q.push(e[i].v);
        }
        return dis[t]>0;
    }
    int dfs(int x,int maxn)
    {
        if (x==t) return maxn;
        int used=0,k;
        for (int i=cur[x];i;i=e[i].next)
        if (dis[e[i].v]==dis[x]+1)
        {
            k=dfs(e[i].v,min(maxn-used,e[i].w));
            e[i].w-=k;e[i^1].w+=k;
            if (e[i].w) cur[x]=i;
            used+=k;
            if (used==maxn) return maxn;
        }
        if (!used) dis[x]=0;
        return used;
    }
    int dinic()
    {
        int ans=0;
        while (bfs())
        ans+=dfs(s,inf);
        return ans;
    }
}
void build(int rt,int now,int L,int R,int p)
{
    if (rt) network::add(now,rt,inf);
    if (L==R) return void(network::add(now,p,1));
    int mid=(L+R)>>1;
    if (mid<h[p])
        ls(now)=ls(rt),
        rs(now)=++cnt,
        network::add(now,rs(now),inf),
        build(rs(rt),rs(now),mid+1,R,p);
    else
        rs(now)=rs(rt),
        ls(now)=++cnt,
        network::add(now,ls(now),inf),
        build(ls(rt),ls(now),L,mid,p);
}
void dfs(int x)
{
    siz[x]=1;
    for (int v,i=0;i<e[x].size();++i)
    {
        v=e[x][i];
        dfs(v);
        siz[x]+=siz[v];
        if (siz[son[x]]<siz[v]) son[x]=v;
    }
    root[x]=root[son[x]];
    int tmp=root[x];
    build(tmp,root[x]=++cnt,1,n,x);
    for (int v,i=0;i<e[x].size();++i)
    {
        v=e[x][i];
        if (v==son[x]) continue;
        for (q.push(v);!q.empty();q.pop())
        {
            v=q.front();
            tmp=root[x];
            build(tmp,root[x]=++cnt,1,n,v);
            for (int j=0;j<e[v].size();++j) q.push(e[v][j]);
        }
    }
}
void get(int rt,int L,int R,int id)
{
    if (Q[id].l<=L&&R<=Q[id].r) return void(network::add(id+n,rt,inf));
    int mid=L+R>>1;
    if (mid>=Q[id].l) get(ls(rt),L,mid,id);
    if (mid<Q[id].r) get(rs(rt),mid+1,R,id);
}
main()
{
    n=in();m=in();
    cnt=n+m;
    for (int i=2;i<=n;++i) e[in()].push_back(i);
    for (int i=1;i<=n;++i) h[i]=in();
    for (int i=1;i<=m;++i) Q[i]=(node){in(),in(),in(),in()},network::add(network::s,i+n,Q[i].t);
    dfs(1);
    network::s=0;network::t=cnt+1;
    for (int i=1;i<=m;++i)  get(root[Q[i].d],1,n,i);
    for (int i=1;i<=n;++i) network::add(i,network::t,1);
    printf("%d\n",network::dinic());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值