[CF286E]Ladies' Shop

本文深入解析了一道复杂的算法竞赛题目,涉及数学证明、群论、动态规划和快速傅立叶变换(FFT)。通过详细的推导过程,阐述了如何寻找满足特定条件的最小集合,以及如何利用生成函数和FFT进行高效计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

传送门 to luogu

洛谷上根本交不起……建议直接去 CF \text{CF} CF上提交。

传送门 to CF

题目概要
给定集合 A = { a 1 , a 2 , a 3 , … , a n } A=\{a_1,a_2,a_3,\dots,a_n\} A={a1,a2,a3,,an} m m m,求一个集合 P = { p 1 , p 2 , p 3 , … , p k } P=\{p_1,p_2,p_3,\dots,p_k\} P={p1,p2,p3,,pk},满足这些条件:

  • ∀ ⟨ t 1 , t 2 , t 3 , … , t k ⟩ ∈ N k , ∑ i = 1 k t i p i > m ∨ ∑ i = 1 k t i p i ∈ A \forall \langle t_1,t_2,t_3,\dots,t_k\rangle\in\N^k,\sum_{i=1}^{k}t_ip_i>m\vee \sum_{i=1}^{k}t_ip_i\in A t1,t2,t3,,tkNk,i=1ktipi>mi=1ktipiA
  • ∀ a ∈ A , ∃ ⟨ t 1 , t 2 , t 3 , … , t k ⟩ ∈ N k , ∑ i = 1 k t i p i = a \forall a\in A,\exist\langle t_1,t_2,t_3,\dots,t_k\rangle\in\N^k,\sum_{i=1}^{k}t_ip_i=a aA,t1,t2,t3,,tkNk,i=1ktipi=a

用大白话说一遍:

  • P P P中选若干个元素(可以重复选择),对其求和,如果不大于 m m m,则这个和应当为 A A A中的元素。
  • A A A中的任意一个元素都可以由上面的这种选择方式凑成。

这样的 P P P可能有很多个,你只需要输出集合大小,也就是 k k k,最小的一个。没有则输出“ NO \text{NO} NO”。

数据范围与约定

  • n , m ≤ 1 0 6 n,m\le 10^6 n,m106
  • A ⊆ Z , A ⊆ [ 1 , n ] A\subseteq \Z,A\subseteq[1,n] AZ,A[1,n]

思路

首先, p ≤ m p\le m pm;否则将其去掉,仍然合法。

结论一

显然,只在 P P P中选一个元素 p x p_x px,也应当满足限制一。根据题意有 p x > m ∨ p x ∈ A p_x>m\vee p_x\in A px>mpxA

p x ≤ m p_x\le m pxm,所以只能是 p x ∈ A ⇔ P ⊆ A p_x\in A\Leftrightarrow P\subseteq A pxAPA

然而,这并不是充要条件。在下文的论述中也应该注意这些关系,读者不妨自己考究一下,下面的这些结论也好、推论也罢,究竟是不是充要关系。

结论二

考虑 a x , a y ∈ A ( x ≠ y ) a_x,a_y\in A(x\ne y) ax,ayA(x=y)两个元素。

限制二 a x , a y a_x,a_y ax,ay可以写成 p p p之和。故, a x + a y a_x+a_y ax+ay可以写成 p p p之和。由限制一 p p p之和应当为 A A A的元素,否则大于 m m m

总结一下, a x + a y ∈ A ∨ a x + a y > m a_x+a_y\in A\vee a_x+a_y>m ax+ayAax+ay>m

用群论的话来说, ( A , + ) (A,+) (A,+)部分满足封闭性。

推论二

将一个累和的式子多用几次上面的结论二,就会发现:

∑ { u } ⊆ A u ∈ A \sum_{\{u\}\subseteq A}u\in A {u}AuA

由于 P ⊆ A P\subseteq A PA,它还有一个附加产品:

∑ { u } ⊆ P u ∈ A \sum_{\{u\}\subseteq P}u\in A {u}PuA

结论三

怎样让这个 P P P最小呢?

首先令 P = A P=A P=A,这样结论一限制二都满足了。

如果去掉一些元素,结论一显然仍然成立,所以只需要使限制二仍成立。

考虑去掉 p x p_x px的影响。不妨设 p x = a y p_x=a_y px=ay,那么原本限制二 a y a_y ay可以由 p x p_x px构成,那么现在便不可了。也就是说, a y a_y ay可以由 p x p_x px以外的 p p p构成。

不妨设 a y = p 1 + p 2 + p 3 + ⋯ + p t a_y=p_1+p_2+p_3+\cdots+p_t ay=p1+p2+p3++pt(对于集合中的元素,顺序是无关紧要的,我完全可以将其重新排列)。

根据推论二 p 1 + p 2 ∈ A , p 3 + p 4 ∈ A , … , p t − 1 + p t ∈ A p_1+p_2\in A,p_3+p_4\in A,\dots,p_{t-1}+p_t\in A p1+p2A,p3+p4A,,pt1+ptA(即使 t t t为奇数,根据结论一 p t ∈ A p_t\in A ptA)。

所以,

p x = a y = ∑ { u } ⊆ A u ⇔ ¬ ( p x ∈ P ) p_x=a_y=\sum_{\{u\}\subseteq A}u\Leftrightarrow \neg(p_x\in P) px=ay={u}Au¬(pxP)

用文字来说,如果 p p p能够由 A A A中的元素表示,则 p p p可以不属于 A A A

有意思的是,这是充要条件: a a a之和一定可以用 p p p之和来表示;不能由 a a a之和表示,也一定不能由 p p p之和表示。

推论三

还记得推论二吗? A A A中的元素之和还是某个 A A A中的元素。

