LibreOJ 515 「LibreOJ β Round #2」贪心只能过样例 题解(bitset,DP)

博客介绍了如何解决一道关于求不同平方和组合的问题,原题链接给出。通过朴素的动态规划方法发现空间和时间都无法通过,然后引入bitset进行优化,将空间复杂度降低,并利用位运算将时间复杂度优化。最终,通过重新定义dp状态,实现快速的集合运算,使得算法在位运算的高效性下变得可行。

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

原题链接:
loj

题意简述

给定 n n n,表示有 n n n个数 x 1 , x 2 . . . x n x_1,x_2...x_n x1,x2...xn,每个在 [ a i , b i ] [a_i,b_i] [ai,bi]之间,求 ∑ i = 1 n x i 2 \sum\limits_{i=1}^{n}x_i^2 i=1nxi2的不同取值个数。

数据

输入
n//n<=100
a1 b1
a2 b2
...
an bn//ai,bi<=100
输出

答案

样例

输入
5
1 2
2 3
3 4
4 5
5 6
输出
26

思路

朴素的 D P DP DP:
考虑到每个数的平方顶多 1 e 6 1e6 1e6,所以我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个数的平方和为 j j j是否珂能(所以是 b o o l bool bool类型)。答案就是 d p [ n ] [ 1 , 2 , 3...1 e 6 ] dp[n][1,2,3...1e6] dp[n][1,2,3...1e6],转移:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − k 2 ] dp[i][j]=dp[i-1][j-k^2] dp[i][j]=dp[i1][jk2],其中 a i &lt; = k &lt; = b i a_i&lt;=k&lt;=b_i ai<=k<=bi

珂是这样不管是空间还是时间都过不去。。。
我们考虑到我们快到上天的 b i t s e t bitset bitset优化。由于我们的 d p dp dp值只有 0 0 0 1 1 1,所以 b i t s e t bitset bitset一下就把空间除了个 32 32 32。原本 1 e 8 1e8 1e8稳爆的空间,变成了 3 e 6 3e6 3e6稳过的空间。

珂是。。。时间?
我们考虑到 d p [ i ] dp[i] dp[i]中的状态,和 d p [ i − 1 ] dp[i-1] dp[i1]中的状态,只是差一个 k 2 k^2 k2。所以我们珂以把 d p dp dp这样重新定义:
d p [ i ] dp[i] dp[i]是一个长度为 1 e 6 1e6 1e6 01 01 01序列,其中这个 01 01 01序列的第 j j j个位置表示前 i i i个数珂不珂以凑到 j j j。那么 d p [ i ] dp[i] dp[i]表示的 01 01 01序列其实就珂以看作前 i i i个数珂以凑出来的集合了。那么 d p [ i ] dp[i] dp[i]其实就是 d p [ i − 1 ] &lt; &lt; ( k 2 ) dp[i-1]&lt;&lt;(k^2) dp[i1]<<(k2) a i &lt; = k &lt; = b i a_i&lt;=k&lt;=b_i ai<=k<=bi)的并集,也就是或起来的结果。因为一个 01 01 01序列我们把它左移 k 2 k^2 k2,就相当于快速实现了集合中的每个数都加上 k 2 k^2 k2。这是非常巧妙的。
(注意边界: d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]=1)
然后原本 O ( 100 ∗ 100 ∗ 1 e 6 ) O(100*100*1e6) O(1001001e6)(最坏情况)的时间复杂度,也给除了个 32 32 32,变成了 3 e 8 3e8 3e8,而且是 3 e 8 3e8 3e8次位运算。你要知道,位运算比开空间和加减乘除快很多,所以一秒内大概珂以执行 1 e 9 1e9 1e9次位运算。(而且代码还短了不少!)

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    bitset<1001000> dp[110];//dp状态
    void Soviet()
    {
        dp[0][0]=1;
        int n;cin>>n;//数据规模小,你甚至珂以用cin
        for(int i=1;i<=n;++i)
        {
            int a,b;
            cin>>a>>b;
            for(int j=a;j<=b;++j)//枚举新加的数
            {
                dp[i]|=(dp[i-1]<<(j*j));//或起来
            }
        }
        printf("%d\n",dp[n].count());//只要输出dp[n]中有多少个1,就是前n个数的平方和的不同取值个数了
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值