同餘+最短路 做完全揹包

POI X Sums

 

 

一個集合 SSS 內有 nnn 個正整數 {a1,a2,…,an}\{a_1, a_2, \ldots, a_n\}{a​1​​,a​2​​,…,a​n​​}S′S'S​′​​ 表示由集合 SSS 中的數相加所得的集合,每個數都可以用無數次。

qqq 次詢問,詢問一個 xxx 是否在 S′S'S​′​​ 內。存在輸出 y 否則輸出 n

n≤5000,q,ai,≤50000,x≤1017n \leq 5000, q, a_i, \leq 50000, x \leq 10^{17}n≤5000,q,a​i​​,≤50000,x≤10​17​​.

輸入 nnn ,然後輸入 nnn 個數,再輸入 qqq,然後qqqxxx

 

題意可以轉化爲:給定a數組,問式子a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k是否有正整數解。

其實看下來,這就是一個容量爲k的完全揹包,判斷是否能恰好裝滿。

叉姐的解釋:

 

 

SSS 中找一個數,例如 a1a_1a​1​​. 一個關鍵性質是:如果 x∈S′x \in S'x∈S​′​​,那麼x+a1∈S′x + a_1 \in S'x+a​1​​∈S​′​​.

那麼設 d[r]d[r]d[r] 表示所有模 a1a_1a​1​​rrr 的數中,最小的 ∈S′\in S'∈S​′​​ 的數。根據性質,d[r],d[r]+a1,d[r]+2a1,…∈S′d[r], d[r] + a_1, d[r] + 2a_1, \ldots \in S'd[r],d[r]+a​1​​,d[r]+2a​1​​,…∈S​′​​. 同時因爲 d[r]d[r]d[r] 是最小的,所以同餘類中 <d[r]< d[r]<d[r] 的數都 ∉S′\notin S'∉S​′​​.

具體求 d[∗]d[*]d[∗] 的過程是最短路。首先,d[0]=0d[0] = 0d[0]=0。其次,可以用d[x]+aid[x] + a_id[x]+a​i​​ 去更新 d[(x+ai) mod a1]d[(x + a_i)\ \mathrm{mod}\ a_1]d[(x+a​i​​) mod a​1​​].
這裏如果用 Dijkstra 的話,複雜度是 O(na1loga1)O(na_1 \log{a_1}). 從這個角度上講,a1a_1​​ 應該取 SS 中最小的。

關鍵的部分就是求d[]數組的最短路,根據定義有d[ (r+a[i])%mod ]=min(d[ (r+a[i])%mod], d[r]+a[i] );

 

另一種用圖論的最短路解釋爲:

設a1爲最小數字,若方程a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k存在非負整數解,那麼k+a1也必然有解。
建立a1個點的圖,點編號爲0到a1-1,i向(i+aj)%a1連邊,邊權爲aj,求0到所有點的最短路,用dis數組存,如果dis[k%a1]<=k,那麼k有解。

 

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <vector>
#define P pair<int,int>

using namespace std;

const int ma=50010,INF=0x3f3f3f3f;
int n,m,a[ma],f[ma];

void solve()
{
    sort(a,a+n);
    m=a[0];
    for(int i=1; i<m; ++i)
        f[i]=INF;

    priority_queue<P,vector<P>,greater<P> > q;
    q.push(P(0,0));
    P t;
    int x;
    while(!q.empty())
    {
        t=q.top();
        q.pop();
        if(f[t.second]<t.first) continue;
        x=t.second;
        for(int i=1; i<n; ++i)
            if(f[x]+a[i]<f[(x+a[i])%m])
            {
                f[(x+a[i])%m]=f[x]+a[i];
                q.push(P(f[(x+a[i])%m],(x+a[i])%m));
            }
    }
}

bool judge(int x)
{
    return x>=f[x%m];
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);

        solve();

        int q,x;
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d",&x);
            if(judge(x)) puts("TAK");
            else puts("NIE");
        }
    }
    return 0;
}

 

再看一題類似的,但是結果是求a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k沒有正整數解的最大K。

 

 

牛場圍欄

 

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)數組a存在值爲1的情況

2)某個點的最小路徑不存在,如果f[x]不存在,那麼f[x]+k*a[1]也不存在,可以到無限大,也就是沒有確定的解

其他的情況都是存在的。

 

