一个神秘的oj2587 你猜是不是dp(线段树优化建图)

这难道不是happiness的翻版题嘛?

从\(S\)向一个点连染成白色的收益

从这个点向\(T\)连染成黑色的收益

对于额外的收益,建一个辅助点,跟区间内的每个点连\(inf\),然后向S/T,连流量为收益

这不就结束了吗?

自信写完,提交

woc!!只有40分?

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch==‘-‘) f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-‘0‘;ch=getchar();}
  return x*f;
}

const int maxn = 100010;
const int maxm = 2e6+1e2;
const int inf = 2e9;

int point[maxn],nxt[maxm],to[maxm],val[maxm];
int h[maxn],cnt=1;
int n,m;
int a[maxn],b[maxn];
int s,t;
queue<int> q;
int ans;

void addedge(int x,int y,int w)
{
    nxt[++cnt]=point[x];
    to[cnt]=y;
    val[cnt]=w;
    point[x]=cnt;
}

void insert(int x,int y,int w)
{
    addedge(x,y,w);
    addedge(y,x,0);
}

bool bfs(int s)
{
    memset(h,-1,sizeof(h));
    h[s]=0;
    q.push(s);
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        for(int i=point[x];i;i=nxt[i])
        {
            int p = to[i];
            if (val[i]>0 && h[p]==-1)
            {
                h[p]=h[x]+1;
                q.push(p);
            }
        }
    }
    if (h[t]==-1) return false;
    else return true;
}

int dfs(int x,int low)
{
    if (x==t || low==0) return low;
    int totflow=0;
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (val[i]>0 && h[p]==h[x]+1)
        {
            int tmp = dfs(p,min(low,val[i]));
            val[i]-=tmp;
            val[i^1]+=tmp;
            low-=tmp;
            totflow+=tmp;
            if (low==0) return totflow;
        }
    }
    if (low>0) h[x]=-1;
    return totflow;
}

int dinic()
{
    int ans=0;
    while (bfs(s))
    {
       ans=ans+dfs(s,inf);
    }
    return ans;
}

void build()
{
    s=n+m+100;
    t=s+1;
    for (int i=1;i<=n;i++)
    {
        if (a[i]>=0 && b[i]>=0)
        {
            insert(s,i,a[i]);
            insert(i,t,b[i]);
        }
        if (a[i]<0 && b[i]>=0)
        {
            insert(i,t,abs(a[i])+b[i]);
        }
        if (a[i]>=0 && b[i]<0)
        {
            insert(s,i,abs(b[i])+a[i]);
        }
        if (a[i]<0 && b[i]<0)
        {
            insert(s,i,abs(b[i]));
            insert(i,t,abs(a[i]));
        }
        if (a[i]>=0) ans+=a[i];
        if (b[i]>=0) ans+=b[i];
    }
}
int main()
{
  freopen("nicai.in","r",stdin);
  freopen("nicai.out","w",stdout);
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;i++) a[i]=read();
  for (int i=1;i<=n;i++) b[i]=read();
  build();
  int num = n+1;
  for (int i=1;i<=m;i++)
  {
     int opt,x,y,z;
     opt=read();
     x=read();
     y=read();
     z=read();
     if (opt==1)
     {
        for (int j=x;j<=y;j++)
        {
            insert(num,j,inf);
           }
         insert(s,num,z);
         num++;
       }
       else
       {
         for (int j=x;j<=y;j++)
        {
            insert(j,num,inf);
           }
         insert(num,t,z);
         num++;
       }
      ans=ans+z;
  }
  cout<<ans-dinic()<<endl;
  //cout<<dinic()<<endl;
  return 0;
}

后来仔细一想。

这么建图的复杂度,简直爆炸呀

不过貌似一段区间同时向一个点连边,这个东西可以优化呀?

哎?好像可以线段树???

这时候就需要我们这个题的重头戏了

线段树优化建图!

线段树优化建图主要是对于一系列一段连续区间向某一个点连边的题。

他的大致思路是

将线段树的节点作为图的点,然后连边的时候,将区间拆成\(log\)个小区间来连边,这样能大大减少边数。然后线段树节点之间的点连边\(inf\),用来确定最小割不会割掉这条边

而一般对于网络流或者双向边的题,一般是需要两颗线段树。

对于这道题,因为是新建的点,需要向\(S/T\)连边

所以需要两颗线段树,但是要注意父亲节点和儿子节点连边的方向

然后对于\(leaf\)节点,我们需要单独记录,并按照上面朴素做法的建图方式建图,然后跑最小割即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)){if (ch==‘-‘) f=-1;ch=getchar();}
   while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-‘0‘;ch=getchar();}
   return x*f;
}

const int maxn = 2e5+1e2;
const int maxm = 4e6;
const int inf = 1e9;

