【BZOJ 3218】 a + b Problem

3218: a + b Problem

Time Limit: 20 Sec   Memory Limit: 40 MB
Submit: 481   Solved: 180
[ Submit][ Status]

Description

【BZOJ 3218】 a + b Problem_第1张图片

【BZOJ 3218】 a + b Problem_第2张图片


非常神的题,与标题没有任何关系。。


用可持久化线段树优化网络流(最小割)。


先说一下最小割:

当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,就会产生为流量的代价

你可能感兴趣的:(网络流,OI,bzoj,可持久化线段树)