Bitset模板 Bitset题型大荟萃

以codeforces上的ASC28J为例,讲了一些我遇到的Bitset的题目及做法

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void fre(){freopen("triatrip.in","r",stdin);freopen("triatrip.out","w",stdout);}
template  inline void scand(T &x){char c;x=0;while((c=getchar())<'0');while(c>='0'&&c<='9')x=x*10+(c-48),c=getchar();}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned int UI;
template  inline void gmax(T &a,T b){if(b>a)a=b;}
template  inline void gmin(T &a,T b){if(bbitset不仅限于32bits or 64bits,而是可以达到甚至是1e8 bits
<2>bitset支持or and xor << >> 等位运算,效率很高
其常用函数如下:
b.any()			判断b中是否存在值为1的二进制位
b.none()		判断b中是否不存在值为1的二进制位
b.count()		判断b中值为1的二进制位个数
b.size()		判断b中二进制位的个数
b[pos]			访问b中在pos处的二进制位
b.test(pos)		判断b中在pos处的二进制位是否为1
b.set()			把b中所有二进制位都置为1
b.set(pos)		把b[pos]置为1
b.reset()		把b中所有二进制位都置为0
b.reset(pos)	把b[pos]置为0
b.flip()		把b中所有二进制位逐位取反
b.flip(pos)		把b[pos]取反

scanf("%lld", &e); cout << e << endl;	//对bitset做读入或输出,输出的位置关系为高位 -> 低位
e[pos] = 1;  cout << e << endl;			//pos的位置关系在bitset上,相对位置关系为高位 -> 低位
使得e <<= 1相当于对于所有的x,都有e[x] = e[x - 1]

UI* v = (UI*)&T;		//把bitset转化为UI的数组

使用举例1	ASC28J:------------------------------------
http://codeforces.com/gym/100342/attachments/download/2145/20072008-winter-petrozavodsk-camp-andrew-stankevich-contest-28-asc-28-en.pdf
[题意]
给你一个可达1500大小的01矩阵,b[i][j]==1表示从i到j有边。
问你这个图中含有多少个三元环。
[分析]
最暴力的O(n^3)做法是——
for(i=0;ii)以及(i->k)边和(k->j)边,那么就构成了一个三元环。
于是for(i=0;i0)(i->1)(i->2)...(i->n)
(0->j)(1->j)(2->j)...(n->j)
两者&一下,然后再count()一下。就AC啦

使用举例2	TOJ4119:------------------------------------
1个bool是1字节,8个bitset单位才是1字节。
所以,用bool可以实现的,几乎都可以用bitset优化时间复杂度+空间复杂度

使用举例3	HDU5313 or GYM100781F ----------------------
注意,bitset可以说b[0]是最低位,
左移<<=会使得其变大(最左边补0)
右移>>=会使得其变小(最右边补0)
如果我们在做一个DP,
对于普遍性操作f[i]|=f[i-x],可以写成f|=f<>x
对于普遍性操作f[i]=f[i+x],可以写成f>>=x
如果存在溢出,是会用0来补的。但是小心边界问题。
比如我们定理了一个长度为8的bitset,却只需要用到长度为4的。
那么f<<=x的时候可能使得4 5 6 7位置也可能为1,然后后来再>>=,可能达不到自然溢出的效果,会出错。
补救的做法可以是设置特殊bitset b,数值为1的大小等于其所需要bitset尺寸,其它位置数值为0
然后每次操作之后&b,就相当于消除溢出,保证正确性

使用举例4	弱校联萌1001D:------------------------------------
http://acm.hdu.edu.cn/webcontest/contest_login.php?cid=7686
[题意]
给你一个图,n(500)个点,m(C(n,2))条边
每条边都至少有一个点的编号是<=30的,
让你对一些节点染色,使得每条边都至少有一个点是染色的,问你最少染色的节点数。
[解法]
正解是bitset爆搜。
我们只需要枚举<=min(n,30)个点的选取状态。
如果与一个点是选取的,那么,所有与这些点相连的边的另外一个点(>30)都可以不用选取。
如果一个点是没有选取的,为了覆盖边,所有与这个点相连的边的另外一个点都必须选取。
e[x]表示x这个点的选取情况,a[x][y]表示x与y之间有边,其中x是限定在[0,29]范围内的。
然后从0到29开始搜,依次枚举一个点选或不选。
选的话e[x]标记为1 有e|=a[x],代码如下:
void dfs(int p,bitset<500>e)
{
	int num=e.count();
	if(num>=ans)return;
	if(p==top)
	{
		ans=num;
		return;
	}
	if(e[p])dfs(p+1,e);//这个点已经被染色,与之有边的点都可以不染色
	else//这个点未被染色
	{
		e[p]=1;dfs(p+1,e);//这个点染色,与之有边的点都可以不染色
		e[p]=0;dfs(p+1,e|a[p]);//这个点不染色,与之有边的点必须染色
	}
}

使用举例5	CAMP原题,打球进洞:------------------------------------
[题意]
有n(5000)个集合,每个集合有m(50范围内)个数(数字范围也在50范围内)
第一个集合的(所有位置)都有一个球
第二个集合的(所有位置+0.5)都有一个洞
我们枚举2个集合pair,共C(n,2)种,第一个集合作为球,第二个集合作为洞。
我们将所有的球坐标变大,进入它们遇到的第一个洞里。
如果有奇数个洞至少有一个球,就获胜。
问对于所有的pair集合(x,y),有多少种获胜的可能。
[分析]
有时候转变一下思维方向非常重要。
这道题中一般思路是想到5000*5000后快速判定胜负
然而,实际上我们可以对于m个位置的每一个,处理其所对应的所有集合。
细节上我们是用bitset实现的。为什么要用bitset,因为我们想压位,然而因为位数太多(>64)而无法用普通变量实现。
于是这道题如果开bitset,那就是开50个长度为5000的bitset,a[i][j]就表示对于数字i,集合j中是否有这个数字。
我们处理的过程,肯定是一个个集合顺序枚举,枚举这个集合是洞的情况(5000*50的枚举量)。如何判定当某个洞有没有进球呢?
只需要枚举[上一个洞+1 ~ 这一个洞]之间的所有位置,把所有or值or到一个bitset tmp中,(就是对一定位置的bitset做or运算)
然后我们查看tmp,tmp是长度为n的bitset,tmp[x] == 1 就表示着,集合x是可以打球入这个洞。
接着把所有洞的进球集合tmp异或一下(就是求所有tmp的异或结果,也是一个长度为n的bitset),
最后的异或值p[x]就表示,以集合x的所有球,进当前集合的所有洞,能进球的洞数为奇数还是偶数。
不停使得ans+=p.count(),就能得到最后的答案。

使用举例6 HDU5036 2014 ACMICPC Asia Regional Beijing Online E Explosion 每个房间若干钥匙 没有钥匙需要爆破 打开所有房间的爆破数期望
[题意]
有n(1000)个房间,每个房间的门必须用其特定钥匙才能打开
每个房间有若干个(0~n)把钥匙(编号1~n)
我们初始没有钥匙,当我们手中的钥匙再也无法开任何一扇门的时候,我们必须要爆破一个房间
问你我们最终进入所有房间的期望爆破数
[分析]
首先要求出对于每个门,有多少扇门的打开会导致这扇门的打开。
这个利用floyd闭包传递实现
for(k)for(i)for(j)if(a[i][k]&&a[k][j])a[i][j]=1
然而这个时间慢会TLE,于是我们用bitset优化
<1> if(a[i][k])a[i][j]|=a[k][j]
<2> if(a[i][k])a[i]|=a[k]
处理之后,a[i].count()就是炸i能打开的房间数,接下来要算期望。
有一种算期望的方法我认为是对的,就是——
用p[i]表示能导致第i个房间被开打的房间,全部都排在第i个房间后面的概率,
只有在这时,对于第i个房间就必须强行炸一次,于是其实答案就是(∑p[i])。
p[i]怎么算呢?这个点排在整个全排列的前面,概率显然是1/k啦
所以总的期望就是先是∑(1/k[i])啦

使用举例7 HDU5890 2016 ACMICPC Asia Regional Qingdao Online L 50个数扔3个后选10和是否可为87
[题意]
有n(50)个数
有m(1e5)个询问
对于每个询问,我们需要扔掉xyz三个数
问你剩下的数中,是否可以恰好选出10个,使得其和恰好为87
[分析]
对于暴力的背包,复杂度是n*10*87的。这道题我们可以加一些优化——
方法一:我们预处理出一组解,得到我们有哪10个数可以构成87。
然后,接下来的移除的3个数,如果都在这10个数之外,我们就直接输出Yes,否则我们再做DP。
这种方法,有C(40,3)种情况被我们简化掉了,就只剩下了1e4种DP需求
方法二:我们对同样的数做判重,哈希得到最小表示法,减少状态数
方法三:我们可以通过前缀和与后缀和做2个DP
bef[i][j][k][w] 表示用[1~i]数中的j个,严格没有选第k个,和为w是否可行
beh[i][j][k][w] 表示用后[i~n]数中的j个,严格没有选第k个,和为87-w是否可行
那么对于一组询问,
(bef[i][j][k] & beh[i][j][k]).any()就是答案

使用举例8 HDU5808 BestCoder Round 86E Price List Strike Back 距离范围、区间范围商店购物 使得价值和恰为m
[题意]
有n(20000)个商店,第i个商店距离家的位置为d[i](1e9),卖的物品的价值为v[i](100)
有m(1e5)个询问(l,r,dis,sum)
问你,如果对[l,r]内,距离我家距离不超过dis的商店做购物(可以对于v[l~r]的每一者选择或不选择),
能否能够恰好使得商品总价值为sum
[分析]
首先,因为涉及到一个距离的影响。
我们要把距离的影响消掉,所以可以考虑对——
"所有商店距离家的距离"
"所有询问距离家的距离"
这两样东西按照升序排序,这样使用双指针,我们就可以使得在处理一个询问的时候,只考虑了所有距离范围内的商店。
在过程中需要用树状数组维护前缀和,用于求出区间范围内某种价值的物品有多少件。
然后开始处理询问——
1,树状数组的查询量最多为m*100*log(n),复杂度为1e8
2,然后做DP,这里的状态转移是f[i]|=f[i-物品价值],这个显然可以用bitsetDP实现。
这里需要一些优化,对于背包上限为m,物品价值为i,显然物品数量最多为m/i
这样子的话,我们背包的大小最多只会为m/1+m/2+...+m/m为mlogm级别(即800左右)
这时做bitsetDP的复杂度上限为800 * 100 / 32,复杂度为m * 800 * 3,复杂度为2e8
我们可以用二进制拆分再次优化。
或者在bitsetDP的时候,DP的终止条件为我们用当前的物品无法再更新出
甚至这两个优化可以加在一起。

使用举例9 2015广东工业大学新生赛E GDUT的实验室 十进制与二进制的比较
[题意]
给你二进制IP编码和十进制IP编码,让你判断两者是否一致。
[分析]
c++的读入——
默认数据按十进制输入输出。如果要求按八进制或十六进制输入输出,在cin或cout中必须指明相应的数据形式。
oct为八进制,hex为十六进制,dec为十进制。即cin>>oct(hex)>>x
c的读入——
%o 读入八进制数
%x 读入十六进制数
bitset的读入——
for (int i = 0; i<4; ++i)cin >> b[i], cin.get();
bitset读入之后,
for (int i = 0; i<4; ++i)if (a[i] != b[i].to_ulong())return FALSE;
bitset.to_string()
bitset.to_ulong()
bitset.to_ullong()

使用举例10 2015-2016 XVI Open CupJ Judgement n人投票前后权威值改变是否有实际区别
[题意]
有n(100)个法官,每个人有一个权威性a[]。
对于一次投票,如果投赞成票人数的权威性之和>=p,那么就认为投票有效,否则无效。
然而,这些人的权威性由a[]被改变成了b[],决定投票有效与否的阈值也由p变成了q。
然后问你,前后的改变是否有实际区别。
如果有,输出同样的投票对应不同的结果的投票方案。
各种与权威性相关的数值a[]、b[]、p、q都在[1,1e6]范围。
[分析]
1,TLE了不一定是时间不够,可能是死循环了。
2,前驱记录输出方案的方法一定要顾忌到是否存在环
3,我们可以通过交换数组的方式实现相似过程的统一过程化处理。
首先,面对复杂度的问题,我们可以先思考朴素好想的暴力解法。
显然,如果投票产生了差别,可能有两种情况——
之前权威之和 >= p,现在 < q;之前权威之和 < p,现在 >= q
我们可以考虑一个背包
f[i][j]表示考虑了前i个人,这些人之前的权威性之和为j,对应的最大后权威性之和
d[i][j]表示考虑了前i个人,这些人之前的权威性之和
不妨先假设p达标,q不达标于是,我们用f[i][j]表示——
前i个人参与投票,当投票对应的a[]的权威值为j时,所对应的最小b[]。
那么有状态转移gmin(f[i+1][j+a[i]],f[i][j]+b[i])
然而j+a[i]与f[i][j]+b[i]都可能爆炸。我们只要控制两者的上限不超过p和q即可(因为实际意义都相同)
最后只要检测f[p]是否= 0; --j)
		{
			int x = min(j + a[o][i], v[o]);				//下一个我方状态点
			int y = min(f[j] + a[o ^ 1][i], v[o ^ 1]);	//下一个对方状态点
			if (y < f[x])
			{
				f[x] = y;
				d[x] = d[j];
				d[x][i] = 1;
			}
		}
		top = min(top + a[o][i], v[o]);
		if (f[v[o]] < v[o ^ 1])
		{
			puts("NO");
			for (int i = 1; i <= n; ++i)printf("%c", d[v[o]][i] ? '1' : '0');
			puts("");
			return 1;
		}
	}
	return 0;
}

