3218: a + b Problem
Time Limit: 20 Sec Memory Limit: 40 MBSubmit: 481 Solved: 180
[ Submit][ Status]
Description
非常神的题,与标题没有任何关系。。
用可持久化线段树优化网络流(最小割)。
先说一下最小割:
当v向u连一条流量为w的边,如果uv同属于S集或T集,或者u属于S集v属于T集,就不会产生代价。
如果u属于T集,v属于S集,就会产生w的代价。
这道题是用所有wi,bi的和减去最小割。
那么S向i点连流量为bi的边,i点向T连流量为wi的边;如果i属于S集,则他被染成黑色,反之是白色。
那么奇怪的方格怎么处理?
新建一个点i',i向i'连流量为pi的边;那么当i属于S集,i'属于T集,就会产生pi的代价。
因此i'向所有(j<i,li<=ai<=ri)的j点连流量为inf的边,此时就满足奇怪的方格了。
可是这样连边,边数是O(n^2)的。一般网络流最多能跑10w条边,所以进行优化。
先忽略奇怪的格子要满足j<i的条件,那么题目就变成了对权值在一段区间的点连边,所以我们可以离散化之后建一棵权值线段树!
那么现在的i'不是直接连向j了,现在变成了 i'-->线段树的一段区间-->区间中的所有j。
然后再考虑j<i的条件,我们可以建可持久化线段树,每一次先连边,再把当前点插入线段树即可。
对于可持久化线段树的连边,当前是第i个数插入,那么新建的所有点都向i连inf的边,因为所有新建的点都包含i;
并且所有新建的点要向第i-1个数的对应点连inf的边,因为他是可持久化的,于是边就连好了。
现在边数就变成O(nlogn)的了~
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#define pb push_back
#define inf 0x3f3f3f3f
#define M 500005
#define N 5005
#include <queue>
using namespace std;
int z,n,d[M],cur[M],h[M],v[M],g[N],r[N],a[N],s,t,ans,l[N],size,cnt=0,tot=1,rt[N];
struct edge
{
int from,to,cap,flow,ne;
}E[M];
struct segtree
{
int l,r;
}T[M];
void Addedge(int from,int to,int cap)
{
E[++tot]=(edge){from,to,cap,0,h[from]};
h[from]=tot;
E[++tot]=(edge){to,from,0,0,h[to]};
h[to]=tot;
}
int Hash(int x)
{
return lower_bound(g+1,g+1+size,x)-g;
}
void Add(int x,int lx,int rx,int l,int r,int k)
{
if (!x) return;
if (lx>=l&&rx<=r)
{
Addedge(k,x,inf);
return;
}
int m=(lx+rx)>>1;
if (l<=m) Add(T[x].l,lx,m,l,r,k);
if (r>m) Add(T[x].r,m+1,rx,l,r,k);
}
void Update(int u,int x)
{
int root=rt[u-1];
rt[u]=++cnt;
int now=cnt;
int l=1,r=size;
while (1)
{
int m=(l+r)>>1;
if (root) Addedge(now,root,inf);
Addedge(now,u,inf);
if (l==r) break;
if (x<=m)
{
T[now].l=++cnt;
T[now].r=T[root].r;
root=T[root].l;
now=cnt;
r=m;
}
else
{
T[now].l=T[root].l;
T[now].r=++cnt;
root=T[root].r;
now=cnt;
l=m+1;
}
}
}
bool bfs()
{
memset(v,0,sizeof(v));
queue<int> Q;
Q.push(s);
d[s]=0,v[s]=1;
while (!Q.empty())
{
int x=Q.front();
Q.pop();
for (int i=h[x];i;i=E[i].ne)
{
edge e=E[i];
if (!v[e.to]&&e.cap>e.flow)
{
v[e.to]=1;
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}
return v[t];
}
int dfs(int x,int a)
{
if (x==t||!a) return a;
int flow=0,f;
for (int &i=cur[x];i;i=E[i].ne)
{
edge &e=E[i];
if (d[x]+1!=d[e.to]) continue;
f=dfs(e.to,min(a,e.cap-e.flow));
if (f>0)
{
e.flow+=f;
E[i^1].flow-=f;
flow+=f;
a-=f;
if (a==0) break;
}
}
return flow;
}
int dinic()
{
int maxflow=0;
while (bfs())
{
for (int i=s;i<=cnt;i++)
cur[i]=h[i];
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main()
{
scanf("%d",&n);
s=0,t=n+n+1;
ans=0;
for (int i=1;i<=n;i++)
{
int b,w,p;
scanf("%d%d%d%d%d%d",&a[i],&b,&w,&l[i],&r[i],&p);
g[i]=a[i];
ans=ans+b+w;
Addedge(s,i,b);
Addedge(i,t,w);
Addedge(i,i+n,p);
}
sort(g+1,g+1+n);
size=unique(g+1,g+1+n)-g-1;
cnt=t;
for (int i=1;i<=n;i++)
{
z=0;
int le=Hash(l[i]),ri=upper_bound(g+1,g+1+size,r[i])-g-1,now=Hash(a[i]);
Add(rt[i-1],1,size,le,ri,i+n);
Update(i,now);
}
printf("%d\n",ans-dinic());
return 0;
}
感悟:
1.一开始wa了几次:
在Add操作中要对于每一个新建点都向之前对应点连边,包括root;
lower_bound是找大于等于他的数第一个出现的位置,upper_bound是找一个数插入这个已排好序的数列的最后的可行位置
2.对于最小割,两点之间连边,满足一个(确定的)属于S,另一个属于T,就会产生为流量的代价