#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\) 数组,更具体地,可以写出它的表达式:

\[c_s=\sum_{i=1}^n\prod_{j=0}^{k-1}[(-1)^{p\odot a_{i,j}}=(-1)^{popcount(s\and 2^j)}] \]

既然 \(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\) 对异或运算的分配律:

\[(i\odot j)\oplus (i\odot k)=i\odot(j\oplus k) \]

那么可以推导出:

\[g_T=\sum_{i=1}^n (-1)^{p\odot z_{i,T}}=\sum_{i=1}^n\prod_{j\in T}(-1)^{p\odot a_{i,j}} \]

\(\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\)),所以可以推导出:

\[g_T=\sum_{s=0}^{2^k-1}(-1)^{T\odot s} c_s \]

发现 \((-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)\),转移:

\[dp(i,s)=\sum_{a|b=s} dp(i-1,a)\cdot dp(i-1,b) \]

注意转移并不需要要求集合 \(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;}
	}
}
posted @ 2022-05-15 11:54  C202044zxy  阅读(171)  评论(3编辑  收藏  举报