bitset
bitset容器是一个bit位元素的序列容器,每个元素只占一个bit位,取值为0或1,因而很节省内存空间。它的10个元素只用了两个字节的空间。
头文件:#include<bitset>
bitset类的方法列表:(bitset<N>b)
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.reset(): 把b中所有二进制位都置为0
b.reset(pos): 把b中在pos位的二进制置为0
b.flip(): 把b中所有二进制位逐位取反
b.flip(pos): 把b中在pos位的二进制位取反
b.to_ulong(): 用b中同样的二进制位返回一个unsigned long 值
os<<b: 把b中的位集输出到os流
b<<k: 把b中所有为1的位向高位移k个位置
b>>k: 把b中所有为1的位向低位移k个,自动舍弃超出范围的位
b=(int)n: 用n的二进制位填充b
两个同级bitset对象还可以进行^、&、| 等操作。
bitset的常数很小,进行整体操作的复杂度大概b.size()/64的样子。
1. 创建bitset对象
创建bitset对象时必须要指定容器大小。bitset对象的大小一经定义就不能修改了。
bitset<100>b:定义bitset对象b,能容纳100个比特位(0-99),所有位初始都为0。
2.设置元素值
(1)采用下标法:b[pos]=1。
bitset<5>b;
b[1]=1,b[2]=1,b[4]=1;
输出:01101
(2)采用set()方法,一次性将元素设置为1。
bitset<5>b;
b.set();
输出:11111
(3)采用set(pos,1)方法直接将pos位置为1或者0
bitset<5>b;
b.set(1,1);
b.set(2,1);
b.set(4,1);
输出:01101
(4)采用reset(pos)方法将pos位置为0
bitset<5>b;
b.set(1,1);
b.set(2,1);
b.set(4,1);
b.reset(2);
输出:01001
3.输出元素
(1)采用下标法输出元素
bitset<5>b;
for(int i=0;i<5;i++) cout<<b[i];
for(int i=0;i<b.size();i++) cout<<b[i];
(2)直接向输出流输出所有元素
cout<<b<<endl;
bitset在很多优化技巧上可以用到,是很好用的stl容器。
我自己拉了一个bitset专题练习,对相关知识进行总结一下:
Bitset
题意:输出n个二进制位。
bitset裸题,直接采用上述赋值法,然后从首个不为0的高位到低位输出即可。
int n;
bitset<11>b;
while(~scanf("%d",&n))
{
b=n;
int flag=0;
for(int i=10;i>=0;i--)
{
if(b[i]) printf("%d",int(b[i])),flag=1;
else if(flag) printf("%d",int(b[i]));
}
puts("");
}
Matrix multiplication
题意:给你两个n*n的矩阵,求矩阵乘积取余3的矩阵。n可达800,时限2s。
直接800^3复杂度5.12e8超时的风险。注意结果对3取余再输出,那么每个初始矩阵的值都只会是0、1、2中的一个。我们可以用3维的bitset表示每一个(i,j)是1还是2,0对大答案无贡献。这样我们普通矩阵相乘是n^3的复杂度,但第三层循环完全可以用bitset优化,对应行和对应列进行&操作再统计有多有个1即知道答案的贡献了。复杂度n^3/64。
int a[maxn][maxn],b[maxn][maxn],c[maxn][maxn];
bitset<maxn>aa[maxn][2],bb[maxn][2];
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
{
aa[i][0].reset();
aa[i][1].reset();
bb[i][0].reset();
bb[i][1].reset();
}
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
{
scanf("%d",&a[i][j]);
a[i][j]%=3;
if(a[i][j]==1) aa[i][0][j]=1;
else if(a[i][j]==2) aa[i][1][j]=1;
}
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
{
scanf("%d",&b[i][j]);
b[i][j]%=3;
if(b[i][j]==1) bb[j][0][i]=1;
else if(b[i][j]==2) bb[j][1][i]=1;
}
// for(int i=0;i<n;i++)
// for(int j=0;j<n;j++)
// printf("i=%d j=%d aa0=%d bb0=%d aa1=%d bb1=%d\n",i,j,int(aa[i][0][j]),int(bb[j][0][i]),int(aa[i][1][j]),int(bb[j][1][i]));
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
c[i][j]=(2*(aa[i][1]&bb[j][0]).count()+2*(aa[i][0]&bb[j][1]).count()+(aa[i][0]&bb[j][0]).count()+4*(aa[i][1]&bb[j][1]).count())%3;
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
printf("%d%c",c[i][j],j==n-1?'\n':' ');
}
return 0;
}
Set Operation
题意:有n个集合,每个集合k个数,Q次查询,每次查询是否存在一个集合同时包含查询的两个数。
每一位表示一个集合,这一位对应的数在哪个集合中出现过就将对应位置为,查询两个数只需将这个两个数位对应的集合进行&后统计1的个数是否为0即可。
const int N=1e4+5;
const int maxn=800+5;
bitset<1001>b[N];
int main()
{
int n,q,k,p;
while(~scanf("%d",&n))
{
for(int i=0;i<N;i++) b[i].reset();
for(int i=0;i<n;i++)
{
scanf("%d",&k);
while(k--)
{
scanf("%d",&p);
b[p][i]=1;
}
}
scanf("%d",&q);
while(q--)
{
scanf("%d%d",&k,&p);
if((b[k]&b[p]).count()) puts("Yes");
else puts("No");
}
}
return 0;
}
Explosion
题意:魔塔都玩过吧,每个房间里都有一些钥匙,能打开对应的门,当没有钥匙能开门后只能用一颗炸弹炸开一扇门,求打开所有门需要炸弹数量的期望。
期望等于值乘以概率,值当然为1了,概率即能够通往这个门的数量的倒数。累加求和就是ans。那就得求出打开哪些门才能打开这扇门,这就是一个传递闭包的过程,用bitset优化
const int N=1e3+5;
const int maxn=800+5;
bitset<N>b[N];
int t,n,k,p;
void init()
{
scanf("%d",&n);
for(int i=1; i<=n; i++) b[i].reset();
for(int i=1; i<=n; i++)
{
scanf("%d",&k);
while(k--)
{
scanf("%d",&p);
b[i][p]=1;
}
b[i][i]=1;
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(b[j][i]) b[j]|=b[i];
}
int main()
{
scanf("%d",&t);
int t1=t;
while(t--)
{
init();
double ans=0.0;
for(int i=1; i<=n; i++)
{
double num=0;
for(int j=1; j<=n; j++)
if(b[j][i]) num+=1;
ans+=1.0/num;
}
printf("Case #%d: %.5f\n",t1-t,ans);
}
return 0;
}
Bipartite Graph
题意:给你n个点,m条边,求最多能加多少边使得这n个点变成一个完全二分图。
什么是完全二分图呢,就是两个集合中的点都互相连边了。先来看一个不等式:ab<=((a+b)^2)/2当且仅当a==b取等。于是我们可以二分图染色将原有边分成的两部分求出来,剩下的就相当于选一些集合合并,合并后的集合的点数和剩下的点数的差值最小。可以用背包优化,但bitset会更优一点,代码写好写好理解。如果用背包,那么背包容量应该设为n/2。但bitset可以直接左移或者右移表示所有数同时加上或减去一个数,我们求出所有组合然后枚举最优解即可。具体怎么求出所有组合看代码:
这个优化可以改变很多题的,很多01背包就可以用这个优化解:比如NYOJ:邮票分你一半、zb的生日。
const int N=1e4+10;
bitset<N>cur;
struct Edge
{
int to,next;
} e[N*20];
struct Node
{
int a,b;
} node[N];
int head[N],tot,flag[N];
void init()
{
tot=0;
memset(flag,0,sizeof(flag));
memset(head,-1,sizeof(head));
cur.reset();
cur.set(0);
}
void add(int u,int v)
{
e[tot].to=v,e[tot].next=head[u];
head[u]=tot++;
e[tot].to=u,e[tot].next=head[v];
head[v]=tot++;
}
void dfs(int &a,int &b, int u,int f)
{
flag[u]=1;
if(f) a++;
else b++;
for(int i=head[u]; i+1; i=e[i].next)
{
int v=e[i].to;
if(flag[v]) continue;
dfs(a,b,v,f^1);
}
}
int main()
{
int t,n,m;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
int u,v,k=0;
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
add(u,v);
}
for(int i=1; i<=n; i++)
if(!flag[i])
{
int a=0,b=0;
dfs(a,b,i,1);
node[k].a=a,node[k++].b=b;
}
for(int i=0; i<k; i++)
cur=(cur<<node[i].a)|(cur<<node[i].b);
int ans=0;
for(int i=1; i<=n; i++)
if(cur[i])
ans=max(ans,(n-i)*i-m);
printf("%d\n",ans);
}
return 0;
}
Rikka with Candies
今年多校的新题,正解应该是手写压位,但优化好的bitset也可以卡过,题意和思路就不详细介绍了。