vijos[P1054] luogu[P2662] 牛场围栏 (数论+最短路,(DP可水过))

3 篇文章 0 订阅
2 篇文章 0 订阅
描述
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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值