题目链接:简单瞎搞题
题意
给你n个区间,每次我们从一个区间 取一个数,然后从n个区间取n个数后,取 ,问s的情况有多少种。
题解
一开始当作正常dp求解,设dp[i][j]:已经取了前i个区间后答案为j的个数。
状态转移方程为:dp[i][j]+=dp[i-1][j-k*k] , (k为从第i个区间选的数字)
然后答案无非最大为
,最后再遍历1~1000000,dp[n][i]有值的答案++,但很明显这个的时间复杂度为
,m为1e6。emmm,于是就Ban了。
那么怎么解呢?
我苦思冥想没想到减少空间和时间复杂度的方法,最后发现有个叫bitset的类似数组的一个数据类型。
bitset的每个元素只有0和1,但每个元素仅占1 bit。Woc,1bit什么概念,平时一个int类型的整数四个字节32位,而这个bitset只有1位,虽然只能存放0和1,但这道题转化一定可以求解的。
先补充一下bitset的一些用法
(引用这位大佬的部分讲解)
用字符串构造时,字符串只能包含 ‘0’ 或 ‘1’ ,否则会抛出异常。
bitset<4> bitset1; //无参构造,长度为4,默认每一位为0
bitset<8> bitset2(12); //长度为8,二进制保存,前面用0补充
string s = "100101";
bitset<10> bitset3(s); //长度为10,前面用0补充
char s2[] = "10101";
bitset<13> bitset4(s2); //长度为13,前面用0补充
cout << bitset1 << endl; //0000
cout << bitset2 << endl; //00001100
cout << bitset3 << endl; //0000100101
cout << bitset4 << endl; //0000000010101
二进制的一些操作符
bitset<4> foo (string("1001"));
bitset<4> bar (string("0011"));
cout << (foo^=bar) << endl; // 1010 (foo对bar按位异或后赋值给foo)
cout << (foo&=bar) << endl; // 0010 (按位与后赋值给foo)
cout << (foo|=bar) << endl; // 0011 (按位或后赋值给foo)
cout << (foo<<=2) << endl; // 1100 (左移2位,低位补0,有自身赋值)
cout << (foo>>=1) << endl; // 0110 (右移1位,高位补0,有自身赋值)
cout << (~bar) << endl; // 1100 (按位取反)
cout << (bar<<1) << endl; // 0110 (左移,不赋值)
cout << (bar>>1) << endl; // 0001 (右移,不赋值)
cout << (foo==bar) << endl; // false (0110==0011为false)
cout << (foo!=bar) << endl; // true (0110!=0011为true)
cout << (foo&bar) << endl; // 0010 (按位与,不赋值)
cout << (foo|bar) << endl; // 0111 (按位或,不赋值)
cout << (foo^bar) << endl; // 0101 (按位异或,不赋值)
可用函数
bitset<8> foo ("10011011");
cout << foo.count() << endl; //5 (count函数用来求bitset中1的位数,foo中共有5个1
cout << foo.size() << endl; //8 (size函数用来求bitset的大小,一共有8位
cout << foo.test(0) << endl; //true (test函数用来查下标处的元素是0还是1,并返回false或true,此处foo[0]为1,返回true
cout << foo.test(2) << endl; //false (同理,foo[2]为0,返回false
cout << foo.any() << endl; //true (any函数检查bitset中是否有1
cout << foo.none() << endl; //false (none函数检查bitset中是否没有1
cout << foo.all() << endl; //false (all函数检查bitset中是全部为1
一些转换类型的函数
bitset<8> foo ("10011011");
string s = foo.to_string(); //将bitset转换成string类型
unsigned long a = foo.to_ulong(); //将bitset转换成unsigned long类型
unsigned long long b = foo.to_ullong(); //将bitset转换成unsigned long long类型
cout << s << endl; //10011011
cout << a << endl; //155
cout << b << endl; //155
介绍完怎么用,开始讲解法,解法很简单,既然 ,那么我可以申请100个长度为 的bitset。
bitset<1000005> dp[105];
定义基本不变,dp[i][j]:已经取了前i个区间后,答案为j是否存在,如果存在为1否则为0。
初始化dp[0][0]=1
从1开始,然后每次加上
,那么怎么加呢?位运算有一个<<左移操作,我们可以dp[i]<<
来达到,代表在取前(i-1)个区间的基础上加上
的结果。
那么状态转移很容易写出
dp[i] |= dp[i-1]<<(i*i); //i为l~r区间内的所有数字
最后答案就是统计dp[n]里这1000005位里为1的个数。
知道了dp定义、初始化以及状态转移方程。这道dp题就迎刃而解啦~~
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<cassert>
#include<cctype>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<deque>
#include<iomanip>
#include<list>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#include <bitset>
using namespace std;
//extern "C"{void *__dso_handle=0;}
typedef long long ll;
typedef long double ld;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
const double PI=acos(-1.0);
const double eps=1e-6;
const ll mod=2333;
const int inf=0x3f3f3f3f;
const int maxn=1e6+10;
const int maxm=100+10;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll gcd(ll a,ll b) { return b==0?a:gcd(b,a%b); }
bitset<1000005> dp[105];
int main()
{
int n;
scanf("%d",&n);
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
int l,r;
scanf("%d%d",&l,&r);
for(int j=l;j<=r;j++)
dp[i] |= (dp[i-1]<<(j*j));
}
cout << dp[n].count() << endl;
}