[树状数组优化DP] HLOJ539. 人品累加和

解决一个寝室人品分配问题,确保每个寝室的人品和不小于0。通过动态规划和树状数组实现,优化求解过程。

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

题解

题目描述
人品是必不可少的,人品还是守恒的。每个人的人品都是不同的,并且有正的(选择题可以用骰子全过),也有负的。
海亮高级中学有n (1<=n<=100,000)(1<=n<=100,000)个学生,第i个人的人品值是Ai(10,000<=Ai<=10,000).Ai(−10,000<=Ai<=10,000).
海亮高级中学决定重新规划寝室,要让每个寝室的人品和不小于0.寝室的人品和是这个寝室里所有人的人品和。
因为名单是按照学籍号给出的,所以,现在你不能调整学生的顺序,只能按照输入的顺序把学生依次分进各个寝室,当然,为了增加难度,每个寝室的人数可以不同,我们甚至可以把n个学生放在同一个寝室。我们的口号,人品比宿舍重要。
比如现在有4个人,人品分别是2, 3, -3, 1。我们可以按照下面分寝室:
(2 3 -3 1)
(2 3 -3) (1)
(2) (3 -3 1)
(2) (3 -3) (1)
这样保证每个寝室的人品和不小于0.
现在的问题是,如果按照这种原则,有多少种分寝室的方法。当然,方法可能很多,你只需要输出方案数对 1,000,000,009 取余数即可。
输入格式
第一行一个整数n
接下来n行,表示每个学生的人品AiAi.

题解

我们先搞这道题的朴素dp

状态

s[i]s[i]是前i个人的人品累加和。
这个状态比较好做f[i]f[i]代表前i个人的分配方式,
所以有f[i]=f[j](s[j]<=s[i],j<=i)f[0]=1f[i]=∑f[j](s[j]<=s[i],j<=i)且有f[0]=1

优化

所以只要用树状数组弄出所有的f[j]∑f[j]就行了。
不过要记得离散化。

code

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int num=0;char c=' ';bool flag=true;
    for(;c>'9'||c<'0';c=getchar())
        if(c=='-')
            flag=false;
    for(;c>='0'&&c<='9';num=(num<<3)+(num<<1)+c-48,c=getchar());
    return flag ? num : -num;
}
const int maxn=100020,p=1000000009;
int n;
struct S{
    int v,id;
}s[maxn];
bool mycmp(S a,S b){
    return a.v<b.v;
}
void init(){
    n=read();
    for(int i=1;i<=n;i++){
        s[i].v=read()+s[i-1].v;
        s[i].id=i;
    }
}
int pos[maxn];
void LSH(){
    sort(s,s+n+1,mycmp);
    int cnt=1,t=0;
    for(int i=0;i<=n;i++)
        if(s[i].v==s[t].v)pos[s[i].id]=cnt;
    else{
        pos[s[i].id]=++cnt;
        t=i;
    }
}//离散化
namespace shuzhuangshuzu{
    int lowbit(int t){return t&-t;}
    int ans,d[maxn];
    int find(int x){
        int t=0;
        while(x){
            t+=d[x];
            x-=lowbit(x);
            t%=p;
        }
        return t;
    }//查询和
    void updata(int x,int v){
        while(x<=n){
            d[x]+=v;
            d[x]%=p;
            x+=lowbit(x);
        }
    }//更新数据,x的位置加上v。
}using namespace shuzhuangshuzu;
void dp(){
    updata(pos[0],1);
    for(int i=1;i<=n;i++){
        ans=find(pos[i]);
        updata(pos[i],ans);
    }
}
int main(){
    init();
    LSH();
    dp();
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值