(upd.2019.7.23 添加了关于gcd==1时有解的证明)
今天学了不少新东西,闲着无聊都发了两发博客了,明明这只是XXY的基础题
题意
用一堆整数S相加组成新数,每个可以用任意次数,求最大的不能组成的数。
思路
-
首先判掉两种情况:1可以组成任何数,2不能组成的数无限大。
-
第一种情况就是在原始的整数集合S中有1。第二种情况是S中所有数的GCD大于1,所以所有不能被GCD整除的数都无法组成
-
然后终于到了求最大无法构成的数了。首先我们假设S中所有数存在l[]数组中,minl是l[]里的随便一个数。所以所有可以组成的数都可以%minl变成0~minl-1的一个数。然而这有什么用呢?我们可以记录一个dis[]数组,用来存能被组成%minl值为i的数中最小的那个,因为只要最小的那个能被组成了,在这个最小数基础上加上minl,就可以保证与他同于的比他大的数全都可以被组成。所以所有dis[]中存的最小值减去minl就是不能取到的最大值,把所有的取min就是最终答案。
-
那么如何求最小值呢?可以联想到最短路算法
(根本联想不到,我第一次看到题解里的SPFA是懵逼的)。具体实现挺简单的,就像普通可行性DP一样转移就好了
upd.
条件:令 S = { x i ∣ 1 ≤ i ≤ n } S=\{x_i|1 \le i \le n\} S={xi∣1≤i≤n}表示初始数集,该集合满足 1 ∈ ̸ S 1 \in\not S 1∉S且 g c d ( x 1 , ⋯   , x n ) = 1 gcd(x_1, \cdots,x_n) = 1 gcd(x1,⋯,xn)=1。
证明有解,分为以下两部分:
- 一定存在一个数无法构造出来
- 不能构造出来的数有上界
首先第1点,根据条件,1是无法构造出来的,所以必然有解。
第2点,根据上面说的算法,本题有解可以转化为存在一个
x
k
∈
S
x_k \in S
xk∈S,
使得
a
1
x
1
+
a
2
x
2
+
⋯
+
a
n
x
n
≡
1
(
m
o
d
  
x
k
)
,
a
i
∈
N
a_1x_1+a_2x_2+\dots+a_nx_n \equiv 1(mod \; x_k),a_i \in N
a1x1+a2x2+⋯+anxn≡1(modxk),ai∈N,既然可以做出1,那么对于每一个同余类只要在等式两边都乘上一个数就好了。
然后来解决转化之后的问题。根据裴蜀定理在多个数下的推论,
可以知道
a
1
x
1
+
a
2
x
2
+
⋯
+
a
n
x
n
=
1
a_1x_1+a_2x_2+\dots+a_nx_n=1
a1x1+a2x2+⋯+anxn=1有整数解。然后因为对
x
k
x_k
xk取模,所以
a
k
∈
Z
a_k\in Z
ak∈Z,
令
g
c
d
(
x
i
∣
i
=
̸
k
)
=
g
gcd(x_i|i=\not k)=g
gcd(xi∣i≠k)=g,那么一定存在无数组
{
b
i
}
\{b_i\}
{bi}使
∑
i
=
̸
k
b
i
x
i
=
g
⋅
x
k
\sum_{i=\not k}b_ix_i=g \cdot x_k
∑i≠kbixi=g⋅xk,那么
a
k
a_k
ak减去
g
g
g,在其他
x
i
x_i
xi的系数
a
i
a_i
ai上分别加上
b
i
b_i
bi,最后
a
k
x
k
a_kx_k
akxk的项可以到无限小,那么就可以使得其他项的系数
a
i
∈
N
(
i
=
̸
k
)
a_i\in N(i=\not k)
ai∈N(i≠k)。
(最后一步还是不严谨呢。。。要是有大佬会证请务必告诉我一声)
上代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 3000 + 10, inf = 1e9 + 7;
int n, m, g, minl, sell;
bool vis[N];
int dis[N];
queue<int> que;
bool inq[N];
void Spfa()
{
memset(dis, 0x3f, sizeof(dis));
que.push(0); dis[0] = 0; inq[0] = 1;
while (!que.empty()){
int u = que.front(); que.pop(); inq[u] = 0;
for (int i = 1; i < N; ++ i)
if (vis[i]){
int v = (u + i) % sell;
if (dis[v] > dis[u] + i){
dis[v] = dis[u] + i;
if (!inq[v]){
inq[v] = 1;
que.push(v);
}
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++ i){
int x;
scanf("%d", &x);
for (int j = 0; j <= m && x - j > 0; ++ j)
vis[x - j] = 1;
}
g = 0;
for (int i = 1; i < N; ++ i)
if (vis[i])
g = __gcd(g, i);
if (vis[1] || g > 1){cout << -1 << endl; return 0;}
for (int i = 1; i < N; ++ i){
if (!vis[i]) continue;
if (!minl) minl = i;
else if (minl) {sell = i; break;}
}
Spfa();
minl = 0;
for (int i = 0; i < sell; ++ i)
minl = max(minl, dis[i] - sell);
cout << minl << endl;
return 0;
}