奶牛抗议-二维偏序优化

奶牛抗议-二维偏序优化

P2344 Generic Cow Protests G

题意

求连续分组和大于等于 \(0\) 的方案数。

思路

容易想到 \(O(n^2)\) 的 dp 解法,其状态定义为到 \(i\) 的位置,当前位置的方案数。转移为:

\[dp_i = \sum_{j=0}^{j < i} (dp_j \mid sum_j \le sum_i) \]

此时暴力查找符合条件的 \(j\) 的复杂度为 \(O(n^2)\),显然会超时。

发现有两个限制,我们可以采用二维偏序优化,将 \(O(n^2)\) 的复杂度降为 \(O(n \log n)\)

二维偏序优化其实就是在两个限制下快速找到前驱,拿坐标说就是找到一个点它所有的 \(x\) 小于它,\(y\) 也小于它的所有点。

pZpvGDO.png

优化的方式是先对一个限制(\(x\))排序,再在树状数组中不断加入另一个限制(\(y\)),并查询,由于顺序的关系,可以保证此时查到的节点满足要求。

代码

点击展开
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e5+10;
constexpr int mod = 1e9+9;

typedef struct node
{
    int id,sum;
    bool operator<(const node &x)const
    {
        return sum==x.sum ? id<x.id : sum<x.sum;// 第一遍排序按sum,相等时要按id排
    }
}node;

node pi[maxn];
int n;
int wi[maxn];
int dp[maxn];
int sum[maxn];

void add(int x,int k)// 树状数组维护dp和
{
    while(x<=n)
    {
        sum[x]+=k;
        if(sum[x]>=mod)
        {
            sum[x]-=mod;
        }
        x+=(x & -x);
    }
}

int query(int x)
{
    int ret=0;
    while(x>0)
    {
        ret+=sum[x];
        if(ret>=mod)
        {
            ret-=mod;
        }
        x-=(x & -x);
    }
    return ret;
}

signed main()
{
    #ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    #endif // ONLINE_JUDGE

    scanf("%lld",&n);
    ++n;// 树状数组从1开始,不能有0节点,而我们要手动添加一个一号节点为0
    for(int i=2;i<=n;++i)
    {
        scanf("%lld",wi+i);
        pi[i].sum=wi[i]+pi[i-1].sum;
        pi[i].id=i;
    }
    pi[1].id=1;
    pi[1].sum=0;

    sort(pi+1,pi+1+n);
    dp[1]=1;

    for(int i=1;i<=n;++i)
    {
        int id=pi[i].id;
        if(id==1)
        {
            add(id,dp[id]);// 0号节点要优先初始化,而不是用别人修改它
            continue;
        }
        dp[id]=query(id);// 求满足j<id && sum_j<=sum_id的dp前缀恶
        add(id,dp[id]);// 加入树状数组
    }

    printf("%lld\n",dp[n]);

    return 0;
}
posted @ 2025-11-10 14:55  玖玮  阅读(0)  评论(0)    收藏  举报