【BZOJ 3218】 a + b Problem

来源:互联网 发布:淘宝信誉查询 编辑:程序博客网 时间:2024/06/05 10:36

3218: a + b Problem

Time Limit: 20 Sec  Memory Limit: 40 MB
Submit: 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,就会产生为流量的代价

1 0