首页 > 其他分享 >SOS dp

SOS dp

时间:2024-04-07 21:23:01浏览次数:19  
标签:const 前缀 int ll mask SOS 高维 dp

SOS dp是解决类似这样的问题

\[\begin{aligned} F[mask] &= \sum _ {i \subseteq mask} A[i]\end{aligned} \]

的一种解法(其实就是高维前缀和)。

这种问题,有不同的解法,比如:

1.时间复杂度 \(O(4^n)\)

for(int mask = 0;mask < (1<<N); ++mask){
	for(int i = 0;i < (1<<N); ++i){
		if((mask&i) == i){
			F[mask] += A[i];
		}
	}
}

不过时间复杂度太高,正常一点的出题人出的题目一定过不了。

2.时间复杂度 \(O(n^3)\)

// iterate over all the masks
for (int mask = 0; mask < (1<<n); mask++){
	F[mask] = A[0];
    // iterate over all the subsets of the mask
    for(int i = mask; i > 0; i = (i-1) & mask){
    	F[mask] += A[i];
    }
}

时间复杂度证明见codeforces

3.SOS dp

我们记 \(F[mask][i]\) 为对于数 \(mask\) 的二进制中,第 \(i\) 位是不同的.

若 \(mask\) 的第 \(i\) 位是 \(0\),则

\(F[mask][i]=F[mask][i-1]\)

若 \(mask\) 的第 \(i\) 位是 \(1\),则

\(F[mask][i]=F[mask][i-1]+F[mask \bigoplus 2^i][i-1]\)
简单来讲,就是:

上图描述了我们如何将\(S(mask,i)\) 集相互关联。任何集合 \(S(mask,i)\)的元素都是其子树中的叶子。红色前缀表示掩码的这一部分对其所有成员/子级都是通用的,而掩码的黑色部分可以不同。

请注意,这些关系形成了一个有向无环图,而不一定是有根树。

在实现了这些关系之后,我们可以很容易地提出相应的动态规划。

代码:

//iterative version
for(int mask = 0; mask < (1<<N); ++mask){
	dp[mask][-1] = A[mask];	//handle base case separately (leaf states)
	for(int i = 0;i < N; ++i){
		if(mask & (1<<i))
			dp[mask][i] = dp[mask][i-1] + dp[mask^(1<<i)][i-1];
		else
			dp[mask][i] = dp[mask][i-1];
	}
	F[mask] = dp[mask][N-1];
}

还可以继续优化:

//memory optimized, super easy to code.
for(int i = 0; i<(1<<N); ++i)
	F[i] = A[i];
for(int i = 0;i < N; ++i)
    for(int mask = 0; mask < (1<<N); ++mask){
        if(mask & (1<<i))
            F[mask] += F[mask^(1<<i)];
}

注意,代码中的 \(N\) 为最大的二进制长度,即 \(N\) 为题目中给定的最大的 \(log n\)。

例题:

1.CF165E Compatible Numbers

作为 CF 中 div2 的最后一题,本人认为有点水。

这其实就是一道高维前缀和的模板题。

分析:对于每一对 \(ai \& aj = 0\),若 \(ai\) 和 \(aj\) 的二进制表示中有同一位均为 \(1\),那么 \(ai \& aj\) 的这一位也会为 \(1\)。因此可以发现,\(ai\) 是 \(aj\) 按位取反后的子集。

实现:预处理出数组 \(a\) 的高维前缀和,每一次询问时先取反,再看取反后子集是否为空。高维前缀和只需存储子集中的任意一个数即可。

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 1e9 + 7;
const int N = 1000005;
const int INF = 0x3f3f3f3f;
int a[N];
int f[1 << 22];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        f[a[i]] = i;
    }
    for (int i = 0; i < 22; i++) {
        for (int j = 0; j < 1 << 22; j++) {
            if (f[j] == 0 && (j >> i & 1)) f[j] = f[j ^ 1 << i];
        }
    }
    a[0] = -1;
    for (int i = 1; i <= n; i++) {
        printf("%d ", a[f[a[i] ^ (1 << 22) - 1]]);
    }
    printf("\n");
    return 0;
}

2. CF449D Jzzhu and Numbers

若设 \(fs\) 表示子集含有 \(s\) 的方案数,那么实际上 \(fs\) 就是所有含 \(s\) 的集合的高维前缀和,倒着处理一遍所有含 \(s\) 的 \(a\) 的个数,那么显然 \(fs=2^{fs}-1\),接下来根据差分的思想再差分回去就好了。

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 1e9 + 7;
const int N = 1000005;
const int INF = 0x3f3f3f3f;
int a[1 << 20];
ll two[N], f[1 << 20];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        a[x]++;
    }
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < 1 << 20; j++) {
            if (!(j >> i & 1)) a[j] += a[j ^ 1 << i];
        }
    }
    two[0] = 1;
    for (int i = 1; i <= n; i++) two[i] = two[i - 1] * 2 % mod;
    for (int j = 0; j < 1 << 20; j++) f[j] = two[a[j]] - 1;
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < 1 << 20; j++) {
            if (!(j >> i & 1)) f[j] -= f[j ^ 1 << i], f[j] %= mod;
        }
    }
    printf("%lld\n", (f[0] + mod) % mod);
    return 0;
}