使用举例11 2016百度之星 - 复赛(Astar Round3)E 长度m的串匹配n个集合
[题意]
给你一个长度为m(2e6)的文本串,
我们想要找出其所有的符合特定模式的子串位置。符合特定模式是指——
<1> 子串长度为n(500)
<2> 第i个字符需要在给定的字符集和Set[i]中(即给出了n个字符集,字符集中的合法字符为数字或大写字母)
[分析]
依然先思考暴力的做法——
我们需要枚举所有起点(m个),再枚举连续的(n长度),然后做字符集的匹配。
但是这个复杂度是1e9的,不能被支持。
我们发现,实际上我们会需要用到这么一个东西,判定位置i的字符是否在第j个集合之中(依然是1e9的复杂度)
而这个东西是大概可以使用bitset做优化的。
然而首先考虑一个复杂的DP做法,这个DP对于相同的尾端点统一化递推处理,这样才方便bitset优化——
f[i][j]表示匹配到第i个位置,以i为尾端的条件下,是否能够实现匹配长度为j(即1~j的匹配都实现)
那么就有 f[i][j]=f[i-1][j-1] && (s[i] in set[j])
程序就是——
for(int i = 1; i <= l; ++i)
{
	f[i][0]=1;
	for(int j = 1; j <= n; ++j)
	{
		f[i][j] = f[i-1][j-1] && (s[i] in set[j])
	}
}
显然,对于此,我们可以使用bitset进行优化——
f[i] = (f[i-1] << 1) & (b[s[i]])
   [j]        [j - 1]        [j]
