描述
John计划为他的牛场建一个围栏,以限制奶牛们的活动。他有N种可以建造围栏的木料,长度分别是l1,l2…lN,每种长度的木料无限。修建时,他将把所有选中的木料拼接在一起,因此围栏的长度就是他使用的木料长度之和。但是聪明的John很快发现很多长度都是不能由这些木料长度相加得到的,于是决定在必要的时候把这些木料砍掉一部分以后再使用。不过由于John比较节约,他给自己规定:任何一根木料最多只能削短M米。当然,每根木料削去的木料长度不需要都一样。不过由于测量工具太原始,John只能准确的削去整数米的木料,因此,如果他有两种长度分别是7和11的木料,每根最多只能砍掉1米,那么实际上就有4种可以使用的木料长度,分别是6, 7, 10, 11。
Clevow是John的牛场中的最聪明的奶牛,John请她来设计围栏。Clevow不愿意自己和同伴在游戏时受到围栏的限制,于是想刁难一下John,希望John的木料无论经过怎样的加工,长度之和都不可能得到她设计的围栏总长度。
不过Clevow知道,如果围栏的长度太小,John很快就能发现它是不能修建好的。因此她希望得到你的帮助,找出无法修建的最大围栏长度。
输入格式
输入的第一行包含两个整数N, M (1<N<100, 0<=M<3000),分别表示木料的种类和每根木料削去的最大值。以下各行每行一个整数li(1<li<3000),表示第i根木料的原始长度。
输出格式
输出仅一行,包含一个整数,表示不能修建的最大围栏长度。如果任何长度的围栏都可以修建或者这个最大值不存在,输出-1。
样例1
样例输入1[复制]
2 1
7
11
样例输出1[复制]
15
限制
各个测试点1秒
来源
WinterCamp 2002
DP水过(luogu上DP可水过, vijos上会WA)
可行性dp,f[j]表示组成j长度是否可行,只要任意j-任意一根可生成的木棒长度可行就可以了,而目标是找到第一根不可行的,输出即可,注意预处理的时候判爆负数 ————luogu题解
代码:
#include <cstdio>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int n, m, vis[1200000], f[2211111];
int c[9211111];
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; j++) {
if(!vis[x - j]) {
vis[x - j] = 1;
c[++c[0]] = x - j;
f[x - j] = 1;
if(x - j == 1) {
cout<< - 1 <<endl;
return 0;
}
}
}
}
for(int i = 1; i <= c[0]; i++) {
int now = c[i];
for(int j = 0; j <= 100010; j++) {
if(f[j - c[i]]) f[j] = 1;
}
}
for(int j = 100010; j >= 0; j--) {
if(!f[j]) {
cout<<j<<endl;
return 0;
}
}
}
正解最短路
- 首先把一开始能弄出的木棍长度处理出来
- 找一个最小的, 设为min_length。 若最小的为1,则输出-1;
- 然后抽象的把0 - min_length - 1 的每一个数看成一个点,某些长度%min_length后,一定在这之间。
- 可以发现,如果我们能够组成某个长度x, 且 y = x % min_length; 又设X = y + i * min_length,则我们一定能够组成所有的X 且X满足下列关系(X%min_length == y);
- 如果不能组成呢?那么我们一定不能组成任意一个X使得(X%min_length == y); 也就是说,不能组成的最大长度是无限大,此时无解,输出-1。
- 如果能组成,我们只保存一个最小的x(x一定大于min_length),那么x - min_length 一定不能被组成。
- 如果余数为 0 - min_length - 1的长度 都能组成,则找一个最大的x ,则x - min_length 为最大的不能组成的长度。
- 所以,抽象出min_length - 1 个点,用dis[i] 表示余数为i的最小长度x。根据一开始的木棍长度,首先得到dis[i]的初始值,然后边走边建边。跑spfa即可
- 这个建边过程是边跑spfa边建边,且不用存每个点可连向那些点,因为对于可以组成的i,其他的每个点都可以与i相连,即这边是它们共有的。
- 为什么要边跑spfa边建边呢?因为对当前i,有一个j,(dis[i]+dis[j])%min_length == X 且dis[X]并未被更新,为初始极大INF,则建的边表中肯定不含这个点! 这时候把这个点加入表中。
代码:
#include <cstdio>
#include <iostream>
#include <string>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define debug cout<<"Orzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"<<endl;
const int INF = 2047483647;
int dis[3426];
int c[422567], vis[5122567];
int n,m;
queue <int> q;
vector <int> v;
int min_length = INF;
int max_length = -5;
int iv[3456];
int spfa() {
for(int i = 0; i <= min_length; i++) {
dis[i] = INF;
}
for(int i = 1; i <= c[0]; i++) {
// cout<<now<<' '<<i<<' '<<c[i]<<' '<<endl;
int now = c[i] % min_length;
if(dis[now] == INF || dis[now] > c[i]) {
dis[now] = c[i];
q.push(now);
}
}
// debug;
while(!q.empty()) {
int now = q.front(); q.pop();
vis[now] = 0;
if(!iv[now]) {
v.push_back(now);
iv[now] = 1;
}
for(int i = 0; i < v.size(); i++) {
int nn = (dis[v[i]] + dis[now]) % min_length;
if(dis[nn] > dis[now] + dis[v[i]]) {
dis[nn] = dis[now] + dis[v[i]];
// if(nn_mod == 3) {
// cout<<dis[nn_mod] <<' '<<"dis["<<now_mod<<"] = "<<dis[now_mod] << ' '<<nn<<endl;
// }
if(!vis[nn]) {
vis[nn] = 1;
q.push(nn);
}
}
}
}
for(int i = 0; i < min_length; i++) {
// cout<<i<<' '<<dis[i]<<' '<<min_length<<' '<<endl;
if(dis[i] < INF) {
max_length = max(max_length, dis[i]);
} else return -1;
}
return max_length - min_length;
}
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; j++)
if(!vis[x - j]) {
vis[x - j] = 1;
c[++c[0]] = x - j;
min_length = min(min_length , c[c[0]]);
if(x - j == 1) {
cout<< - 1 <<endl;
return 0;
}
}
}
cout<<spfa()<<endl;
// cout<<spfa();
return 0;
}