【题解】P5979 [PA 2014] Druzyny

P5979 [PA 2014] Druzyny

题意

\(n\) 个人排成一行(从 \(1\)\(n\) 编号),把他们分成若干组,每一组都是编号连续的一段,每个人属于且仅属于一个组。

\(i\) 个人期望它所在的组的人数 \(\in [c_i,d_i]\),否则这个分组方案就是不合法的。

求可以分成的组的数目的最大值,以及有多少种分组方案能达到最大值。

题解

知识点:动态规划,扫描线,线段树,cdq 分治。

远古疑难杂症,年初的时候写了发暴力就跑路了,今天来解决它。

\(f_i\) 表示考虑了前 \(i\) 个,最多能分多少组。

\(g_i\) 表示考虑了前 \(i\) 个,在最优分组数的前提下的有多少种方案。

假设 \(j-1\) 能转移到 \(i\)(即将 \(j\sim i\) 分为一组),则应该满足以下条件:

\[\max_{k=j}^i c_k \le i-j+1 \le \max_{k=j}^i d_k \]

非常棘手的条件,每一项都有 \(i,j\),直接做根本优化不了,我们想要的的条件应该是每一项只有 \(i\) 或者 \(j\)

这时候就可以考虑 cdq 分治了,设当前局面为区间 \([l,r]\),设定一个 \(mid\),使 \(j\in [l,mid]\)\(i\in [mid+1,r]\)

则上面的条件可以拆为两个:

\[\max_{k=j}^{mid} c_k \le i-j+1 \le \max_{k=j}^{mid} d_k \]

\[\max_{k=mid+1}^i c_k \le i-j+1 \le \max_{k=mid+1}^i d_k \]

然后进行移项得到:

\[\max_{k=j}^{mid} c_k +j-1 \le i \le \max_{k=j}^{mid} d_k +j-1 \]

\[-\max_{k=mid+1}^i d_k+i+1\le j \le -\max_{k=mid+1}^i c_k +i+1 \]

可以发现这两个条件都是每一项只有 \(i\) 或者 \(j\),这意味着我们可以分开处理。

对于 \(j\),通过处理第一个条件可以划定满足条件的 \(i\),计算出区间,用扫描线简单维护。

对于 \(i\),通过处理第二个条件可以选定满足条件的 \(j\),直接计算出区间,去扫描线维护的线段树上询问即可。

时间复杂度 \(O(n \log^2 n)\)

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()

#define N 1002506
#define int long long

const int mod=1e9+7;
int n,c[N],d[N];
pr dp[N];
vector<int>ad[N],dl[N];

inline pr mg(pr x,pr y){
    if(x.fi==y.fi){
        return {x.fi,(x.se+y.se)%mod};
    }

    if(x.fi>y.fi){
        return x;
    }

    return y;
}

struct segt{
    #define mid ((l+r)>>1)

    pr tr[N<<2];
    
    inline void build(int k,int l,int r){
        tr[k]={-1e18,0};

        if(l==r){
            return;
        }

        build(k*2,l,mid);
        build(k*2+1,mid+1,r);
    }

    inline void upd(int L,int k,int l,int r,pr d){
        if(l==r){
            tr[k]=d;
            return;
        }

        if(L<=mid){
            upd(L,k*2,l,mid,d);
        }
        else{
            upd(L,k*2+1,mid+1,r,d);
        }

        tr[k]=mg(tr[k*2],tr[k*2+1]);
    }

    inline pr ask(int L,int R,int k,int l,int r){
        if(L<=l&&R>=r){
            return tr[k];
        }

        pr ans={-1e18,0};

        if(L<=mid){
            ans=mg(ans,ask(L,R,k*2,l,mid));
        }
        if(R>mid){
            ans=mg(ans,ask(L,R,k*2+1,mid+1,r));
        }

        return ans;
    }

    #undef mid
}t;

inline void cdq(int l,int r){
    if(l==r){
        if(c[l]>1){
            return;
        }

        pr pre=dp[l-1];
        pre.fi++;

        dp[l]=mg(dp[l],pre);

        return;
    }

    int mid=(l+r)>>1;

    cdq(l,mid);

    int C=0,D=1e9;
    per(i,l,mid){
        C=max(c[i],C);
        D=min(d[i],D);

        int L=max(mid+1,C+i-1),R=min(r,D+i-1);

        if(L<=R){
            ad[L].pb(i);
            dl[R+1].pb(i);
        }
    }

    C=0,D=1e9;

    rep(i,mid+1,r+1){
        for(int x:ad[i]){
            t.upd(x,1,0,n,dp[x-1]);
        }
        for(int x:dl[i]){
            t.upd(x,1,0,n,{(int)-1e18,0});
        }

        C=max(c[i],C);
        D=min(d[i],D);

        int L=max(l,i+1-D),R=min(mid,i+1-C);

        if(L<=R){
            pr res=t.ask(L,R,1,0,n);
            res.fi++;

            dp[i]=mg(dp[i],res);
        }

        ad[i].clear();
        dl[i].clear();
    }

    cdq(mid+1,r);
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    cin>>n;

    dp[0]={0,1};
    t.build(1,0,n);

    rep(i,1,n){
        dp[i]={-1e18,0};
        cin>>c[i]>>d[i];
    }

    cdq(1,n);

    // rep(i,1,n){
    //     cout<<dp[i].fi<<' '<<dp[i].se<<"\n";
    // }

    if(!dp[n].se){
        cout<<"NIE";
    }
    else{
        cout<<dp[n].fi<<' '<<dp[n].se;
    }

    return 0;
}
posted @ 2025-07-15 10:47  Lucyna_Kushinada  阅读(14)  评论(0)    收藏  举报