「2019CCPC哈爾濱站A」 Artful Paintings【二分+差分約束】

題目鏈接

題意

  • 就是有一個長度爲nn的只含有0011的數組,開始你不知道數組的內容,給你兩種描述:

    • 第一種表示區間[li,ri][l_i,r_i]內至少含有kik_i11
    • 第二種表示除去區間[li,ri][l_i,r_i]的剩下部分至少含有kik_i11

    然後讓你求在滿足所有這些給定的限制條件下,整個數組最少含有多少個11

題解

  • 首先這個題和17CCPC哈爾濱站L有相似之處,只是把樹上問題放到了序列上,問題就變複雜了一些
  • 爲什麼變複雜了呢?首先那個題的題解鏈接先貼上:17CCPC哈爾濱站題解,可以看到爲什麼那個題可以dpdp,原因就在於那個題實際上是利用了區間包含的特點,而沒有區間交叉的情況,因爲當前節點的子樹一定包含所有以兒子爲根節點的子樹,所以只有包含關係,當放到序列上時,就會產生交叉情況
  • 另外如果你做過POJ3169的話,那麼這個題應該能根塊想出來做法
  • 首先容易想到的是如果答案是ansans,只要ans<nans<n那麼ans+1ans+1也一定可以滿足給定的所有條件,因爲你可以在任意一個空的位置放上一個11,所以答案具有單調性,考慮二分,關鍵是怎麼checkcheck的問題,考慮前綴和sumsum,首先對於第一種描述,可以轉化爲sum[ri]sum[li1]kisum[r_i]-sum[l_i-1]\geq k_i,那麼對於第二種描述,考慮轉化一下:區間[li,ri][l_i,r_i]內的1的個數最多有midkimid-k_i個,即sum[ri]sum[li1]midkisum[r_i]-sum[l_i-1]\leq mid-k_i,然後還有一些隱含的條件那就是 i[1,n],0sum[i]sum[i1]1\forall\ i \in [1,n], 0\leq sum[i]-sum[i-1] \leq 1,以及sum[n]sum[0]midsum[n]-sum[0]\leq mid,根據這些約束條件建圖(具體建圖方法參見挑戰P111P111),然後跑spfaspfa判斷是否有負環,如果有則差分約束系統無解,即二分的midmid太小
  • 另外有一個剪枝技巧就是如果跑spfaspfa中途遇到有某個節點的disdis值小於零,那麼一定有負環,原因是由於i[1,n]\forall i \in[1,n],節點ii向節點i1i-1連了一條去哪治爲0的邊,那麼就是說對於任意一個節點ii到節點0都有一條最長是0的長度的最短路,那麼如果0到某個節點的disdis值小於00就一定有負環
  • 假了這個優化只用了31ms31ms就跑完了,目前這份代碼的提交在Codeforces的提交是所有提交當中跑的最快的(其中_TLE_和wzw19991105都是我的號)46ms46ms的提交用的是C++ STL的dequedeque31ms31ms是自己手寫了個dequedeque
    在這裏插入圖片描述
    剪枝前是這樣的(差一點沒過去)
    在這裏插入圖片描述

代碼

#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; 
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章