一个神秘的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;
}