/*
真的是老淚縱橫,這題用多組輸入,結果有1 的情況,輸出,完了我用continue,結果一直WA在第十個樣例,一改回單租輸入就AC了,我的內心是絕望的。
https://vijos.org/p/1054
*/

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#define P pair<int,int>

using namespace std;

const int ma=3010,INF=0x3f3f3f3f;
int l[ma],f[ma],cnt;
bool vis[ma];

void solve()
{
    int m=l[1];
    memset(f,INF,sizeof(f));
    f[0]=0;
    priority_queue<P,vector<P>,greater<P> > q;
    q.push(P(0,0));
    int x;
    P t;
    while(!q.empty())
    {
        t=q.top();
        q.pop();
        x=t.second;
        if(f[x]<t.second) continue;
        for(int i=1; i<=cnt; ++i)
        {
            if(f[x]+l[i]<f[(x+l[i])%m])
            {
                f[(x+l[i])%m]=f[x]+l[i];
                q.push(P(f[(x+l[i])%m],(x+l[i])%m));
            }
        }
    }

    for(int i=0; i<m; ++i)
        if(f[i]==INF)
        {
            puts("-1");
            return;
        }

    int ans=0;
    for(int i=0; i<m; ++i)
        ans=max(ans,f[i]-m);
    printf("%d\n",ans);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(vis,false,sizeof(vis));
    int x;
    for(int i=0; i<n; ++i)
    {
        scanf("%d",&x);
        for(int j=(x-m>0?x-m:1); j<=x; ++j)
            vis[j]=true;
    }
    if(vis[1])
    {
        puts("-1");
        return 0;
    }

    cnt=0;
    for(int i=1; i<=3000; ++i)
        if(vis[i]) l[++cnt]=i;

    solve();

    return 0;
}

 

還有一個類似的題

 

 

2118: 墨墨的等式

Description

墨墨突然對等式很感興趣,他正在研究a1x1+a2y2+…+anxn=B存在非負整數解的條件,他要求你編寫一個程序,給定N、{an}、以及B的取值範圍,求出有多少B可以使等式存在非負整數解。

Input

輸入的第一行包含3個正整數,分別表示N、BMin、BMax分別表示數列的長度、B的下界、B的上界。輸入的第二行包含N個整數,即數列{an}的值。

Output

輸出一個整數,表示有多少b可以使等式存在非負整數解。

 

 

這個要求給出的[ L,R ]區間能夠使a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k有非負整數解的k的個數。

計算個數的過程如下:

 

LL cal(LL x)
{
    LL ans=0;
    for(int i=0;i<m;++i)
    {
        if(x>=f[i])
        ans+=1ll*(x-f[i])/m+1;
    }
    return ans;
}


然後用cal(R)-cal(L-1)就是結果。代碼如下:

 

 

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#define LL long long
#define P pair<LL,int>

using namespace std;

const int ma=5e5+10;
const LL INF=0x3f3f3f3f3f3f3f3f;
int l[ma],n,m;
LL beg,en,f[ma];

LL cal(LL x)
{
    LL ans=0;
    for(int i=0;i<m;++i)
    {
        if(x>=f[i])
        ans+=1ll*(x-f[i])/m+1;
    }
    return ans;
}

void solve()
{
    m=l[1];
    int k=1;
    while(!m)
        m=l[++k];

    for(int i=1;i<m;++i)
        f[i]=INF;

    f[0]=0;
    priority_queue<P,vector<P>,greater<P> > q;
    q.push(P(0,0));
    int x;
    P t;
    while(!q.empty())
    {
        t=q.top();
        q.pop();
        x=t.second;
        if(f[x]<t.second) continue;
        for(int i=1; i<=n; ++i)
        {
            if(f[x]+l[i]<f[(x+l[i])%m])
            {
                f[(x+l[i])%m]=f[x]+l[i];
                q.push(P(f[(x+l[i])%m],(x+l[i])%m));
            }
        }
    }
}

int main()
{
    scanf("%d%lld%lld",&n,&beg,&en);
    for(int i=1; i<=n; ++i)
        scanf("%d",&l[i]);

    sort(l+1,l+n+1);
    if(!l[n])
    {
        printf("0\n");
        return 0;
    }

    solve();

    printf("%lld\n",cal(en)-cal(beg-1));

    return 0;
}

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章