BZOJ3711: [PA2014]Druzyny

138 篇文章 0 订阅
41 篇文章 0 订阅

orz

考虑dp,f[i]表示1~i至多分成几段,g[i]表示1~i分成f[i]段的方案数
转移的时候将c[i],d[i]的限制分开考虑
对于d[i]的限制,不难发现他有单调性,可以预处理L[i]表示L[i]~i-1的j满足d[i]的限制可以由j转移到i
然后考虑c[i]的限制,他没有单调性,于是对c进行分治
如果我们在处理[l,r]这一个区间,找出[l+1,r]内c的最大值的位置k,那么k左边的j转移到i时,c值都是确定的c[k],接下来就是根据L[i],i,j的位置关系分情况讨论

先递归处理[l,k-1]的dp值,然后考虑[l,k-1]的j到[k,r]的i的转移
1: L[i]<=l,max(l+c[k],k)<=i<=min(k1+c[k],r)
这种情况下随着i的右移,能贡献到i的j的集合每次增加一个元素,L[i]<=l的区间可以二分求 O(logn) ,先用线段树查对左端点的贡献,然后i右移时可以O(1)更新,复杂度是 O()
2: L[i]<=l,k1+c[k]<=i<=r
可以直接线段树上区间更新i,复杂度 O(logn)
3: l<L[i]<=k1,k<=i<=r ,二分求出i的取值范围,枚举i线段树查更新,因为分治的区间是不相交的,也就是说 l<L[i]<=k1 对每个i只有一个这样的区间,因此这部分总复杂度是 O(nlogn)
因为总共 O(n) 个区间,总复杂度 O(nlogn)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void read(int &x)
{
    char c; int f=1; 
    while(!((c=getchar())>='0'&&c<='9')) if(c=='-') f=-1;
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
    if(f==-1) x=-x;
}
const int maxn = 1010000;
const int mod = 1000000007;
void add(int &a,int b){a+=b;if(a>=mod)a-=mod;}

int n;
int c[maxn],d[maxn],L[maxn];
int t[maxn],ti[maxn],tp;

struct node{int x,c;}f[maxn],zero;
void upd(node &a,const node &b)
{
    if(a.x<b.x) a.x=b.x,a.c=0;
    if(a.x==b.x) add(a.c,b.c);
}

struct segment{int ci;node f,flag;}seg[maxn<<2];
void build(const int x,const int l,const int r)
{
    seg[x].f=seg[x].flag=zero;
    if(l==r) { seg[x].ci=l; return; }
    int mid=l+r>>1,lc=x<<1,rc=lc|1;
    build(lc,l,mid); build(rc,mid+1,r);
    seg[x].ci=c[seg[lc].ci]>=c[seg[rc].ci]?seg[lc].ci:seg[rc].ci;
}
node re;
int loc;
void updp(const int x,const int l,const int r)
{
    upd(seg[x].f,f[loc]);
    if(l==r) { seg[x].f=f[l]; return; }
    int mid=l+r>>1,lc=x<<1,rc=lc|1;
    if(loc<=mid) updp(lc,l,mid);
    else updp(rc,mid+1,r);
}
void queryp(const int x,const int l,const int r)
{
    upd(re,seg[x].flag);
    if(l==r)return;
    int mid=l+r>>1;
    if(loc<=mid) queryp(x<<1,l,mid);
    else queryp(x<<1|1,mid+1,r);
}
node fl;
int lx,rx;
void cover(const int x,const int l,const int r)
{
    if(rx<l||r<lx) return;
    if(lx<=l&&r<=rx) { upd(seg[x].flag,fl);return; }
    int mid=l+r>>1;
    cover(x<<1,l,mid); cover(x<<1|1,mid+1,r);
}
void queryf(const int x,const int l,const int r)
{
    if(rx<l||r<lx) return;
    if(lx<=l&&r<=rx) { upd(re,seg[x].f);return; }
    int mid=l+r>>1;
    queryf(x<<1,l,mid); queryf(x<<1|1,mid+1,r);
}
int queryc(const int x,const int l,const int r)
{
    if(rx<l||r<lx) return 0;
    if(lx<=l&&r<=rx) return seg[x].ci;
    int mid=l+r>>1;
    int la=queryc(x<<1,l,mid),ra=queryc(x<<1|1,mid+1,r);
    if(c[la]==c[ra]) return la-l>r-ra?la:ra;
    return c[la]>c[ra]?la:ra;
}

void solve(const int l,const int r)
{
    if(l==r)
    {
        re=zero; loc=l; queryp(1,0,n);
        upd(f[l],re);
        loc=l; updp(1,0,n);
        return;
    }
    lx=l+1,rx=r; int k=queryc(1,0,n),cc=c[k];
    solve(l,k-1);

    int pl=k,pr=r;
    while(pl<=pr)
    {
        int mid=pl+pr>>1;
        if(L[mid]<=l) pl=mid+1;
        else pr=mid-1;
    }pl--;
    if(pl>=k)
    {
        int u=min(pl,k-1+cc);
        re=zero; lx=l,rx=max(k-cc,l); queryf(1,0,n);
        for(int j=max(k,l+cc);j<=u;j++)
        {
            if(re.x>=0) re.x++,upd(f[j],re),re.x--;
            if(j<u&&j+1-cc>=l) upd(re,f[j+1-cc]);
        }
        if(u<pl)
        {
            lx=u+1,rx=pl; fl=re; 
            if(fl.x>=0) fl.x++,cover(1,0,n);
        }
    }
    int tmp=++pl; pr=r;
    while(pl<=pr)
    {
        int mid=pl+pr>>1;
        if(L[mid]<=k-1) pl=mid+1;
        else pr=mid-1;
    }pl--;
    if(L[pl]>l&&L[pl]<k)
    {
        pr=pl; pl=tmp;
        for(int j=pl;j<=pr;j++)
        {
            lx=L[j],rx=min(k-1,j-cc);
            if(lx<=rx)
            {
                re=zero; queryf(1,0,n);
                if(re.x>=0) re.x++,upd(f[j],re),re.x--;
            }
        }
    }

    solve(k,r);
}

int main()
{
    zero=(node){-1,0};

    read(n);
    for(int i=1;i<=n;i++) read(c[i]),read(d[i]);

    tp=0; int l=1;
    for(int i=1;i<=n;i++)
    {
        if(t[tp]>=d[i])
        {
            t[tp]=d[i];
            while(tp>1&&t[tp-1]>=t[tp]) t[--tp]=t[tp+1];
        }
        else t[++tp]=d[i],ti[tp]=i;
        int j=L[i-1]; if(l>tp) l=tp;
        while(i-j>t[l])
        {
            j++; if(l<tp&&j+1>=ti[l+1]) l++;
        }
        L[i]=j;
    }
    f[0]=(node){0,1}; for(int i=1;i<=n;i++) f[i]=zero;
    build(1,0,n); 

    solve(0,n);
    //for(int i=1;i<=n;i++) printf("%d %d\n",f[i].x,f[i].c);
    if(f[n].x==-1) puts("NIE");
    else printf("%d %d\n",f[n].x,f[n].c);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值