即做一个预处理b[s[i]][j]表示字符s[i]是否在第j个集合中出现。
for (int i = 0; s[i]; ++i)
{
	bt1[0] = 1;
	bt2 = bt1 << 1 & b[s[i]];
	if (bt2[n]) printf("%d\n", i - n + 2);
	swap(bt1, bt2);
}

使用举例12 Codeforces Round 360 (Div 1)C The Values You Can Make 若干硬币总数为m子集可能方案数
[题意]
Pari wants to know the values x such that
there exists a subset of coins with the sum m
such that some subset of this subset has the sum x,
题目是说,要在恰好构成m的硬币的子集上求其子集能够达成sum为x的x数量
换句话说,举个例子
5 6
3 3 2 2 2
ans = 0 2 3 4 6 ,并没有5,因为5打破了集合的内部性
硬币数为n(500),背包上限为m(500)
[分析]
这个DP的思想要好好学习一下——2side DP大法
就是把复杂度看似为2^n的DP,用n^2的方法实现
我们用f[i][j][k]表示枚举了前i个物品,其中一部分物品的sum为j,另外一部分物品的sum为k的可行性
显然初始化f[0][0][0]=1
DP转移是枚举可行的f[i-1][j][k],然后新的物品可以加入j或者k中去
这个DP的时间复杂度为O(n^3),空间复杂度为O(n^2),因为第一维度可以省略
这样DP完之后,我们就可以根据最后的具体合并方案,而得到答案了。
具体上可以使用bitset优化
b[j][k]表示考虑了前i枚硬币,总的硬币面额为j,左半边的硬币面额为k的状态可达性
优化成bitset b[j]。然后更新包括——
b[j + x] |= b[j] 硬币放在右边
b[j + x] |= b[j] << x 硬币放在左边

