「CF1713F」Lost Array
题目
点这里看题目。
有一个长度为 的非负整数序列 ,以此生成一个 的非负整数矩阵 :
-
对于 ,有 。
-
对于 ,有 。
-
对于 ,有 。
设 ,现在给出 ,构造一组可以生成 的 ,或报告无解。
所有数据满足 。
分析
容易发现, 对于 的贡献系数为 。
然而,在异或的含义下,我们关心的是 ,根据 Lucas 定理 为 当且仅当 (用 表示二进制下按位与)。
进一步地,我们尝试把 拆开。容易发现,上述条件等价于 ,因为如果与非零,则 的最低位不会被进位补齐,就会真的变成 。
找出 ,我们把 当作“全集”,则所有 的 必然是 的“子集”,这样我们就把 的限制变成了 条 的子集异或和的方程。
设 ,也就是先反转再补零的结果。
如果 ,那么我们就可以直接执行逆变换算出来 ,进而得到 。
然而实际情况没有这么好。设 ,则我们得到的实际上是 到 的准确值。但一个前缀的 的值是丢失的,所以光凭这些信息还不足以帮助我们快速地解出 。
我们尝试再建立一点 和 之间的联系。不妨考虑朴素的容斥,我们知道 ,而在按位异或的意义下 系数毫无作用,所以 !也可以说 和 互为子集异或和!
我们马上注意到 在某些位置上为 ,所以这又给我们新增了 条关于 的方程。一共 条方程,正好可以解出 个变量。然而,未知量集中在前缀上,有效的方程集中在后缀上,我们还需要更多的处理。
不妨再把问题化简,我们消去所有已知的 的贡献,算出 。现在我们得到的就是纯粹的和前 个 相关的问题了。结合二进制变换的一贯尿性,我们不妨考虑分治方法解决这个问题。
用 来表示“如果 仅在前 位做前缀和,已知 ,且 都是 ,求出 ”。计算过程需要讨论一点情况:
-
如果 ,则可以直接逆变换算,这是边界。
-
如果 ,则 这一段因为全为 而没有贡献,可以直接递归到 计算。
-
否则,我们必须考虑交叉贡献。
两边尝试一下,发现先计算区间 会容易一点。那么,在递归下去之前,我们需要消除 的影响。
设 。由于我们只知道 的信息,所以我们可以计算出仅在前 位做前缀和时的 。好巧不巧,根据对称性,区间 中也正好只有 个值未确定,递归下去形式已知,正好就是 。
解决完一边之后,我们可以相应地去掉 的贡献,从而得到仅在前 位做前缀和时的 。所以,我们已经到边界了!
容易发现复杂度为 ,因为每次递归大小折半且实质上只会走向单侧。
Remark.
不知道能写点什么,真正重要的内容全部用黑色加粗了。
不过我想说,tnnd 你谷题解区前三篇写的都是什么东西?正常人怎么会想到“先超集再子集”这种反常思路?
当然很有可能是因为我是一个神必。总之看得我一头雾水。一般来说,思路应当连贯,后来的多多少少要和已有的照应。思考的时候也可以注意一下,如果思路太发散就需要用纸笔辅助,避免忘了自己想过什么东西。
代码
#include <cstdio>
#include <vector>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int MAXN = ( 1 << 20 ) + 5;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
int su[MAXN];
int f[MAXN], tmp[MAXN];
int N, U, K;
inline void Conv( const int &b, int *coe ) {
int n = 1 << b;
for( int i = 0 ; i < b ; i ++ )
for( int j = 0 ; j < n ; j ++ )
if( j >> i & 1 ) coe[j] ^= coe[j ^ ( 1 << i )];
}
void Divide( const int &B, const int &L, const int &R, const int &len, int *val ) {
if( ! len || B < 0 ) return ;
int mid = ( L + R ) >> 1;
if( len == R - L + 1 ) {
rep( i, 0, len - 1 ) tmp[i] = val[i];
Conv( B, tmp );
rep( i, 0, len - 1 ) su[i + L] = tmp[i];
} else if( len <= mid - L + 1 )
Divide( B - 1, L, mid, len, val );
else {
std :: vector<int> old( val, val + len );
int rsd = len - ( R - mid ), hlf = 1 << ( B - 1 );
rep( i, 0, rsd - 1 ) val[i + hlf] ^= val[i];
Divide( B - 1, mid + 1, R, rsd, val + hlf );
rep( i, 0, hlf - 1 )
tmp[i] = i < rsd ? su[i + mid + 1] : 0;
Conv( B - 1, tmp );
rep( i, 0, hlf - 1 ) tmp[i] ^= old[i + rsd];
rep( i, 0, hlf - 1 ) val[i] = tmp[i];
Divide( B - 1, L, mid, hlf, val );
}
}
int main() {
Read( N );
for( U = 1, K = 0 ; U < N ; U <<= 1, K ++ );
rep( i, 0, N - 1 ) {
int b; Read( b );
su[( U - 1 ) ^ i] = b;
}
rep( i, 0, U - 1 ) f[i] = i <= U - 1 - N ? 0 : su[i];
Conv( K, f );
if( U == N ) {
rep( i, 1, N )
Write( f[N - i] ), putchar( " \n"[i == N] );
return 0;
}
Divide( K, 0, U - 1, U - N, f + N );
Conv( K, su );
rep( i, 1, N ) Write( su[N - i] ), putchar( " \n"[i == N] );
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2021-11-15 「牛客」牛半仙的妹子序列
2021-11-15 「CF1119G」Get Ready for the Battle
2020-11-15 [CSP2020]函数调用