最后,我发现这场 CF 做 D 题的人比做 A 的还多

标签:const,前缀,int,ll,mask,SOS,高维,dp
From: https://www.cnblogs.com/jxy2012/p/18119898

相关文章

  • 以太网UDP:心跳包、ICMP与ARP
    参考:https://juejin.cn/post/6844903951452602375心跳包UDP:用户数据报协议:主要用在实时性要求比较高的以及对质量相对较弱的地方.但是面对现在高质量的线路不会容易丢包,除非是一些拥塞条件下,如流媒体TCP:传输控制协议:是面连接的那么运行环境必然要求其可靠性不可丢包,......
  • Hetao P1178 冒险者 题解 [ 绿 ][ 最短路 ][ 线性 dp ]
    原题题解本蒟蒻采用的和大部分人解法不同,是根据当前标记值的总和跑最短路的一种解法。思路30min,调代码2h的我太蒻了首先观察题面可以发现本题求的是最少操作数,由于要求最小且有变化的过程,所以可以使用dp求解,也可以使用最短路算法求解,本篇先介绍最短路的算法。其实......
  • QFComponent for lazarus增加新控件TQFGridPanelComponent
    TQFGridPanelComponent控件支持在单元格绑定可视控件,运行时单元格绑定的控件会吸附到相应的单元格里。|姓名|[#][C2]单位|办公地址|备注||:-:|:-:|:-:|:-:||秋风6|[bm4]检测中心1|南山建工村1|||秋风7|检测中心2|<COMPNAME=name3>[#][c4]南山建工村2|||[c2]地址|<COMPNAME=n......
  • 数位dp
    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555题目大意:给一个数字n,范围在1~2^63-1,求1~n之间含有49的数字有多少个。思路:经典的数位DP,学习了一下,看的别人的代码:http://www.cnblogs.com/luyi0619/archive/2011/04/29/2033117.html状态转移:dp[i][0......
  • [笔记]数位dp例题及详解(更新中)
    数位dp的定义引自洛谷日报#84:求出在给定区间\([L,R]\)内,符合条件\(f(i)\)的数\(i\)的个数。条件\(f(i)\)一般与数的大小无关,而与数的组成有关。由于是按位dp,数的大小对复杂度的影响很小。由于数位dp状态的上下文信息比较多,所以一般用记忆化搜索实现,而非递推。P4999烦人的数......
  • socket编程——C++实现基于UDP协议的简单通信(含详解)
    文章后面有代码,可以直接复制在VisualStudio2022中运行(注意:必须是两个项目,客户端服务端各一个,连接在同一网络中,先运行服务端,并且客户端数据发送的目标IP要改为你服务端的IP)目录前言帮助文档一、UDP通信框架1.服务端2.客户端二、服务端实现1.加载库(WSAStartup函数)......
  • 背包DP
    01背包定义dp[i][j]表示从前i件物品中选,体积不超过j的最大价值N,V=map(int,input().split())v=[0]*(N+1)w=[0]*(N+1)foriinrange(1,N+1):v[i],w[i]=map(int,input().split())f=[[0]*(V+1)for_inrange(N+1)]#对于第i件物品,选或......
  • Windows 11 RDP 设置自定义证书
    1.随便生成一个证书或者去freessl之类的地方申请一个证书2.将证书转换成pfx格式opensslpkcs12-export-inkeyprivate_key.key-incertificate.pem-certfileCACert.pem-outcertificate.pfx3.打开certlm右键个人->所有任务->导入,导入刚刚创建的pfx证书......
  • 数位DP
    CF204A题目链接https://codeforces.com/problemset/problem/204/A题目大意模板讲解数位DP模板#include<bits/stdc++.h>#definelllonglongusingnamespacestd;intn,m,t;lll,r;strings;llmemo[20][10][10];lldfs(inti,intfirst,intlast,boolis_limi......
  • Offer必备算法21_回文串dp_六道力扣题详解(由易到难)
    目录①力扣647.回文子串解析代码②力扣5.最长回文子串解析代码③力扣1745.分割回文串IV解析代码④力扣132.分割回文串II解析代码⑤力扣516.最长回文子序列解析代码⑥力扣1312.让字符串成为回文串的最少插入次数解析代码本篇完。①力扣647.回文子串64......