vijos[P1054] luogu[P2662] 牛場圍欄 (數論+最短路,(DP可水過))

描述
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章