【DP】解析 SOSdp(子集和 dp)
引入
求
解释: 即 ,熟悉位运算的同学很容易看出 就是二进制表示的集合 中 的子集。
其中 是子集 所对应的贡献。
举例来说:
的所有子集为
那么对于 式,当 时,
子集和dp 就是用来高效求解上述的 的。
原理
我们用 表示二进制表示的集合 的最后 位变化的所有子集的贡献的和。
听起来不太好理解,举例来说:
我们约定,一个二进制数从右到左的下标 分别为 ,如
index: 4 3 2 1 0
number:1 0 1 1 0
如
(这里就是 部分的所有子集)
考虑状态转移:
对于一个状态
- 位为 时,有
- 位为 时,有
原理很好理解,当 位为 时,只能选择不取。 当 位为 时可以选择取和不取,那么贡献就是取和不取的贡献之和。
代码:类似于 背包,我们可以去掉一维(由柿子特征恒等变形)
void sos(){
for(int i=0;i<(1<<N);i++)
f[i]=w[i];
for(int i=0;i<N;i++)
for(int st=0;st<(1<<N);st++)
if(st&(1<<i)) f[st]+=f[st^(1<<i)];
}
例题:
https://codeforces.com/gym/102576/problem/B
分析
利用 定理,转化为求 的对数,用sos求解即可。
代码
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read()
{
char c=getchar();
int x=0,f=1;
while(c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=20;
int w[1<<N],f[1<<N];
int n;
void sos(){
for(int i=0;i<N;i++)
for(int st=0;st<(1<<N);st++)
if(st&(1<<i)) f[st]+=f[st^(1<<i)];
}
int main(){
int T; cin>>T;
while(T--){
memset(f,0,sizeof f);
n=read();
for(int i=1;i<=n;i++){
w[i]=read();
f[w[i]]++;
}
sos();
ll res=0;
for(int i=1;i<=n;i++)
res+=f[w[i]];
cout<<res<<endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效