子集和DP
这个东西好玄学……
巧了Catherine就喜欢玄学!
总结的话,等我再刷两个T再upd。可能以后再upd吧……
void fwt_or(int f[], int opt)
{
opt = (opt + mod) % mod;
for(int l=2,k=1; l<=len; l<<=1,k<<=1)
for(int i=0; i<len; i+=l)
for(int j=0; j<k; j++)
f[i+j+k] = (f[i+j+k]+1ll*opt*f[i+j]%mod)%mod;
}
这东西我不会,只是想装X。
B. 放进去
对每个物品分别考虑(假设只有一行),选择一个集合,贡献就是其中对应最小的a,用二进制数表示1表示选了0表示没选,最后的答案就是f[s]+sumb[s]。对每个集合求sumb的话按lowbit拆开算就好了,主要是这个f比较复杂。
预处理:把a降序排列,如果当前的前缀 s 包含 i ,f[s] += a[i],对不包含 i 的上一个前缀减掉a[i]。这样的话如果从后往前(按操作顺序,其实就是每次跳回上一个子集)求和,就相当于先假设最小值是全局最小a[min],回到上一个子集加上a[cmin]-a[min](cmin表示次小值),就是推翻原来的假设再新建一个。
把操作顺序看成时间观察这些状态,时间从后往前就是最小值从小到大依次缺失。
每个f[s]最终的结果就是s的超集对应的f全部求和。
- 为什么是超集求和?
如果某一列没有选,它一定不会成为最小值,但是把它选上它也不一定是最小值,只有当它以及比它a更小的位置全都为0的,不选这一列才会对答案造成影响(使这一行的最小值变大)。
要找到答案变成了什么,就是找到最小值缺失的程度(从谁以下更小的数都不被包含在集合),把当前集合和预处理得到的这些状态进行“匹配”,如果当前集合是预处理出的某些状态的子集,设这些状态中时间最早的是 x ,(按照时间排列预处理得到的状态形成一个序列) x 的后缀和就是不断推翻对最小值的假设知道第 x 大的数(降序排列的第x个)作为最小值对当前状态成立。
由于物品很多,每组物品排序的结果有很多种,我们选择把所有的超集拿出来求和,没有预处理过的状态 f 值为0多加没有影响,分开考虑 f 不为0的所有状态恰好就是上文从 x 开始累记的“后缀和”,因为对于每个物品都是求和运算,可以把状态合并。
- 怎么快速得到所有超集的和?
所有的状态都被压成了 m 位的二进制数,设第一层循环到了 i ,这一轮结束后的效果是每个数都变成了只考虑最后 i 位的超集和,每一个第 i 位是0的状态都累加上的末 i-1 位的答案。
把每个状态单独拿出来就是:
可以转移到 00000 的有 00001,00010,00100,01000……
转移到 00001 的有 00011,00101,01001,10001……
转移到 00010 的有 00011,00110,01010,10010……
转移到 00011 的有 00111,01011,10011……
转移到 00110 的有 00111,01110,10110……
规律比较明显,就是每次尝试往每一位加1,本来就是1的跳过。
如果按照循环的顺序考虑的话:
省略前面的 f ,第一轮 0000 += 0001,0010 += 0011,0100 += 0101,0110 += 0111……
第二轮 0000 += 0010 (0010 = 0010 + 0011),0001 += 0011,0100 += 0110(0110 = 0110 + 0111)……
这个形式就像统计后缀和,每次只加它的下一项,和前缀和的统计方式同理,拿前缀和来举例子,对于高维前缀和,计算方式如下:
code
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int p=1;p<=k;p++)
a[i][j][p]+=a[i-1][j][p];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int p=1;p<=k;p++)
a[i][j][p]+=a[i][j-1][p];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int p=1;p<=k;p++)
a[i][j][p]+=a[i][j][p-1];
高维前缀和开了很多数组,把每一维分开计算;回到这个题,二进制数的每一位就像一个[],m 位二进制数看成求 m 维后缀和,对每一维(二进制数的每一位)考虑都有下一项,每一位的下一项就是在这一位上加1,如果这一位本来就是1那么它不存在下一项它的当前维后缀和就是自己跳过就好了。把自己跳过的具体实现就是用两层循环把自己这一位岔开。
于是问题解决,代码来自Chen_jr大佬%%%%%%%%%%%。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 34000000;
int n, m, a[30], lg[maxn], p[30], b[30];
ll sb[maxn], f[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
bool cmp(int x, int y) {return a[x] < a[y];}
int main()
{
freopen("putin.in", "r", stdin);
freopen("putin.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<=m; i++) p[i] = i - 1;
for(int i=1; i<=n; i++)
{
for(int j=0; j<m; j++) a[j] = read();
sort(p+1, p+1+m, cmp);
for(int s=0,j=m; j>0; j--)
{
f[s] -= a[p[j]]; s |= (1<<p[j]); f[s] += a[p[j]];
}
}
for(int i=0; i<m; i++)
{
for(int j=0; j<(1<<m); j+=(1<<i+1))
{
for(int k=0; k<(1<<i); k++)
{
f[j+k] += f[j+k+(1<<i)];
}
}
}
for(int i=1; i<=m; i++) b[i] = read();
for(int i=2; i<(1<<m); i+=i) lg[i] = lg[i>>1] + 1;
for(int i=1; i<(1<<m); i++) sb[i] = sb[i^(i&-i)] + b[lg[i&-i]+1];
ll ans = 4e18;
for(int s=1; s<(1<<m); s++) ans = min(ans, sb[s]+f[s]);
printf("%lld\n", ans);
return 0;
}