[BZOJ3218]a+b Problem

壹、题目

传送门 to Darkbzoj

贰、思考

似乎和文理分科有点像,但是这个题的要求是有异色的是 "奇怪" 的。

考虑正难则反,如果一个点是奇怪的,那么它的贡献就是 \(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\) 的边,会出现负容量,这个其实是有补救措施的。

专门做成了一个总结

posted @ 2021-02-06 12:28  Arextre  阅读(128)  评论(0编辑  收藏  举报