[BZOJ3218]a+b Problem
壹、题目
贰、思考
似乎和文理分科有点像,但是这个题的要求是有异色的是 "奇怪" 的。
考虑正难则反,如果一个点是奇怪的,那么它的贡献就是 \(b_i-p_i\),反之,如果它不是奇怪的,就是 \(b_i\) 了,如果要求一个点不是 "奇怪" 的,那么就要求所有满足 \(1\le j<i,l_i\le a_j\le r_i\) 的点都是黑色,也就是说如果这些点都是黑色,就会有额外的贡献。
这就和文理分科十分像了,考虑源点黑,汇点白,划分两个点集,首先从 \(s\) 向每个点连接 \(b_i-p_i\) 的边,意味着选择黑色,然后从每个点向汇点连接 \(w_i\) 的边,意味着选择白色,考虑都是黑色的,就建虚点,对于每个虚点,从源点连接一条边权为对应格子的 \(p_i\),然后从这个虚点向每个满足条件的格子连接一个 \(+\infty\),然后直接跑最大流求最小割,最后的答案就是所有的点减去最小割。
然后这种方法 \(\tt MLE+TLE\) 了 去吔屎吧,然鹅什么都布吉岛的我布吉岛怎么去优化,然后我 \(\tt GG\) 了。
这个方法有点问题,因为 \(b_i-p_i\) 可能出现负边,但是有补救方法,具体可以看部分 伍 。
真正的解决方案:
从 \(s\) 向每个点连接 \(b_i\) (就是 \(x\))的边,然后从每个点向汇点连接 \(w_i+p_i\) (就是 \(y\))的边,对于每个虚点,从源点连接一条边权为对应格子的 \(p_i\)(就是 \(z\)),然后从这个虚点向每个满足条件的格子连接一个 \(+\infty\),然后直接跑最大流求最小割,最后的答案就是所以点的 \(w_i+b_i+p_i\) 减去这个图上的最小割。
叁、题解
好不容易想出来建图,然后布吉岛怎么优化......
考虑我们原先的建图方式需要多少空间?\(\mathcal O(n^2)\),那还是去自闭吧......
使用传说中的线段树优化建图,但是是二维偏序,所以还要考虑可持久化,而在线段树上的边,需要注意的是全部的值都应该赋为 \(+\infty\) 表示不可断掉。
可耻就话的时候不仅要继承点,还要继承边,具体实现看代码。
肆、代码
还没跳出来,然鹅不想调了,先留个坑在这里吧......
我TM终于把坑填了......调出来啦,是线段树函数里面 \(l\) 初值的问题......
using namespace Elaina;
const int maxn=5e3;
const int inf=(1<<30)-1;
int a[maxn+5],b[maxn+5],w[maxn+5],l[maxn+5],r[maxn+5],p[maxn+5];
int n,maxa;
ll ans=0;
inline void input(){
n=readin(1);
rep(i,1,n)a[i]=readin(1),b[i]=readin(1),w[i]=readin(1),l[i]=readin(1),r[i]=readin(1),p[i]=readin(1);
rep(i,1,n)maxa=Max(Max(maxa,a[i]),Max(l[i],r[i]));
}
struct edge{int to,nxt,c;
edge(const int T=0,const int N=0,const int C=0):to(T),nxt(N),c(C){}
}e[maxn*200+5];
int tail[maxn*200+5],ecnt;
int cur[maxn*200+5];
int ncnt;
inline void add_edge(const int u,const int v,const int c){
e[++ecnt]=edge(v,tail[u],c);tail[u]=ecnt;
e[++ecnt]=edge(u,tail[v],0);tail[v]=ecnt;
}
inline void update(const int i,const int x){
e[i].c-=x,e[i^1].c+=x;
}
int rt[maxn+5];
int ls[maxn*200+5],rs[maxn*200+5];
#define mid ((l+r)>>1)
/**
* @param x 新建的当前点
* @param i 前一个版本的点
* @param p 从树连到 p 点上面
* @param a p 所对应的点的 a[] 的值
*/
void modify(int& x,const int p,const int a,const int i,const int l=0,const int r=maxa){
x=++ncnt;
ls[x]=ls[i],rs[x]=rs[i];
// 相当于继承前一个版本的俩儿子
if(i)add_edge(x,i,inf);
// 到底层了, 加边走人
if(l==r)return add_edge(x,p,inf);
if(a<=mid){
modify(ls[x],p,a,ls[i],l,mid);
add_edge(x,ls[x],inf);
}
else{
modify(rs[x],p,a,rs[i],mid+1,r);
add_edge(x,rs[x],inf);
}
}
/**
* @param L 询问左端点
* @param R 询问右端点
* @param p 这个点往树上连边
* @param i 现在在树上的哪个点
*/
void query(const int L,const int R,const int p,const int i,const int l=0,const int r=maxa){
if(!i)return;
if(L>R)return;
if(L<=l && r<=R)return add_edge(p,i,inf);
if(L<=mid)query(L,R,p,ls[i],l,mid);
if(mid<R)query(L,R,p,rs[i],mid+1,r);
}
inline void build(){
memset(tail,-1,sizeof tail);ecnt=-1;
rep(i,1,n){
ans=ans+b[i]+w[i]+p[i];
add_edge(0,i,b[i]);
add_edge(i,n+1,w[i]+p[i]);
}
ncnt=n+1;
rep(i,1,n){
int nde=++ncnt;
add_edge(0,nde,p[i]);
add_edge(nde,i,inf);
if(i>1)query(l[i],r[i],nde,rt[i-1]);
modify(rt[i],i,a[i],rt[i-1]);
}
}
int dis[maxn*200+5];
queue<int>Q;
inline int bfs(){
memset(dis,-1,(ncnt+1)<<2);
while(!Q.empty())Q.pop();
dis[0]=0;Q.push(0);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=tail[u],v;~i;i=e[i].nxt)if(e[i].c){
v=e[i].to;
if(dis[v]==-1){
dis[v]=dis[u]+1;
Q.push(v);
}
}
}
return dis[n+1]!=-1;
}
int dfs(const int u,const int flow,const int t){
if(u==t)return flow;
int rest=flow;
for(int& i=cur[u];~i;i=e[i].nxt)if(e[i].c){
int v=e[i].to;
if(dis[v]==dis[u]+1){
int k=dfs(v,Min(rest,e[i].c),t);
rest-=k,update(i,k);
if(rest==0)break;
}
}
return flow-rest;
}
inline void dinic(){
while(bfs()){
memcpy(cur,tail,(ncnt+1)<<2);
ans-=dfs(0,inf,n+1);
}
writc(ans,'\n');
}
signed main(){
input();
build();
dinic();
return 0;
}
伍、思路の一些问题及补救措施
我们加边时要加容量为 \(b_i-p_i\) 的边,会出现负容量,这个其实是有补救措施的。