2019CCPC哈尔滨A题——差分约束系统+二分

题目链接:https://codeforces.com/gym/102394/problem/A

题目大意:

 有N≤3e3个格子,你可以任意给每个格子染色,但是要满足M≤3e3限制条件,限制条件有两种类型:
1. 区间[l,r]中被染色的格子数量不少于K。
2. 区间[l,r]外被染色的格子数量不少于K。
在满足所有限制条件下求染色格子数量的最小值。
题解:

算作是差分约束+二分的板子题,比赛时还不知道差分约束是什么,当时一点思路都没有,赛后看了大牛们的讲解才理解,其实就是把一些不等式问题转换成最短路或者最长路,不懂得可以看看这篇文章:http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html

写的是真的好,里面讲的一道例题就和这个题差不多。

首先因为染色格子的数量是越多越好,即满足递增的,那么我们就可以二分枚举染色格子的总数量mid

我们很容易能写出以下不等式(注意那些隐藏条件,因为差分约束正确的前提是满足所有条件):

S_{r}-S_{l-1}\leqslant mid-K

S_{r}-S_{l-1}\geqslant K \Rightarrow S_{l-1}-S_{r}\leqslant -K 

S_{i}-S_{i-1}\leqslant 1

S_{i}-S_{i-1}\geqslant 0\Rightarrow S_{i-1}-S_{i}\leqslant 0

S_{n}-S_{0}= mid \Rightarrow S_{n}-S_{0}\leq mid,S_{n}-S_{0}\ge mid \Rightarrow S_{n}-S_{0}\leq mid,S_{0}-S_{n}\le mid

然后我们分别建图判断是否存在0-n的最短路就行了。

trick:这个题有点卡常,用spfa时需要剪枝一下,实测仅用SLF优化也可以过,不过有个更牛皮的优化,具体内容参考代码

代码实现:

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define debug printf("ac\n");
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int INF = 0x3f3f3f3f;
const int N = 3e3+7;
struct edge{//链式前向星存图
    int u,v,w,nxt;
    int flag;//flag为1记做+delta,为2记做-delta,方便进行改变枚举的值的影响
}e[N*10];
int head[N],edgeCnt,dis[N];
deque<int> q;
int cnt[N],inq[N];//常规的SPFA所需的数组
int n,m1,m2,T;
inline void addEdge(int u,int v,int w,int flag){//建有向边
    e[++edgeCnt]={u,v,w,head[u],flag};
    head[u]=edgeCnt;
}
inline void add(int delta){//修改枚举值对边的影响
    rp(i,1,edgeCnt){
        if(e[i].flag==1) e[i].w+=delta;
        if(e[i].flag==2) e[i].w-=delta;
    }
}
int Spfa(int delta){//spfa判断是否有负环
    add(delta);//加上枚举值的影响
    while(!q.empty()) q.pop_front();
    rp(i,0,n){
        dis[i]=(i==0)?0:INF;
        cnt[i]=inq[i]=0;
    }
    q.push_back(0);
    inq[0]=1;
    while(!q.empty()){
        int now=q.front();
        q.pop_front();
        inq[now]=0;
        if(cnt[now]++>n){
            add(-delta);//减去枚举值的影响
            return 0;
        }
        //大力剪枝,如果有一个点的最短路为负,则必定存在负环
        //因为 i->i-1 有一条权值为0的边,而且是从0为起点走的,那么就可以通过走序号不断减1的边回到0,这样就形成了一个负环
        if(dis[now]<0){
            add(-delta);
            return 0;
        }
        for(int i=head[now];i;i=e[i].nxt){
            if(dis[e[i].v]>dis[now]+e[i].w){
                dis[e[i].v]=dis[now]+e[i].w;
                if(!inq[e[i].v]){
                    inq[e[i].v]=1;
                    if(!q.empty()&&dis[e[i].v]<dis[q.front()])//比较常见的SLF优化
                        q.push_front(e[i].v);
                    else
                        q.push_back(e[i].v);
                }
            }
        }
    }
    add(-delta);
    return 1;
}
void init(){//初始化
    mst(head,0); 
    edgeCnt=0;
}
int main(){
    T=read();
    while(T--){
        init();
        n=read(),m1=read(),m2=read();
        while(m1--){
            int l=read(),r=read(),k=read();
            //S(r)-S(l-1)>=k => S(l-1)-S(r)<=-k
            addEdge(r,l-1,-k,0);
        }        
        while(m2--){
            int l=read(),r=read(),k=read();
            //S(r)-S(l-1)<=mid-k
            addEdge(l-1,r,-k,1);
        }
        rp(i,1,n){
            //S(i)-S(i-1)<=1
            //S(i)-S(i-1)>=0 => S(i-1)-S(i)<=0
            addEdge(i-1,i,1,0);
            addEdge(i,i-1,0,0);
        }
        //S(n)-S(0)==mid => S(n)-S(0)>=mid and S(n)-S(0)<=mid
        //S(n)-S(0)>=mid => S(0)-S(n)<=-mid
        addEdge(0,n,0,1);
        addEdge(n,0,0,2);
        int l=0,r=n,ans=-1;
        while(l<=r){//二分枚举符合条件的值
            int mid=(l+r)>>1;
            // cout<<mid<<" "<<Spfa(mid)<<endl;
            if(Spfa(mid)){
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值