使用举例12 中国大学生程序设计竞赛中南邀请赛(重现)I Substring Query 匹配串中各模板串出现次数
[题意]
字符串长度为5e4,操作个数为1e5
询问有两种:
1:p ch 修改s[p]为ch
2:0 string 询问string在s中出现的次数
[分析]
for (int i = 0; i < 26; ++i)b[i].reset();
for (int i = 1; i <= l; ++i)b[s[i] - 'a'].set(i);	//依然是形成了一个 字符 -> 位置 的套路
while (q--)
{
	int op; scanf("%d", &op);
	//询问是即时完成的
	if (op == 0)
	{
		scanf("%s", ss + 1); int ll = strlen(ss + 1);
		//一开始得到了哪些位置可以是匹配ss[1]的位置
		bt = b[ss[1] - 'a']; 
		//然后我们把匹配位置 >> (i - 1) 与1对其,得到了其延展
		for (int i = 2; i <= ll; ++i)
			bt &= (b[ss[i] - 'a'] >> (i - 1));
		printf("%d\n", bt.count());
	}
	//修改只需要对bitset做暴力修改
	else
	{
		char ch;
		scanf(" %c", &ch);
		b[s[op] - 'a'].reset(op);
		b[ch - 'a'].set(op);
		s[op] = ch;
	}
}