int f[8*maxn],g[8*maxn];
int point[maxn],nxt[maxm],to[maxm];
int h[maxn],cnt=1,val[maxm];
int n,m;
int s,t;
int leaf[maxn];
long long ymh=0;
int tmp=1 ;
queue<int> q;

void addedge(int x,int y,int w)
{
    nxt[++cnt]=point[x];
    to[cnt]=y;
    val[cnt]=w;
    point[x]=cnt;
}

void insert(int x,int y,int w)
{
    addedge(x,y,w);
    addedge(y,x,0);
}

void build(int root,int l,int r)
{
    if (l==r)
    {
        leaf[l]=++tmp;
        f[root]=tmp;
        return;
    }
    int mid = (l+r) >> 1;
    f[root]=++tmp;
    build(2*root,l,mid);
    build(2*root+1,mid+1,r);
    insert(f[root],f[2*root],inf);
    insert(f[root],f[2*root+1],inf);
}

void build1(int root,int l,int r)
{
    if (l==r)
    {
      g[root]=leaf[l];
      return;
    }
    int mid = (l+r) >> 1;
    g[root]=++tmp;
    build1(2*root,l,mid);
    build1(2*root+1,mid+1,r);
    insert(g[2*root],g[root],inf);
    insert(g[2*root+1],g[root],inf);
}

void update(int root,int l,int r,int x,int y,int p)
{
    if (x<=l && r<=y)
    {
        insert(p,f[root],inf);
        return;
    }
    int mid =(l+r) >> 1;
    if (x<=mid) update(2*root,l,mid,x,y,p);
    if (y>mid) update(2*root+1,mid+1,r,x,y,p);
}

void update1(int root,int l,int r,int x,int y,int p)
{
    if (x<=l && r<=y)
    {
        insert(g[root],p,inf);
        return;
    }
    int mid =(l+r) >> 1;
    if (x<=mid) update1(2*root,l,mid,x,y,p);
    if (y>mid) update1(2*root+1,mid+1,r,x,y,p);
}

bool bfs(int s)
{
    memset(h,-1,sizeof(h));
    h[s]=0;
    q.push(s);
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        for (int i=point[x];i;i=nxt[i])
        {
            int p = to[i];
            if (val[i]>0 && h[p]==-1)
            {
                h[p]=h[x]+1;
                q.push(p);
            }
        }
    }
    //cout<<1<<endl;
    if (h[t]==-1) return false;
    else return true;
}

int dfs(int x,int low)
{
    if (x==t || low==0) return low;
    int totflow=0;
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (val[i]>0 && h[p]==h[x]+1)
        {
            int tmp = dfs(p,min(low,val[i]));
            low-=tmp;
            totflow+=tmp;
            val[i]-=tmp;
            val[i^1]+=tmp;
            if (low==0) return totflow;
        }
    }
    if (low>0) h[x]=-1;
    return totflow;
}

int dinic()
{
    int ans=0;
    while (bfs(s))
    {
        ans=ans+dfs(s,inf);
    }
    return ans;
}

int b[maxn],w[maxn];
int main()
{
  freopen("nicai.in","r",stdin);
  freopen("nicai.out","w",stdout);
  n=read(),m=read();
  build(1,1,n);
  build1(1,1,n);
  s=maxn-100;
  t=s+1;
  for (int i=1;i<=n;i++) b[i]=read();
  for (int i=1;i<=n;i++) w[i]=read();
  for (int i=1;i<=n;i++)
  {
    if (b[i]>=0) insert(s,leaf[i],b[i]);
    else insert(leaf[i],t,-b[i]);
  }
  for (int i=1;i<=n;i++)
  {
    if (w[i]>=0) insert(leaf[i],t,w[i]);
    else insert(s,leaf[i],-w[i]);
  }
  for (int i=1;i<=n;i++)
  {
    if (b[i]>0) ymh=ymh+b[i];
    if (w[i]>0) ymh=ymh+w[i];
  }
  for (int i=1;i<=m;i++)
  {
     int l,r,opt,x;
     opt=read();
     l=read();
     r=read();
     x=read();
     ++tmp;
     if (opt==1)
     {
        insert(s,tmp,x);
        update(1,1,n,l,r,tmp);
     }
     if (opt==2)
     {
        insert(tmp,t,x);
        update1(1,1,n,l,r,tmp);
     }
     ymh+=x;
  }
  //cout<<ymh<<endl;
  cout<<ymh-dinic();
  return 0;
}

原文地址:https://www.cnblogs.com/yimmortal/p/10160863.html

时间: 2024-10-19 20:41:21

一个神秘的oj2587 你猜是不是dp(线段树优化建图)的相关文章

【bzoj4276】[ONTAK2015]Bajtman i Okr?g?y Robin 线段树优化建图+费用流

