Try to find out the wrong in the test

本文介绍了一道关于分组问题的算法题,通过分治与线段树的巧妙结合来解决。问题中每个人有特定的人数限制,需要最大化分组数量。文章详细解析了解决方案,并给出了具体实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

有n个人排成一排,你需要对这n个人分组,每组必须是连续的一段。
每个人有要求,(c[i],d[i])表示这个人所在的组的最少人数和最多人数。
求最多能分成多少组和方案。
n<=1e6

Solution

如果只有d的限制这道题就很好做了。
因为d限制了我们i只能从i前面的一段区间转移过来,不妨设为left[i],显然left是单调的
但是有c的限制就很麻烦了,因为c的限制是一堆零碎的区间。
于是我们可以考虑按c的最大值分治。设c的最大值的位置为k,值为c。
我们先处理[l,k-1],然后考虑跨过k的转移。
这样子我们的下界就一定是k。
接下来我们考虑分类讨论
1:left[i] < l&&i<=k-1+c
这样子的点的转移区间是左边的一段区间,并且随着i的移动可转移的区间也不断扩大。
于是我们可以对第一个位置用线段树查出它的答案,接下来线性更新即可。
2:left[i ]< l&&i>k-1+c
符合条件的i也一定是一段区间,并且转移区间是整个左区间,于是直接用线段树维护就好了。
3:l<=left[i] < k
直接在线段树上查找。
4:left[i]>=k
分治到[k,r]区间。

但是我们这样不是常规的分治,如何考虑复杂度?
第1种情况最开始显然是O(n log n)的
考虑i的移动,最坏情况下i会移动min(k-l,r-k+1)次,也就是两边的长度取min
把分治过程看成启发式合并,这一部分也是O(n log n)的
第2种情况显然是O(n log n)的
第3种情况,对于每个点我们只会做一次,所以也是O(n log n)的
至此我们已经完美的解决了这道神题Orz

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

int read() {
    char ch;
    for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
    int x=ch-'0';
    for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    return x;
}

const int N=1e6+5,Mo=1e9+7;

#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))

struct node{
    int mx,cnt;
    node(int _mx=0,int _cnt=0) {mx=_mx;cnt=_cnt;}
    friend node operator + (node y,node z) {
        if (y.mx==-1) return z;
        if (z.mx==-1) return y;
        node x;x.mx=max(y.mx,z.mx);
        if (y.mx==z.mx) x.cnt=(y.cnt+z.cnt)%Mo;
        if (y.mx>z.mx) x.cnt=y.cnt;
        if (y.mx<z.mx) x.cnt=z.cnt;
        return x;
    }
}tr[N<<2],lazy[N<<2],f[N];

int n,mx[N<<2],c[N],d[N],q[N],left[N];

void update(int v,node z) {
    tr[v]=tr[v]+z;lazy[v]=lazy[v]+z;
}

void down(int v) {
    if (lazy[v].mx!=-1) {
        update(v<<1,lazy[v]);
        update(v<<1|1,lazy[v]);
        lazy[v]=node(-1,0);
    }
}

void build(int v,int l,int r) {
    if (l==r) {mx[v]=l;return;}
    int mid=l+r>>1;
    build(v<<1,l,mid);
    build(v<<1|1,mid+1,r);
    if (c[mx[v<<1]]>=c[mx[v<<1|1]]) mx[v]=mx[v<<1];
    else mx[v]=mx[v<<1|1];
}

int get_max(int v,int l,int r,int x,int y) {
    if (l==x&&r==y) return mx[v];
    int mid=l+r>>1;
    if (y<=mid) return get_max(v<<1,l,mid,x,y);
    else if (x>mid) return get_max(v<<1|1,mid+1,r,x,y);
    else {
        int a=get_max(v<<1,l,mid,x,mid);
        int b=get_max(v<<1|1,mid+1,r,mid+1,y);
        return (c[a]>=c[b])?a:b;
    }
}

void modify(int v,int l,int r,int x,int y,node z) {
    if (x>y) return;
    if (l==x&&r==y) {update(v,z);return;}
    int mid=l+r>>1;down(v);
    if (y<=mid) modify(v<<1,l,mid,x,y,z);
    else if (x>mid) modify(v<<1|1,mid+1,r,x,y,z);
    else modify(v<<1,l,mid,x,mid,z),modify(v<<1|1,mid+1,r,mid+1,y,z);
    tr[v]=tr[v<<1]+tr[v<<1|1];
}

node query(int v,int l,int r,int x,int y) {
    if (x>y) return node(-1,0);
    if (l==x&&r==y) return tr[v];
    int mid=l+r>>1;down(v);
    if (y<=mid) return query(v<<1,l,mid,x,y);
    else if (x>mid) return query(v<<1|1,mid+1,r,x,y);
    else return query(v<<1,l,mid,x,mid)+query(v<<1|1,mid+1,r,mid+1,y);
}

void init() {
    n=read();
    fo(i,1,n) c[i]=read(),d[i]=read();
    int head=1,tail=0;
    fo(i,1,n) {
        while (head<=tail&&d[q[tail]]>d[i]) tail--;
        q[++tail]=i;
        for(int j=left[i-1];;j++) {
            while (head<=tail&&q[head]<=j) head++;
            if (i-j<=d[q[head]]) {left[i]=j;break;}
        }
    }
    fo(i,0,n) f[i]=node(-1,0);f[0]=node(0,1);
    fo(i,1,n<<2) tr[i]=lazy[i]=node(-1,0);
}

int find(int l,int r,int v) {
    r++;
    while (l<r) {
        int mid=l+r>>1;
        if (left[mid]<v) l=mid+1;
        else r=mid;
    }
    return l-1;
}

void solve(int l,int r) {
    if (l>r) return;
    if (l==r) {
        modify(1,0,n,l,l,f[l]);
        f[l]=query(1,0,n,l,l);
        return;
    }
    int k=get_max(1,0,n,l+1,r),mx=c[k];
    solve(l,k-1);
    int mid=max(l+mx,k);
    if (left[mid]<l&&mid<=r) {
        node now=query(1,0,n,l,mid-mx);
        if (now.mx!=-1) {
            now.mx++;
            f[mid]=f[mid]+now;
            now.mx--;
        }
        fo(i,mid+1,min(k-1+mx,r)) {
            if (left[i]>=l) break;
            now=now+f[i-mx];
            if (now.mx!=-1) {
                now.mx++;
                f[i]=f[i]+now;
                now.mx--;
            }
        }
    }

    mid=k+mx;
    if (left[mid]<l&&mid<=r) {
        node now=query(1,0,n,l,k-1);
        if (now.mx!=-1) {
            now.mx++;
            modify(1,0,n,mid,find(mid,r,l),now);
        }
    }

    mid=find(k,r,l)+1;
    fo(i,mid,r) {
        if (left[i]>=k) break;
        node now=query(1,0,n,left[i],min(k-1,i-mx));
        if (now.mx!=-1) {
            now.mx++;
            f[i]=f[i]+now;
        }
    }
    solve(k,r);
}

int main() {
    freopen("schooldays.in","r",stdin);
    freopen("schooldays.out","w",stdout);
    init();
    build(1,0,n);
    solve(0,n);
    if (f[n].mx==-1) puts("-1");
    else printf("%d %d\n",f[n].mx,f[n].cnt);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值