bitset的使用 (组合的可能性),以及数位DP
越来越破防......
bitset是个存储二进制序列的容器,容器本身支持位运算
这道题的意思:
A→B,A和B一共有多少种数?,B不知道
B→C,B和C一共有多少种数?,C不知道。。。。。。
大问题可拆成小问题,
小问题有最小问题,DP
从最右边往左DP
每次将当前 i 的 value 计入当前 dp[ i ]的统计数量中
若右边有可达的地方,就合并集合
但合并集合很麻烦
所以就有了bitset
将bitset开到二维
第一维为当前位置 i
第二维为当前位置 i 能到的所有点的 value 的集合
将可达的点设为 1,通过异或操作即可轻松完成集合合并
要统计有几个数时,用 .count() 函数就可以获得 二进制序列中 1 的个数
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e5+10;
LL n,ans,value[N];
bitset<N> dp[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>value[i];
for(int i=n;i>=1;i--)
{
dp[i][value[i]]=1;
if(2*i<=n)
{
dp[i] |=dp[i*2];
}
if(i+value[i]<=n)
{
dp[i] |=dp[i+value[i]];
}
ans=max(ans,(LL)(dp[i].count()));
}
cout<<ans;
return 0;
}
找的是由这几个数能组成多少种组合(大于0)
很像01背包问题,只是将结果换为 0 或 1 ,表示 状态 j 可不可到达 (01背包本身就是求全部可行组合)
经典背包问题中 j 减去 cost 后就要 加上对应的 value
但这不是死的规矩,只是因为经典背包中要求的是最大 value,
背包的核心是 j - cost 或 j + cost 来确认当前可达的状态,再根据这个状态求我们要的答案
这题中我们要求的是根据上一个状态确认的可达状态,只需要知道是否可达,所以只需要 并( | )就行了
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e5+10;
LL n,m,ans,dp[110][N*2],a[N*2];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
m+=a[i];
}
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=m;j>=0;j--)
{
//因为是同时根据左右的状态转移,所以没法滚动
dp[i][j]|=dp[i-1][j];
dp[i][j]|=dp[i-1][abs(j-a[i])];
if(j+a[i]<=m) dp[i][j]|=dp[i-1][j+a[i]];
}
}
for(int i=1;i<=m;i++)
{
ans+=dp[n][i];
}
cout<<ans;
return 0;
}
豪勒,下面是bitset做法,因为我们求的只是所有组合的可能性,所以可以使用bitset
把每个位置当作得数,
加上一个数就是 << 操作再并原来的
减去一个数就是 >> 操作再并原来的
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e5+10;
LL n,m,ans,a[N];
bitset<200010> dp;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
dp[0]=1;
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp|=dp<<a[i];//要先计算总和,不然计算减去某个砝码时会漏掉
}
for(int i=1;i<=n;i++)
{
dp|=dp>>a[i];
}
cout<<dp.count()-1;//得数为0不算在内
return 0;
}
N皇后问题[https://www.luogu.com.cn/problem/P1562]
经典题,直接代码解释
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e5+10;
LL n,sta[30],ans,all;//all记录放完N个皇后,及每列全满状态(全为1).sta【】记录每行初始已有的皇后位置
void dfs(int now,int l,int r,int d)//now为每列是否有皇后放置了,l为左对角线(左下到右上),r为右对角线(右下到左上),d为第几层(从1开始
{
if(now==all){ans++;return;}//每列都放了,说明摆放成功,ans++后返回
int pos = all&(~(now|l|r|sta[d]));//pos为皇后可放置位置,now|l|r|sta[d]为已放置,取反为可放置,再与all进行按位与( & )得到皇后在该行可放置位置
while(pos)
{
int p = pos&-pos;//不断取lowbit(从右往左第一位1),dfs在位置p放置的情况
pos-=p;//之后减去p,下一个循环就可以取到当前从右往左第二个1,即可放置皇后的位置
dfs(now+p,(l+p)<<1,(r+p)>>1,d+1);//now+p为该列放置了皇后的状态,l+p<<1为左对角线位置,r+p>>1为右对角线位置,d+1为第几层
//为什么(l+p<<1)为左对角线位置,(r+p>>1)为右对角线位置?
//以左对角线(左下到右上)为例
//每当一个皇后被放置,她在下一行的攻击点为她的位置的左下角,即p<<1
//在此基础上,p并上当前所有皇后在当前行的攻击点,再左移一格,就得到了目前所有皇后左对角线在下一行的攻击点(l+p)<<1
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
all = (1<<n)-1;//计算全满状态
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
char tmp;
cin>>tmp;
if(tmp=='.') sta[i]|=1<<(n-j);//有皇后的位置使用"并"计算( | )记录
}
}
dfs(0,0,0,1);//从目前0皇后,0左对角线,0右对角线,处于第1层开始计算
cout<<ans;
return 0;
}

浙公网安备 33010602011771号