题目描述 有n个强盗,其中第i个强盗会在[a[i],a[i]+1],[a[i]+1,a[i]+2],...,[b[i]-1,b[i]]这么多段长度为1时间中选出一个时间进行抢劫,并计划抢走c[i]元.作为保安,你在每一段长度为1的时间内最多只能制止一个强盗,那么你最多可以挽回多少损失呢? 输入 第一行包含一个正整数n(1<=n<=5000),表示强盗的个数. 接下来n行,每行包含三个正整数a[i],b[i],c[i](1<=a[i]<b[i]<=5000,1<=c[i]

【bzoj3073】[Pa2011]Journeys 线段树优化建图+堆优化Dijkstra

题目描述 Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路.N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在xy之间建造一条道路.Seter保证不会有一个国家与自己之间有道路. Seter好不容易建好了所有道路,他现在在位于P号的首都.Seter想知道P号国家到任意一个国家最少需要经过几条道路.当然,Se

【BZOJ3681】Arietta 树链剖分+可持久化线段树优化建图+网络流

[BZOJ3681]Arietta Description Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中.但是她从未停止过和恋人 Velding 的书信往来.一天,她准备去探访他.对着窗外的阳光,临行前她再次弹起了琴.她的琴的发声十分特殊.让我们给一个形式化的定义吧.所有的 n 个音符形成一棵由音符 C ( 1 号节点) 构成的有根树,每一个音符有一个音高 Hi .Arietta 有 m 个力度,第 i 个力度能弹出 Di 节点的子树中,音高在 [Li,R

【ARC069F】Flags 2-sat+线段树优化建图+二分

Description ? 数轴上有 n 个旗子,第 ii 个可以插在坐标 xi或者 yi,最大化两两旗子之间的最小距离. Input ? 第一行一个整数 N. ? 接下来 N 行每行两个整数 xi,yi Output ? 一个整数表示答案. Sample Input Sample #1 3 1 3 2 5 1 9 Sample #2 5 2 2 2 2 2 2 2 2 2 2 Sample #3 22 93 6440 78 6647 862 11 8306 9689 798 99 801 52

Codeforces 787D. Legacy 线段树优化建图+最短路

output standard output Rick and his co-workers have made a new radioactive formula and a lot of bad guys are after them. So Rick wants to give his legacy to Morty before bad guys catch them. There are n planets in their universe numbered from 1 to n.

机房测试5:reverse(bfs+set 或 线段树优化建图)

题目: 分析: 首先画样例分析一下,会发现如果要求一个位置要多少次翻转,就将这个位置向与它关联的点连边(关联点指的是可能与它值互换的位置),一直连到起点为止,连边的次数即为它所需步数. 所以转换成求单源最短路,因为边权为1,可以用bfs. 但是这道题n的范围很大,刚刚的做法是n*k的,考虑优化. 法1:在建图上优化 题目要求的是区间翻转,所以也对应着相关性质:每个点连边一定是都连的奇数点或偶数点(画图可知),且这些奇数偶数点都对应着一段连续的区间. 如果可以将点向点连边优化成点向区间连边,复杂度

bzoj5017 [Snoi2017]炸弹 (线段树优化建图+)tarjan 缩点+拓扑排序

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5017 题解 这个题目方法挺多的. 线段树优化建图 线段树优化建图的做法应该挺显然的,一个炸弹能够引爆的炸弹的显然应该是一个区间里面的,直接对这个区间进行线段树优化建图. 这样可以得到一个带环图,缩点以后这个炸弹能够炸到的炸弹就是从这个点能够走到的点. 但是这个不太好做,不过可以发现最终的炸弹也是一个区间,所以可以通过缩点后的 DAG 来求出左右端点. 时间复杂度 \(O(n\log n)\)

[POI2015][bzoj4383] Pustynia [线段树优化建图+拓扑排序]

题面 bzoj权限题传送门 luogu传送门 思路 首先,这个题目显然可以从所有小的点往大的连边,然后如果没环就一定可行,从起点(入读为0)开始构造就好了 但是问题来了,如果每个都连的话,本题中边数是$O(n^2)$级别的,显然会挂 发现两条性质: 1.所有的限制条件中,给定的总点数不超过3e5个 2.是一个点比一段区间大 第二个条件决定了我们可以利用线段树优化建图,而第一个条件告诉了我们,本题的总边数应该是$sumk\astlog_2n$级别的 那么就做完了 注意拓扑排序的时候有个技巧,把连向

[bzoj5017][Snoi2017]炸弹 tarjan缩点+线段树优化建图+拓扑

5017: [Snoi2017]炸弹 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 608  Solved: 190[Submit][Status][Discuss] Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi?Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆. 现在,请你帮忙计算一下,先把第 i 个炸弹引爆,将引爆多少个炸弹呢? Inp