【同餘最短路】P3403+P2371+P2662+牛客4853D

同餘最短路經典套路

給定nn個數,每個數都可以選多次(也可以不選),將所有選中的數相加。問最終能夠組成多少種不同的數或者不能組成的最大數或者。。。
形如ax+by+cz+..=kax+by+cz+..=k,其中a,b,ca,b,c表示對應的數的選中次數。
從最簡單的ax+by=kax+by=k入手分析:
by=kaxby=k-ax by%x[0,x1]by\%x \in{[0,x-1]} i(i+y)%x,cost=y,i[0,x1]i\Rightarrow(i+y)\%x,cost=y,i\in{[0,x-1]}
也就是說由byby組成的值對xx取餘,可以將這些值根據餘數分爲xx組,也就是同餘類。並且從餘數ii到餘數(i+y)%x(i+y)\%x的代價是yy
建圖,邊的信息爲(i,(i+y)%x,y),i[0,x1](i, (i+y)\%x, y), i\in{[0,x-1]},表示從點ii到點(i+y)%x(i+y)\%x的代價是yy(當然也存在 by%xiby\%x\ne i這種情況)。
dis[i]dis[i]表示滿足by%x=iby\%x=i的最小的byby,也就是最少要選取bbyy,才能使by%x=i,b0by\%x=i,b≥0,也就是每個同餘類中的最小值。
dis[]dis[]就相當於在圖中求最短路(我習慣用spfa),且dis[0]=0dis[0]=0(因爲0y%x=00*y\%x=0
求完dis[i]dis[i]之後便得到了每個同餘類中的最小值pip_i,很明顯pi+xp_i+xpip_i屬於同一個同餘類中,並且pi+txpj+tx,0i,jx1,ijp_i+tx\ne p_j+tx,0≤i,j≤x-1,i\ne j,這樣就得得到了ax+by=kax+by=k組成的所有的不同的kk值。
對於ax+by+cz=kax+by+cz=k這種式子,按照同樣的思路建圖,邊的信息爲:
(i,(i+y)%x,y)(i, (i+y)\%x, y)(i,(i+z)%x,z)(i, (i+z)\%x, z)
對應的dis[i]dis[i]表示滿足by+cz%x=iby+cz\%x=i的最小的by+czby+cz
爲了使圖中的節點數最少,取這nn個數中的最小值作爲xx,那麼圖中的邊數爲x(n1)x(n-1)

P3403

傳送門

  • 題意:
    相當於求ax+by+cz+1ax+by+cz+1能夠組成多少個小於hh的不同的數。
  • 思路:
    因爲by+cz+1by+cz+1b=0,c=0b=0,c=0時得到11,所以根據dis[i]dis[i]的定義有dis[1%x]=1dis[1\%x]=1
    求出dis[]dis[]後,可知ans=i=0x1(hdis[i]x+1)\displaystyle ans=\sum_{i=0}^{x-1} (\frac{h-dis[i]}{x}+1)(h>=dis[i])(h>=dis[i])
  • ac代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1e5+10;
struct node{
    int nxt, val;
};
vector<node> edge[maxn];
queue<int> q;
int x, y, z;
ll h;
ll dis[maxn];
bool inq[maxn];
void spfa()
{
    for(int i = 0; i <= x; i++) dis[i] = LLONG_MAX, inq[i] = false;
    dis[1%x] = 1;
    q.push(1%x); inq[1%x] = true;
    while(!q.empty())
    {
        int now = q.front(); q.pop(); inq[now] = false;
        for(auto e: edge[now])
        {
            int nxt = e.nxt, val = e.val;
            if(dis[now]+val<dis[nxt])
            {
                dis[nxt] = dis[now]+val;
                if(!inq[nxt]) q.push(nxt), inq[nxt] = true;
            }
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%lld %d %d %d", &h, &x, &y, &z);
    int p = min(x, min(y, z));
    if(y==p) swap(x, y);
    else if(z==p) swap(x, z);
    for(int i = 0; i < x; i++)
    {
        edge[i].push_back({(i+y)%x, y});
        edge[i].push_back({(i+z)%x, z});
    }
    spfa();
    ll ans = 0;
    for(int i = 0; i < x; i++) if(h>=dis[i]) ans += (h-dis[i])/x+1;
    printf("%lld\n", ans);
    return 0;
}

P2371

傳送門

  • 題目:
    aixi\sum{a_ix_i}的值有多少個在[l,r][l,r]內,xix_i已知。
  • 思路:
    先求出aixi\sum{a_ix_i}有多少種小於等於h的值,記爲cnt[h]cnt[h](這就是P3403要解決的問題)。
    ans=cnt[r]cnt[l1]ans=cnt[r]-cnt[l-1]
  • ac代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 5e5+10;
struct node{
    int nxt, val;
};
vector<node> edge[maxn];
queue<int> q;
ll dis[maxn];
int a[20];
bool inq[maxn];
ll n, l, r;
void spfa()
{
    for(int i = 0; i <= a[1]; i++) dis[i] = LLONG_MAX;
    dis[0] = 0; q.push(0); inq[0] = true;
    while(!q.empty())
    {
        int now = q.front(); q.pop(); inq[now] = false;
        for(auto e: edge[now])
        {
            int nxt = e.nxt, val = e.val;
            if(dis[now]+val<dis[nxt])
            {
                dis[nxt] = dis[now]+val;
                if(!inq[nxt]) q.push(nxt), inq[nxt] = true;
            }
        }
    }
}
ll cnt(ll h)
{
    ll ans = 0;
    for(int i = 0; i < a[1]; i++)
        if(h>=dis[i]) ans += (h-dis[i])/a[1]+1;
    return ans;
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%lld %lld %lld", &n, &l, &r);
    int mi = INT_MAX;
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]), mi = min(mi, a[i]);
    for(int i = 1; i <= n; i++) if(a[i]==mi) {swap(a[i], a[1]); break; }
    for(int i = 0; i < a[1]; i++)
        for(int j = 2; j <= n; j++)
            edge[i].push_back({(i+a[j])%a[1], a[j]});
    spfa();
    printf("%lld\n", cnt(r)-cnt(l-1));
    return 0;
}

P2662

傳送門

  • 題目:
    題目轉化爲求aixi\sum{a_ix_i}不能組成的最大的數(根據題意可知指的是正數),xix_i已知,不存在或最大值不能確定輸出1-1
  • 思路:
    • 如果存在xi=1x_i=1,那麼可以得到任何正數,輸出1-1
    • 如果對於xx的一個同餘類ii,得不到相應的dis[i]dis[i],說明對於滿足w%x=iw\%x=iww,都不能由這些數得到,且ww可以無限大,無法確定最大值,輸出1-1
    • dis[i]dis[i]是同餘類ii裏面的最小值,那麼在同餘類ii中,屬於這個同餘類但不可得到的最大數爲dis[i]xdis[i]-x,那麼ans=max{dis[i]x},i[0,x1]ans=max\{dis[i]-x\},i\in{[0,x-1]}
  • ac代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e3+10;
struct node{
    int nxt, val;
};
vector<node> edge[maxn];
queue<int> q;
int n, m, x;
int a[110];
ll dis[maxn];
bool inq[maxn];
void spfa()
{
    for(int i = 0; i < x; i++) dis[i] = LLONG_MAX, inq[i] = false;
    dis[0] = 0; inq[0] = true;
    q.push(0);
    while(!q.empty())
    {
        int now = q.front(); q.pop(); inq[now] = false;
        for(auto e: edge[now])
        {
            int nxt = e.nxt, val = e.val;
            if(dis[now]+val<dis[nxt])
            {
                dis[nxt] = dis[now]+val;
                if(!inq[nxt]) q.push(nxt), inq[nxt] = true;
            }
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    sort(a+1, a+1+n);
    if(m>=a[1]-1) {printf("-1\n"); return 0;}//任何長度都可以達到
    x = a[1]-m;
    for(int i = 1; i <= n; i++)
        for(int j = max(a[i-1]+1, a[i]-m); j <= a[i]; j++)//不統計重複的長度
            for(int k = 0; k < x; k++)
                edge[k].push_back({(k+j)%x, j});
    spfa();
    ll ans = 0;
    for(int i = 0; i < x; i++)
    {
        if(dis[i] == LLONG_MAX) {printf("-1\n"); return 0;}//最大值不存在(可以無窮大)
        else ans = max(ans, dis[i]-x);//該同餘類不能構成的最大的數
    }
    printf("%lld\n", ans);
    return 0;
}

牛客練習賽60:D

傳送門

  • 題目:
    求滿足ax+by+cz=kax+by+cz=k的一組解,a,b,ca,b,c已知。
  • 思路:
    裸的同餘最短路題,在求最短路的時候記錄下bb值在最短路的路徑中出現的次數。
    當然本地也可以枚舉z的值,用擴展歐幾里得算法做,然後就是上模板了。
  • ac代碼:
    • 同餘最短路做法(正解):
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1e5+10;
int a, b, c;
ll k;
struct node{
    int nxt, val;
};
vector<node> edge[maxn];
queue<int> q;
ll dis[maxn], cntb[maxn];
bool inq[maxn];
void spfa()
{
    for(int i = 0; i < a; i++) dis[i] = LLONG_MAX, cntb[i] = 0;
    dis[0] = 0;
    q.push(0);
    while(!q.empty())
    {
        int now = q.front(); q.pop(); inq[now] = false;
        for(auto e: edge[now])
        {
            int nxt = e.nxt, val = e.val;
            if(dis[now]+val<dis[nxt])
            {
                dis[nxt] = dis[now]+val;
                cntb[nxt] = cntb[now];
                if(val==b) cntb[nxt]++;
                if(!inq[nxt]) inq[nxt] = true, q.push(nxt);
            }
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%d %d %d %lld", &a, &b, &c, &k);
    for(int i = 0; i < a; i++)
    {
        edge[i].push_back({(i+b)%a, b});
        edge[i].push_back({(i+c)%a, c});
    }
    spfa();
    for(int i = 0; i < a; i++)
    {
        if(k>=dis[i] && (k-dis[i])%a==0)
        {
            printf("%lld %lld %lld\n", (k-dis[i])/a,  cntb[i], (dis[i]-cntb[i]*b)/c);
            break;
        }
    }
    return 0;
}
  • 擴展歐幾里得算法:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1e3+10;
ll a, b, c, k;
ll exgcd(ll a, ll b, ll &x, ll &y)//ax+by,返回gcd(a, b)
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return a;//gcd(a,b)
    }
    ll r = exgcd(b, a%b, x, y);//x1=y2 ,  y1=x2-a/b*y2
    ll t = x;
    x = y;
    y = t - a/b*y;
    return r;
}
bool linear_equation(ll a, ll b, ll c, ll &x, ll &y)//ax+by=c
{
    ll com = exgcd(a, b, x, y);
    if(c%com) return false;
    ll k = c/com;
    x *= k; y *= k;//一組解
    ll B = abs(b/com);
    x = ((x%B)+B)%B; //最小正整數解x
    y = (c-a*x)/b;
    if(x>=0 && y>=0) return true;
    else return false;
}
int main()
{
    scanf("%lld %lld %lld %lld", &a, &b, &c, &k);
    ll x, y;
    for(ll z = 0; z <= 100000; z++)
    {
        if(linear_equation(a, b, k-z*c, x, y))
        {
            printf("%lld %lld %lld\n", x, y, z);
            break;
        }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章