「2019CCPC哈尔滨站A」 Artful Paintings【二分+差分约束】

题目链接

题意

  • 就是有一个长度为 n n n的只含有 0 0 0 1 1 1的数组,开始你不知道数组的内容,给你两种描述:

    • 第一种表示区间 [ l i , r i ] [l_i,r_i] [li,ri]内至少含有 k i k_i ki 1 1 1
    • 第二种表示除去区间 [ l i , r i ] [l_i,r_i] [li,ri]的剩下部分至少含有 k i k_i ki 1 1 1

    然后让你求在满足所有这些给定的限制条件下,整个数组最少含有多少个 1 1 1

题解

  • 首先这个题和17CCPC哈尔滨站L有相似之处,只是把树上问题放到了序列上,问题就变复杂了一些
  • 为什么变复杂了呢?首先那个题的题解链接先贴上:17CCPC哈尔滨站题解,可以看到为什么那个题可以 d p dp dp,原因就在于那个题实际上是利用了区间包含的特点,而没有区间交叉的情况,因为当前节点的子树一定包含所有以儿子为根节点的子树,所以只有包含关系,当放到序列上时,就会产生交叉情况
  • 另外如果你做过POJ3169的话,那么这个题应该能根块想出来做法
  • 首先容易想到的是如果答案是 a n s ans ans,只要 a n s < n ans<n ans<n那么 a n s + 1 ans+1 ans+1也一定可以满足给定的所有条件,因为你可以在任意一个空的位置放上一个 1 1 1,所以答案具有单调性,考虑二分,关键是怎么 c h e c k check check的问题,考虑前缀和 s u m sum sum,首先对于第一种描述,可以转化为 s u m [ r i ] − s u m [ l i − 1 ] ≥ k i sum[r_i]-sum[l_i-1]\geq k_i sum[ri]sum[li1]ki,那么对于第二种描述,考虑转化一下:区间 [ l i , r i ] [l_i,r_i] [li,ri]内的1的个数最多有 m i d − k i mid-k_i midki个,即 s u m [ r i ] − s u m [ l i − 1 ] ≤ m i d − k i sum[r_i]-sum[l_i-1]\leq mid-k_i sum[ri]sum[li1]midki,然后还有一些隐含的条件那就是 ∀   i ∈ [ 1 , n ] , 0 ≤ s u m [ i ] − s u m [ i − 1 ] ≤ 1 \forall\ i \in [1,n], 0\leq sum[i]-sum[i-1] \leq 1  i[1,n],0sum[i]sum[i1]1,以及 s u m [ n ] − s u m [ 0 ] ≤ m i d sum[n]-sum[0]\leq mid sum[n]sum[0]mid,根据这些约束条件建图(具体建图方法参见挑战 P 111 P111 P111),然后跑 s p f a spfa spfa判断是否有负环,如果有则差分约束系统无解,即二分的 m i d mid mid太小
  • 另外有一个剪枝技巧就是如果跑 s p f a spfa spfa中途遇到有某个节点的 d i s dis dis值小于零,那么一定有负环,原因是由于 ∀ i ∈ [ 1 , n ] \forall i \in[1,n] i[1,n],节点 i i i向节点 i − 1 i-1 i1连了一条去哪治为0的边,那么就是说对于任意一个节点 i i i到节点0都有一条最长是0的长度的最短路,那么如果0到某个节点的 d i s dis dis值小于 0 0 0就一定有负环
  • 假了这个优化只用了 31 m s 31ms 31ms就跑完了,目前这份代码的提交在Codeforces的提交是所有提交当中跑的最快的(其中_TLE_和wzw19991105都是我的号) 46 m s 46ms 46ms的提交用的是C++ STL的 d e q u e deque deque 31 m s 31ms 31ms是自己手写了个 d e q u e deque deque
    在这里插入图片描述
    剪枝前是这样的(差一点没过去)
    在这里插入图片描述

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=3005;
const int maxm=100005;