使用举例13 HDU5745 2016 Multi-University Training Contest 2L La Vie en rose 目标串多少子串可以被原始串做相邻交换得到
[题意]
给你一个长度为n(1e5)的目标字符串a
我们有一个长度为m(min(5000,n))的基础串b,
对于这个目标字符串,显然有n-m+1个长度为m的子串
问你,在这些子串中,有哪些子串,是可以通过对基础串一些相邻位置的交换,得到b。
即,我们可以选出若干个基础串的若干个位置p[],使得p[i+1]>p[i]+1,
然后我们swap(p[i],p[i]+1),就可以实现变形后的基础串与目标字符串的匹配
[分析]
尽管暴力可过,但是正解是bitset,我们依然需要学习一下——
这题的暴力做法,其实本身就比较相似于一个DP。
这道题定义f[i][j][k]表示——
a串匹配到位点a[i]
b串匹配到位点b[j]
j这个位置的匹配状态为k(0表示b[j]要与b[j-1]交换,1表示没有交换,2表示b[j+1]交换)
转移方程是这个样子的——
dp[i][j][0]=dp[i-1][j-1][2] & (a[i]==b[j-1])
//a[i-1]与b[0~j-1]匹配了,且a[i]实际要与b[j-1]做匹配

dp[i][j][1]=dp[i-1][j-1][0] & (a[i]==b[j])
|		    dp[i-1][j-1][1] & (a[i]==b[j])
//a[i-1]与b[0~j-1]匹配了,且a[i]实际要与b[j]做匹配

