#14 CF1103D & CF1119H & CF908H
Professional layer
题目描述
解法
又和正解想到一块去了,话说我为什么现在觉得这些题都不是特别难了。
首先求出所有数的 \(\gcd\),记为 \(G\),那么 \(G\) 的质因子种类至多 \(m=11\) 种,并且每次操作至少都要把一种质因数的次数归零,所以可以得到操作次数也 \(\leq m\)
那么不难设计一个暴力的状压 \(dp\),设 \(f[i][s]\) 表示已经操作了 \(i\) 个数,解决的质因数集合是 \(s\),的最小花费总和,使用子集枚举可以做到 \(O(nm\cdot 3^m)\)
优化显然要在这个 \(n\) 上下功夫,我们考虑一个数的作用是解决某个 \(s\),那么从 \(s\) 的角度考虑,我们只需要保留花费最小的 \(m\) 个数就可以避免冲突。
那么我们可以得到 \(O(m\cdot 2^m)\) 个形如 \((i,s)\) 的有效转移,表示数 \(i\) 可以解决集合 \(s\),转移的时候按 \(i\) 分层,上一层向下一层转移(因为一个数最多解决一个集合),时间复杂度 \(O(m^2\cdot3^m)\)
剩下的问题是如何求出这些有效转移,我们先把 \(a_i\) 只保留 \(G\) 中的质因子,这样最多只有 \(12000\) 左右的种类,对于每个种类选取前 \(m\) 个加入到所有可能解决的集合中,最后对于每种 \(s\) 再保留前 \(m\) 个即可。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
const int M = 1000005;
const int N = 1<<11;
const int inf = 1e15;
const int MOD = 1e6+7;
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,G,ans,a[M],b[M],p[12],tot,F[MOD];
int sum[N],f[12][N],g[12][N];vector<int> v[M],vc[N];
vector<pair<int,int>> d,t;
struct edge{int h,next;}e[M];
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int cmp(int i,int j) {return b[i]<b[j];}
void ins(int h1,int h2,int id)
{
for(int i=F[h1];i;i=e[i].next) if(e[i].h==h2)
{v[i].pb(id);return ;}
e[++tot]=edge{h2,F[h1]};F[h1]=tot;v[tot].pb(id);
}
void solve(int x,vector<int> &v)
{
int sz=min((int)v.size(),m);
sum[0]=1;sort(v.begin(),v.end(),cmp);
for(int i=0;i<m;i++)
{
int y=1;
while(x%p[i]==0) x/=p[i],y*=p[i];
for(int s=0;s<(1<<i);s++)
sum[s|(1<<i)]=sum[s]*y;
}
for(int s=1;s<(1<<m);s++) if(sum[s]<=k)
for(int i=0;i<sz;i++)
vc[s].pb(v[i]);
}
void upd(int i,int s,int c)
{
if(f[i][s]>c) f[i][s]=c,d.pb({i,s});
}
signed main()
{
n=read();k=read();
for(int i=1;i<=n;i++) G=gcd(G,a[i]=read());
for(int i=1;i<=n;i++) b[i]=read();
//decompose G
for(int i=2;i*i<=G;i++) if(G%i==0)
{p[m++]=i;while(G%i==0) G/=i;}
if(G>1) p[m++]=G;
//find all useful trans
for(int i=1;i<=n;i++)
{
int x=a[i],y=1;
for(int j=0;j<m;j++)
while(x%p[j]==0) x/=p[j],y*=p[j];
ins(y%MOD,y,i);//hashing
}
for(int i=1;i<=tot;i++) solve(e[i].h,v[i]);
//sorting
for(int s=0;s<(1<<m);s++)
{
sort(vc[s].begin(),vc[s].end(),cmp);
int sz=min((int)vc[s].size(),m);
for(int i=0;i<sz;i++) t.pb({vc[s][i],s});
}
sort(t.begin(),t.end());
//dp
for(int i=0;i<=m;i++)
for(int s=0;s<(1<<m);s++)
f[i][s]=g[i][s]=inf;
f[0][0]=g[0][0]=0;
for(int i=0,lst=0;i<t.size();i++)
{
int u=t[i].first,s=t[i].second,w=b[u];
if(u!=lst)
{
for(auto x:d)
g[x.first][x.second]=
f[x.first][x.second];
d.clear();lst=u;
}
upd(1,s,w);
int U=((1<<m)-1)^s;
for(int t=U;t;t=(t-1)&U)
for(int c=0;c<m;c++)
upd(c+1,s|t,g[c][t]+w);
}
ans=inf;
for(int i=0;i<=m;i++) if(f[i][(1<<m)-1]<inf)
ans=min(ans,f[i][(1<<m)-1]*i);
printf("%lld\n",(ans>=inf)?-1:ans);
}
Triple
题目描述
解法
我无语了,这篇文章 几句话就证出来了,那不是显得我说了一堆废话吗?
真的被教育了,花了整整一个下午才完全明白,你需要完全理解异或卷积的原理才能理解本题。
约定:记 \(x\odot y\) 表示 \(x\and y\) 的二进制位上 \(1\) 个数的奇偶性。
发现本题就是黎明前的巧克力
的加强版,我们提出一个更为普遍的问题并尝试解决它:集合中有 \(m\) 个元素,给定 \(n\) 个集合幂级数 \(f_i=\sum_{j=0}^{k-1}w_j\cdot x^{a_{i,j}}\),其中 \(k\) 和 \(w\) 数组都已知,求 \(\prod_{i=1}^n f_i\) 的每一项。
考虑暴力做法就是对每个 \(f_i\) 分别快速沃尔什变换(下文简称变换)然后乘起来得到 \(F\),现在我们考虑快速求出 \(F\),我们下文着重讨论 \(F\) 的第 \(p\) 位所具有的性质。
根据异或卷积的基本知识可以知道,\(F_p\) 可以写成 \(\prod_{s=0}^{2^k-1}(\sum_{j=0}^{k-1} \pm w_j)^{c_s}\) 的形式,其中 \(s\) 代表一种系数组合,如果它的第 \(j\) 位为 \(1\) 则 \(w_j\) 前面的符号是 \(-1\),如果它的第 \(j\) 位为 \(0\) 则 \(w_j\) 前面的符号是 \(1\),\(c_s\) 就表示这种系数组合在所有 \(\hat f_i\) 中的出现次数。
值得注意的是,\(c_s\) 和 \(w_j\) 完全没有关系,每个 \(p\) 上天然具有这样的 \(c\) 数组,更具体地,可以写出它的表达式:
既然 \(w_j\) 是系数,那么我们可以通过调整系数构造方程反解出 \(c_s\),考虑到 \(T\subseteq\{0,1...k-1\}\) 的数量正好有 \(2^k\) 个,对于每个 \(T\) 我们考虑构造出对应的方程。
设 \(z_{i,T}=\sum_{j\in T} a_{i,j}\),我们考虑集合幂级数 \(h_T=\sum_{i=1}^n x^{z_{i,T}}\),对其进行变换得到 \(\hat h_T\),记 \(g_T=\hat h_{T,p}\)(我们还是只考虑第 \(p\) 位),那么现在考虑构建 \(c_s\) 和 \(g_T\) 之间的等式关系,需要利用 \(\odot\) 对异或运算的分配律:
那么可以推导出:
\(\prod_{j\in T} (-1)^{p\odot a_{i,j}}\) 可以看成集合幂级数 \(f_i\) 给位置 \(p\) 的贡献,考虑如果 \((-1)^{p\odot z_{i,T}}=-1\),那么贡献是 \(-1\);如果 \((-1)^{p\odot z_{i,T}}=1\),那么贡献是 \(1\);把这东西对应到 \(c_s\) 上去,这里 \(c_s\) 的意义理解为对 \(n\) 个集合幂级数贡献的求和,那么如果 \((-1)^{T\odot s}=-1\),则贡献 \(-c_s\);如果 \((-1)^{T\odot s}=1\),那么贡献 \(c_s\)(结合上面的表达更好理解,原因就是要 \(T\) 和 \(s\) 中同时出现才贡献 \(-1\)),所以可以推导出:
发现 \((-1)^{T\odot s}\) 正好是变换的系数,所以我们把 \(g\) 逆变换就可以得到 \(c\),时间复杂度 \(O(n2^k+(m+k)2^{m+k})\)
算法流程建议参考一下我带注释的代码,可以帮助你再次梳理思路:
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1<<17;
const int MOD = 998244353;
const int inv2 = (MOD+1)/2;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,W[M],w[M],lg[M],f[M],g[M],z[M][5],sum[M];
int qkpow(int a,int b)//快速幂
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
void fwt(int *a,int n,int op)//异或卷积
{
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=i<<1)
for(int k=0;k<i;k++)
{
int x=a[j+k],y=a[i+j+k];
a[j+k]=(x+y)%MOD;
a[i+j+k]=(x-y+MOD)%MOD;
if(op==-1)
a[j+k]=a[j+k]*inv2%MOD,
a[i+j+k]=a[i+j+k]*inv2%MOD;
}
}
signed main()
{
n=read();m=read();k=3;
int h[1<<k][1<<m]={};
for(int i=0;i<k;i++) w[i]=read();
lg[0]=-1;
for(int i=0;i<(1<<k);i++)//预处理出系数组合对应的权值
{
if(i) lg[i]=lg[i>>1]+1;
for(int j=0;j<k;j++)
{
if(i>>j&1) W[i]=(W[i]-w[j]+MOD)%MOD;
else W[i]=(W[i]+w[j])%MOD;
}
}
for(int i=1;i<=n;i++)//读入集合幂级数
{
for(int j=0;j<k;j++) z[i][j]=read();
h[0][0]++;//计算h
for(int j=1;j<(1<<k);j++)
{
int low=j&(-j);
sum[j]=sum[j-low]^z[i][lg[low]];
h[j][sum[j]]++;
}
}
//对h进行正变换
for(int i=0;i<(1<<k);i++) fwt(h[i],1<<m,1);
for(int i=0;i<(1<<m);i++)//对于每种个位置分别求系数
{
for(int j=0;j<(1<<k);j++)
g[j]=h[j][i];
fwt(g,1<<k,-1);//逆变换出c_s
f[i]=1;//根据 f 的形式计算
for(int j=0;j<(1<<k);j++)c++
f[i]=f[i]*qkpow(W[j],g[j])%MOD;
}
fwt(f,1<<m,-1);//逆变换回去
for(int i=0;i<(1<<m);i++) printf("%lld ",f[i]);
puts("");
}
New Year and Boolean Bridges
题目描述
分享一段最近看到的话:
感觉反演一般是增添一个求和号之后再交换求和顺序,以退为进简化问题?
解法
感觉本题更偏向于构造,现在看也并没有什么难点。
关系为 A
的 \((i,j)\) 一定处在同一个强连通块中,所以可以把它们先连起来。然后如果关系为 X
的 \((i,j)\) 缩在了一个强连通块中那么一定无解,可以把这个先判断掉。
假设我们已经确定了有向图的强连通块,那么剩下的点一定是连成一条链,这样就可以轻易地满足所有限制。而强联通块内部为了节省边数一定是连城大环,设点数 \(\geq 2\) 的强连通块有 \(i\) 个,那么答案就是 \(n+i-1\)
我们需要尽可能减少 \(i\),就需要把某些强连通分量合并到一起。考虑这样的强连通分量最多 \(23\) 个,可以用 \(dp\) 解决问题,设 \(dp(i,s)\) 表示共有 \(i\) 个合并后的强连通分量,其包含原来强连通分量的集合是 \(s\),可以预处理出 \(dp(0,s)\),转移:
注意转移并不需要要求集合 \(a,b\) 不交,因为如果一个集合合法,那么其子集也一定合法。显然可以用 \(\tt fwt\) 优化转移,而且每次转移之后不需要逆变换回去,我们只需要判断 \(dp(i,U)\) 是否合法即可,那么对单项进行逆变换只需要子集反演即可,时间复杂度 \(O(\frac{n}{2}\cdot 2^{n/2})\)
为了防止 \(dp\) 数组在过程中乘爆需要带上模数,出错概率极低。
#include <cstdio>
const int M = 55;
const int N = 1<<23;
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,fa[M],b[M],ban[M],siz[M];char s[M][M];
int f[N],g[N],lg[N];
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void fwt(int *a,int n)
{
for(int i=1;i<n;i<<=1) for(int j=0;j<n;j+=i<<1)
for(int k=0;k<i;k++) a[i+j+k]+=a[j+k];
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
for(int j=1;j<i;j++) if(s[i][j]=='A')
fa[find(i)]=find(j);
}
for(int i=1;i<=n;i++) for(int j=1;j<i;j++)
if(s[i][j]=='X' && find(i)==find(j))
{puts("-1");return 0;}
for(int i=1;i<=n;i++) siz[find(i)]++;
for(int i=1;i<=n;i++) if(siz[i]>1) b[i]=++m;
if(!m) {printf("%d\n",n-1);return 0;}
for(int i=1;i<=n;i++) for(int j=1;j<i;j++)
{
int x=find(i),y=find(j);
if(s[i][j]=='X' && b[x] && b[y])
{
ban[b[x]]|=1<<(b[y]-1);
ban[b[y]]|=1<<(b[x]-1);
}
}
f[0]=1;lg[0]=-1;
for(int i=1;i<(1<<m);i++)
{
lg[i]=lg[i>>1]+1;
int low=i&(-i),x=lg[low]+1;
if(!((i^low)&ban[x])) f[i]=f[i^low];
}
if(f[(1<<m)-1]) {printf("%d\n",n);return 0;}
fwt(f,1<<m);int ans=n;
for(int i=0;i<(1<<m);i++) g[i]=f[i];
while(1)
{
ans++;
for(int i=0;i<(1<<m);i++)
f[i]=1ll*f[i]*g[i]%MOD;
int u=0;
for(int i=0;i<(1<<m);i++)
{
if((m-__builtin_popcount(i))&1)
u=(u-f[i]+MOD)%MOD;
else u=(u+f[i])%MOD;
}
if(u) {printf("%d\n",ans);return 0;}
}
}