[CF577B]Modulo Sum

220 篇文章 2 订阅
30 篇文章 0 订阅

题目

传送门 to CF

思路

观察到 n n n 大而 m m m 小,肯定考虑设计只跟 m m m 有关的算法。

背包题,不难想到如下优化方式:确保每次更新都至少有一个新的数字能被凑出来。这样的话就只用更新 O ( m ) \mathcal O(m) O(m) 次,复杂度最高也就 O ( m 2 ) \mathcal O(m^2) O(m2) 了。

这是容易做到的。用 d i s ( x ) dis(x) dis(x) 表示,对于当前的数字种类 a a a ,在 x x x 处最多能递推多少次。如果用 S S S 表示已经能够凑出的字符,则

∀ i ∈ [ 0 , d i s ( x ) ) ,    x + i a ∉ S \forall i\in[0,dis(x)),\;x+ia\not\in S i[0,dis(x)),x+iaS

x + d i s ( x ) ⋅ a ∈ S x+dis(x)\cdot a\in S x+dis(x)aS

边界是 ∀ x ∈ S ,    d i s ( x ) = 0 \forall x\in S,\;dis(x)=0 xS,dis(x)=0 。当然你也可以是别的写法。理解到我的目标就行。

尽管 x + k a x+ka x+ka 在模 m m m 意义下是个环,但是仍然可以转移,因为其值实际上是意义唯一的。简单的实现方式是循环两遍。

那么,我们在背包转移时,对于一个 x ∈ S x\in S xS ,我们将其后方的 min ⁡ [ d i s ( x + a ) , c n t a ] \min[dis(x+a),cnt_a] min[dis(x+a),cnta] x + k a x+ka x+ka 格子都赋值为 t r u e \tt true true 即可。不难证明这是 O ( m 2 ) \mathcal O(m^2) O(m2) 的算法。并且可以推广到余数为任意值。

代码

在我的代码实现中,规定了 d i s ( 0 ) = 1 dis(0)=1 dis(0)=1 ,否则无法走到 n o w = 0 now=0 now=0 的情况。

写法因人而异。

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

const int MaxM = 1000;
int gcd[MaxM]; // gcd[x] = gcd(x,m)
bool dp[MaxM+5];
int cnt[MaxM], dis[MaxM];

int main(){
	int n = readint(), m = readint();
	for(int i=0; i<n; ++i)
		++ cnt[readint()%m];
	for(int i=1; i<m; ++i)
		if(m%i == 0) // 可能是 gcd
			for(int j=1; j<m/i; ++j)
				gcd[i*j] = i;
	dp[0] = true; // empty set
	bool ans = cnt[0];
	for(int a=1; a<m&&!ans; ++a){
		int d = gcd[a];
		for(int i=0; i<d; ++i)
		for(int j=2*m/d-1; ~j; --j){
			int now = (i+j*a)%m;
			dis[now] = dis[(now+a)%m]+1;
			if(dp[now]) dis[now] = !now;
		}
		for(int i=0; i<d; ++i)
		for(int j=0; j<m/d; ++j){
			int now = (i+j*a)%m;
			if(dp[now]){
				int t = dis[(now+a)%m];
				t = min(t,cnt[a]);
				for(; t; --t,++j){
					now = (now+a)%m;
					dp[now] = true;
					if(now == 0)
						ans = true;
				}
			}
		}
		// printf("dp:");
		// for(int i=0; i<m; ++i)
		// 	printf("%d",dp[i]);
		// putchar('\n');
	}
	puts(ans ? "YES" : "NO");
	return 0;
}

后记

对于特殊的情况,只求为 m m m 的倍数时, n > m n>m n>m前缀和 根据抽屉原理必然有重复。故直接输出 Y E S \tt YES YES 即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值