int n,m1,m2,head[maxn],tot,dis[maxn],inq[maxn],cnt[maxn];
struct opt{int l,r,w;}a[maxn],b[maxn];
struct edge{int u,v,w,type,next;}e[maxm];
void add_edge(int u,int v,int w,int t) {e[++tot]=edge{u,v,w,t,head[u]};head[u]=tot;}

struct dequeue_{
    int a[maxn],L,R,siz=0;
    bool empty() {return siz==0;}
    dequeue_(int l=1,int r=0,int siz_=0) {L=l,R=r,siz=siz_;} 
    void clear() {
        L=1,R=0,siz=0;
    }
    bool push_front(int val) {
        if(siz==maxn-1) return false;
        if(L==1) L=maxn-1,a[L]=val,siz++;
        else L--,a[L]=val,siz++;
        return true;
    }
    bool push_back(int val) {
        if(siz==maxn-1) return false;
        if(R==maxn-1) R=1,a[R]=val,siz++;
        else R++,a[R]=val,siz++;
        return true;
    } 
    int pop_front() {
        if(siz==0) return 0;
        int ans=0;
        if(L==maxn-1) ans=a[L],L=1,siz--;
        else ans=a[L],L++,siz--;
        return ans;
    }
    int pop_back() {
        if(siz==0) return 0;
        int ans=0;
        if(R==1) ans=a[R],R=maxn-1,siz--;
        else ans=a[R],R--,siz--;
        return ans;
    }
    int front() {return a[L];}
    int back() {return a[R];}
};
bool spfa(int s,int mid) {
    for(int i=0;i<=n;i++) dis[i]=0x3f3f3f3f,inq[i]=0,cnt[i]=0;
    dequeue_ que;
    que.push_front(s);
    inq[s]=1,dis[s]=0,cnt[0]=1;
    while(!que.empty()) {
        int cur=que.front();
        que.pop_front();
        inq[cur]=0;
        for(int i=head[cur];i;i=e[i].next) {
            int weight=e[i].type?mid+e[i].w:e[i].w;
            if(dis[e[i].v] > dis[e[i].u]+weight) {
                dis[e[i].v]= dis[e[i].u]+weight;
                if(dis[e[i].v] < 0) return false; //根据建图特点剪枝
                if(!inq[e[i].v]) {
                    if(!que.empty() && dis[e[i].v]>=dis[que.front()]) que.push_back(e[i].v);
                    else que.push_front(e[i].v);  //SLF优化
                    cnt[e[i].v]++,inq[e[i].v]=1;
                    if(cnt[e[i].v]>n) return false;
                }
            }
        }
    }
    return true;
}

bool check(int mid) {
    e[tot].w=-mid,e[tot-1].w=mid;  //修改这两条边的权值
    return spfa(0,mid);
}
int main() {
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d %d %d",&n,&m1,&m2);
        for(int i=1;i<=m1;i++) scanf("%d %d %d",&a[i].l,&a[i].r,&a[i].w);
        for(int i=1;i<=m2;i++) scanf("%d %d %d",&b[i].l,&b[i].r,&b[i].w);
        for(int i=1;i<=n;i++) add_edge(i,i-1,0,0),add_edge(i-1,i,1,0);
        for(int i=1;i<=m1;i++) add_edge(a[i].r,a[i].l-1,-a[i].w,0);
        for(int i=1;i<=m2;i++) add_edge(b[i].l-1,b[i].r,-b[i].w,1);
        add_edge(0,n,0,0),add_edge(n,0,0,0);
        int l=0,r=n,ans=-1;  //二分答案
        while(l<=r) {
            int mid=(l+r)>>1;
            if(check(mid)) r=mid-1,ans=mid;
            else l=mid+1;
        }
        printf("%d\n",ans);
        for(int i=0;i<=n;i++) head[i]=0; //清空
        tot=0; 
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值