[BZOJ 3218] A+B Problem
Link:
Solution:
由于染色将点集分为两块,想到最小割模型
最大化权值可以看成总和减去最小化损失,于是由“最大割” ----> 最小割
(1)网络流建图
$<S,i,b[i]>$割掉表示选白色,$<i′,T,w[i]>$割掉表示选黑色,
接下来对于“奇怪的点对”$(i,j)$,连接$<i,j,p[i]>$,表示如果同时保留$<S,i,b[i]>$和$<j,T,w[j]>$就要割掉$<i,j,p[i]>$
但这样仍存在问题:对于不同的$j$,$p[i]$会计算多次
于是我们新增节点$i''$,$<i,i′',p[i]>$保证$p[i]$只计算1次,$<i′',j,INF>$表示和$j$是不会被割的
这样直接求出$mincut$,然后答案就是$\sum {(b[i]+w[i])}−mincut$
(2)数据结构优化边数
但这样的边数是$O(n^2)$,$MLE+TLE$双喜临门
注意影响的范围是一段区间,所以可以用线段树的节点去表示对一个区间的影响,来达到优化的目的
即由连接$<i′',j,INF>$改为连$<i′',seg[l,r],INF>$
此外还要连接线段树中父子关系:$<fa,ch[0/1],INF>$,以及叶子节点及其对应的点:$<leaf_i,i,INF>$。
由于 V(du)F(liu)K 要求$1<=j<i$,强行可持久化,于是将线段树要改为可持久化线段树
这样边数和点数都是$n*log(n)$的,就可以通过了。
Note:
这里有一个问题就是$a[i]$可能多次出现,于是要保证连接一个$a[i]$后便等效于连接了所有$a[i]$
所以需要新版本的$a[i]$叶子连向老版本的$a[i]$叶子$<new,old,INF>$
Code:
#include <bits/stdc++.h> using namespace std; #define mid (l+r)/2 const int MAXN=1e5+10; const int INF=1<<27; struct testdata{int a,b,w,l,r,p;}dat[MAXN]; int n,S,T,cnt=0,dsp[MAXN],root[MAXN],tot=0,res=0; namespace MaxFlow //最大流 { int level[MAXN],iter[MAXN]; struct edge{int to,cap,rev;}; vector<edge> G[MAXN]; inline void add_edge(int from,int to,int cap) { G[from].push_back(edge{to,cap,G[to].size()}); G[to].push_back(edge{from,0,G[from].size()-1}); } bool bfs() { memset(level,-1,sizeof(level)); queue<int> que;que.push(S);level[S]=0; while(!que.empty()) { int u=que.front();que.pop(); for(int i=0;i<G[u].size();i++) { edge &e=G[u][i]; if(e.cap && level[e.to]==-1) level[e.to]=level[u]+1,que.push(e.to); } } return (level[T]>0); } int dfs(int v,int f) { if(v==T) return f; for(int &i=iter[v];i<G[v].size();i++) { edge &e=G[v][i]; if(e.cap && level[e.to]==level[v]+1) { int d=dfs(e.to,min(e.cap,f)); if(d) { e.cap-=d; G[e.to][e.rev].cap+=d; return d; } } } return 0; } int dinic() { int ret=0; while(bfs()) { memset(iter,0,sizeof(iter));int f; while((f=dfs(S,INF))>0) ret+=f; } return ret; } }; namespace PrTree //主席树 { int ch[MAXN][2]; void Insert(int &x,int y,int pos,int id,int l,int r) { x=++cnt;ch[x][0]=ch[y][0];ch[x][1]=ch[y][1]; if(l==r) { MaxFlow::add_edge(2*n+x,id,INF); if(y) MaxFlow::add_edge(2*n+x,2*n+y,INF); //对相同点的特殊处理 return; } if(pos<=mid) Insert(ch[x][0],ch[y][0],pos,id,l,mid); else Insert(ch[x][1],ch[y][1],pos,id,mid+1,r); if(ch[x][0]) MaxFlow::add_edge(2*n+x,2*n+ch[x][0],INF); if(ch[x][1]) MaxFlow::add_edge(2*n+x,2*n+ch[x][1],INF); } void Query(int cur,int a,int b,int id,int l,int r) { if(!cur) return; if(a<=l && r<=b) { MaxFlow::add_edge(id,2*n+cur,INF); return; } if(a<=mid) Query(ch[cur][0],a,b,id,l,mid); if(b>mid) Query(ch[cur][1],a,b,id,mid+1,r); } }; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d%d%d%d%d",&dat[i].a,&dat[i].b,&dat[i].w,&dat[i].l,&dat[i].r,&dat[i].p), res+=(dat[i].b+dat[i].w); for(int i=1;i<=n;i++) //离散化 dsp[++tot]=dat[i].a,dsp[++tot]=dat[i].l,dsp[++tot]=dat[i].r; sort(dsp+1,dsp+tot+1);tot=unique(dsp+1,dsp+tot+1)-dsp-1; for(int i=1;i<=n;i++) dat[i].a=lower_bound(dsp+1,dsp+tot+1,dat[i].a)-dsp, dat[i].l=lower_bound(dsp+1,dsp+tot+1,dat[i].l)-dsp, dat[i].r=lower_bound(dsp+1,dsp+tot+1,dat[i].r)-dsp; S=0; for(int i=1;i<=n;i++) PrTree::Insert(root[i],root[i-1],dat[i].a,i,1,tot), PrTree::Query(root[i-1],dat[i].l,dat[i].r,i+n,1,tot); T=2*n+cnt+1; for(int i=1;i<=n;i++) //建图 MaxFlow::add_edge(S,i,dat[i].b), MaxFlow::add_edge(i,T,dat[i].w), MaxFlow::add_edge(i,i+n,dat[i].p); printf("%d",res-MaxFlow::dinic()); return 0; }
Review:
很吼的一道题目啊
(1)对点集分类,想到最小割
同时如果求“最大割”,由最小代价来转化
(2)如果对于多个二元组只计算一次权值时,拆点!
连边$<i,i'',cost_i>$来保证只计算一次权值,而$<i′',j,INF>$保证不会计入答案
(3)数据结构“区间建图”
第一次见到的操作(orz VFK),如果连边时是区间操作,考虑使用连接RMQ数据结构上节点的方式来优化边数
(4)如果要保证$1<=j<i$,记得可持久化
其实就是记录前缀
(5)如果数据本身不重要,只考虑大小关系时,使用离散化简化数据集合