首先的话我们需要知道这种类型的题一般常规做法就三种暴力、dp和搜索(当然有些神奇的题目可能会用到其他的做法比如说数论)。暴力思路很容易想到,但是复杂度显然不允许。需要考虑 dp /记忆化搜索,首先可以发现的是产生的数非常小,于是我们可以开一个数组来存放存产生的数(产生了 32 那么 mark[32] = 1)那么我们就可以这么做,外循环循环 n 次,内循环循环 l 到 r ,每次遍历整个数组。对于上一轮产生的所有数,我们加上i×i(l-r)累加到新数组(mark1[32+ii] = 1),因为赋值永远是1所以在这个过程中完成了自动除重 ,然后再把新数组赋值给旧数组。for(i->n)mark[i] = mark1[i]。我们很容易发现,在整个过程我们只有 0 和 1 ,而且你会发现32 + i×i之后在新的数组的中的位置刚好左移了i×i位。那么我们可以状态压缩,用一个01串来表示产生的数的集合,同时优化状态转移,现在我们就可以整体转移了,不再需要一个一个枚举然后转移。但是这个01串需要开到100×100×100长度,显然什么类型都没有这么长,于是我们可以用到 STL 中的一个卡空间神器 bitset 容器。你可以把 bitset 看成是一个可以随便定义长度的二进制串,他重载了所有的位运算,并且每一位只占 1 bit,也就是一个 bool 变量的 1/8(他的内部大概是位域组织的。位域的话是c/c++中一个很冷门的知识。)。bitset<100>a代表你定义了一个长度为 100 的二进制串。需要注意的是 bitset 的 <> 中的数必须要是常数不能是变量,因为他需要在编译的时候确定大小,这一定也让我肯定了它内部是位域组织的。
代码:
#include<bits/stdc++.h> using namespace std; const int N = 101*101*101; inline int read() { char c=getchar(); int x=0,fh=0; while(c<'0'||c>'9'){fh|=c=='-';c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return fh?-x:x; } int main() { int n = read(); bitset<N>ans;//答案 int l = read(); int r = read(); for(int i=l;i<=r;++i)ans[i*i] = 1;//首先在外面我们处理第一对 l 和 r ,便于编写接下来的代码。 for(int i=1;i<n;++i) { int l = read(); int r = read(); bitset<N>tmp; for(int j=l;j<=r;++j) { tmp |= ans<<j*j; } ans = tmp; } cout<<ans.count();//最后有多少个 1 就是集合中有多少个数 return 0; }