dp[i][j][2]=dp[i-1][j-1][0] & (a[i]==b[j+1])
		   |dp[i-1][j-1][1] & (a[i]==b[j+1])
//a[i-1]与b[0~j-1]匹配了,且a[i]实际要与b[j+1]做匹配
=========================================================================
不过该程序的常数巨大,后来还是被加强加版数据卡得TLE了。
我们再来学习另外一种快些的bitset做法,该做法是把a做bitset压位展开的。
我们开bitsetdp[3],再开bitsetw[26]w[i],表示对于字符i,其在a[]串的哪些位置出现。
初始化dp[0][i]都为1,表示匹配b的长度为0的条件下,以i为a[]的匹配起点,都是可以匹配的。
然后我们做m轮次的匹配,每次取出b中的字符。显然,正常的匹配是这样子的——
dp[nxt]=dp[now]&(w[b[i]]>>i),即以a[]的每个位置为起点,我们查看每个位置往后走i(i∈[0,m))位的字符,是否依然可以匹配到b串,
不过还存在了交换的匹配方式——
dp[nxt]=dp[pre]&(w[b[i]]>>(i-1))&(w[b[i-1]]>>i),即考虑交换的匹配。

使用举例14 ICPCCamp2016 Day5L 希哥一血
[题意]
有n(5e4)个点
有m(1e5)条边,每条边都互不相同
我们希望找到三条边,构成一个最小的三角形。
[分析]
这道题的实现需要一个枚举的过程,两个贪心的思想,和一个bitset的数据结构
<1>枚举的过程——
对于构成三角形的三条边,不妨假设a < b < c,并设dif = c - b。
满足三角形的限制,除了a < b < c这个假设外,就只需要满足a > dif
于是我们枚举dif从1到maxdif(即maxv - minv),枚举的复杂度为m
<2>第一个贪心的思想——
此处已知了dif,应该对应得到满足条件的最小的a,因为这样还可以使得我们对于 b c 拥有更大的选择空间
<3>第二个贪心的思想——
对于一个固定的a,我们枚举要找到合法的解(b,c),同时,还要对应使得b尽可能小,这样才会贪心使得面积尽可能小
<4>于是,问题变成了——
我们如何快速找到边长b和b+dif(且有限制条件b+dif>a)呢
之前的复杂度已经有了O(m)的乘法系数了,这里可以使用一个bitset结构维护。
我们用一个bitset数组B[],B[x]表示是否有边长为x的边存在,1表示存在。于是使得T = B & (B >> dif)
我们得到的T,如果T[x] = 1且x > a,那么x就对应一个可行的b的解。于是我们需要实现在bitset中查找最小>a的1,
希哥的做法是我们把bitset转int,然后查找比x大的int位是否有不为0的数,如果有,那就找到了所对应的b和c
利用这样的a、b、c,我们用海伦公式直接求得面积即可。

强转函数:UI* v = (UI*)&T;		//把bitset转化为UI
*/

bitsetout[N];out[i][j]是原始图,是正向边
bitsetin[N];//in[j][i]是反向图,是反向边
int main()
{
	while (~scanf("%d", &n))
	{
		LL ans = 0;
		for (i = 0; i


你可能感兴趣的:(模板)