[2020牛客算法竞赛入门课第六节习题] 简单瞎搞题 bitset+简单dp

题目链接:简单瞎搞题

题意

给你n个区间,每次我们从一个区间 l [ i ] r [ i ] {l[i]~r[i]} 取一个数,然后从n个区间取n个数后,取 s = i = 1 n x i 2 {s=\sum_{i=1}^{n}x_i^2} ,问s的情况有多少种。

题解

一开始当作正常dp求解,设dp[i][j]:已经取了前i个区间后答案为j的个数。
状态转移方程为:dp[i][j]+=dp[i-1][j-k*k] , (k为从第i个区间选的数字)
然后答案无非最大为 100 10 0 2 {100*100^2} ,最后再遍历1~1000000,dp[n][i]有值的答案++,但很明显这个的时间复杂度为 O ( n 2 m ) {O(n^2m)} ,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

介绍完怎么用,开始讲解法,解法很简单,既然 i = 1 n x i 2 1 0 6 {\sum_{i=1}^{n}x_i^2}≤10^6 ,那么我可以申请100个长度为 1 0 6 {10^6} 的bitset。

bitset<1000005> dp[105];

定义基本不变,dp[i][j]:已经取了前i个区间后,答案为j是否存在,如果存在为1否则为0。
初始化dp[0][0]=1
从1开始,然后每次加上 x i 2 {x_i^2} ,那么怎么加呢?位运算有一个<<左移操作,我们可以dp[i]<< x i 2 {x_i^2} 来达到,代表在取前(i-1)个区间的基础上加上 x i 2 {x_i^2} 的结果。

那么状态转移很容易写出

扫描二维码关注公众号,回复: 11581783 查看本文章
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;
}

猜你喜欢

转载自blog.csdn.net/weixin_44235989/article/details/107825643