原题链接:
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=1∑nxi2的不同取值个数。
数据
输入
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[i−1][j−k2],其中
a
i
<
=
k
<
=
b
i
a_i<=k<=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[i−1]中的状态,只是差一个
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
]
<
<
(
k
2
)
dp[i-1]<<(k^2)
dp[i−1]<<(k2)(
a
i
<
=
k
<
=
b
i
a_i<=k<=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(100∗100∗1e6)(最坏情况)的时间复杂度,也给除了个
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;
}