将组成 p p p的这一堆 a a a分成两份,显然,两份都是某个 a a a.

于是乎,如果 p p p可以不属于 P P P,当且仅当 p = a x + a y p=a_x+a_y p=ax+ay

检查限制一

由于之前的结论一并不是限制一的充要条件,我们还需要再验证一次限制一

假设限制一没有被满足,也就是

¬ ( ∑ { u } ⊆ P u ∈ A ) \neg\left(\sum_{\{u\}\subseteq P}u\in A\right) ¬{u}PuA

发现这是很荒谬的:结论二已经帮你解决了该问题。

总结
  • 结论一 P ⊆ A P\subseteq A PA
  • 结论二 a x + a y ∈ A ∨ a x + a y > m a_x+a_y\in A\vee a_x+a_y>m ax+ayAax+ay>m
  • 推论三:如果 p = a x + a y p=a_x+a_y p=ax+ay,那么 P P P可以去掉 p p p,否则不能

所以,算法就是下面的这个流程:

  • 初始化集合 P = A P=A P=A
  • 求出所有不超过 m m m a x + a y a_x+a_y ax+ay
  • 对于其中一个和 r r r,如果 r ∈ A r\in A rA,则将其从 P P P中删去,否则输出 NO \text{NO} NO
  • 检查完所有的和之后, P P P集合就是答案

那么怎么求 a x + a y a_x+a_y ax+ay呢?用一个生成函数即可:

f ( x ) = ∑ i = 0 m b i x i f(x)=\sum_{i=0}^{m}b_i x^i f(x)=i=0mbixi

b i = 1 b_i=1 bi=1当且仅当 i ∈ A i\in A iA,或者 i = 0 i=0 i=0

f ( x ) f(x) f(x)与自己作卷积,那么 x k x^k xk的系数就是 ∑ i = 0 k b i b k − i \sum_{i=0}^{k}b_ib_{k-i} i=0kbibki,将其记为 g ( k ) g(k) g(k)

显然,这个 g ( k ) g(k) g(k)的意义就是选择 A ∪ { 0 } A\cup\{0\} A{0}中的两个元素(可以相同),有多少种不同的方案满足求和为 k k k

只考虑 k ≠ 0 k\ne 0 k=0的情况。分一个类:

  • 如果原本 k ∈ A k\in A kA:若 g ( k ) = 2 g(k)=2 g(k)=2,显然, 2 = b 0 b k + b k b 0 2=b_0b_k+b_kb_0 2=b0bk+bkb0——也就是说, k k k不能由两个 A A A中的元素相加而成——故 k ∈ P k\in P kP;若 g ( k ) ≠ 2 g(k)\ne 2 g(k)=2,则 k k k不属于 P P P
  • 如果 k k k不属于 A A A:若 g ( k ) > 0 g(k)>0 g(k)>0,显然,这意味着两个 A A A中的元素(可以重复)之和不属于 A A A。这意味着无解。反之,什么也不会发生。

卷积就很简单了,只需要做一次 FFT/NTT \text{FFT/NTT} FFT/NTT即可。反正我打的 NTT \text{NTT} NTT

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}
inline int qkpow(long long base,int q,int Mod){
	long long ans = 1; base %= Mod;
	for(; q; q>>=1,base=base*base%Mod)
		if(q&1) ans = ans*base%Mod;
	return ans;
}

const int MaxN = 4000005;

int omg[MaxN];
void NTT(int a[],int n,int Mod,int g,int opt){
	omg[0] = 1;
	for(int i=1; i<n; ++i)
		omg[i] = 1ll*omg[i-1]*g%Mod;
	int logN = 0;
	while((1<<logN) < n) ++ logN;
	for(int i=0; i<n; ++i){
		int t = 0;
		for(int j=0; (i>>j)!=0; ++j)
			if(i>>j&1) t |= 1<<(logN-j-1);
		if(t < i) swap(a[t],a[i]);
	}
	for(int len=2; len<=n; len<<=1)
		for(int *p=a; p!=a+n; p+=len)
			for(int i=0; i<(len>>1); ++i){
				int t = 1ll*omg[(n/len*i*opt+n)%n]*p[i+(len>>1)]%Mod;
				p[i+(len>>1)] = (p[i]-t+Mod)%Mod, p[i] = (p[i]+t)%Mod;
			}
	if(opt == -1){
		int inv = qkpow(n,Mod-2,Mod);
		for(int i=0; i<n; ++i)
			a[i] = 1ll*a[i]*inv%Mod;
	}
}

int n, m, f[MaxN], F[MaxN];
void input(){
	n = readint(), m = readint();
	for(int i=1; i<=n; ++i)
		f[readint()] = 1;
	f[0] = 1;
}

void solve(){
	int N = 1;
	while(N <= (m<<1)) N <<= 1;
	const int Mod = 998244353;
	const int g = qkpow(3,(Mod-1)/N,Mod);
	for(int i=0; i<=m; ++i) F[i] = f[i];
	NTT(f,N,Mod,g,1);
	for(int i=0; i<N; ++i)
		f[i] = 1ll*f[i]*f[i]%Mod;
	NTT(f,N,Mod,g,-1);
	bool ok = true;
	for(int i=1; i<=m; ++i)
		if(F[i] == 0 and f[i] >= 1)
			ok = false;
	if(not ok)
		puts("NO");
	else{
		puts("YES");
		int k = 0;
		for(int i=1; i<=m; ++i)
			if(F[i] == 1 and f[i] == 2)
				++ k;
		writeint(k), putchar('\n');
		for(int i=1; i<=m; ++i)
			if(F[i] == 1 and f[i] == 2)
				writeint(i), putchar(' ');
		putchar('\n');
	}
}

int main(){
	input(), solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值