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

同余最短路经典套路

给定 n n n个数,每个数都可以选多次(也可以不选),将所有选中的数相加。问最终能够组成多少种不同的数或者不能组成的最大数或者。。。
形如 a x + b y + c z + . . = k ax+by+cz+..=k ax+by+cz+..=k,其中 a , b , c a,b,c a,b,c表示对应的数的选中次数。
从最简单的 a x + b y = k ax+by=k ax+by=k入手分析:
b y = k − a x by=k-ax by=kax b y % x ∈ [ 0 , x − 1 ] by\%x \in{[0,x-1]} by%x[0,x1] i ⇒ ( i + y ) % x , c o s t = y , i ∈ [ 0 , x − 1 ] i\Rightarrow(i+y)\%x,cost=y,i\in{[0,x-1]} i(i+y)%x,cost=y,i[0,x1]
也就是说由 b y by by组成的值对 x x x取余,可以将这些值根据余数分为 x x x组,也就是同余类。并且从余数 i i i到余数 ( i + y ) % x (i+y)\%x (i+y)%x的代价是 y y y
建图,边的信息为 ( i , ( i + y ) % x , y ) , i ∈ [ 0 , x − 1 ] (i, (i+y)\%x, y), i\in{[0,x-1]} (i,(i+y)%x,y),i[0,x1],表示从点 i i i到点 ( i + y ) % x (i+y)\%x (i+y)%x的代价是 y y y(当然也存在 b y % x ≠ i by\%x\ne i by%x=i这种情况)。
d i s [ i ] dis[i] dis[i]表示满足 b y % x = i by\%x=i by%x=i的最小的 b y by by,也就是最少要选取 b b b y y y,才能使 b y % x = i , b ≥ 0 by\%x=i,b≥0 by%x=i,b0,也就是每个同余类中的最小值。
d i s [ ] dis[] dis[]就相当于在图中求最短路(我习惯用spfa),且 d i s [ 0 ] = 0 dis[0]=0 dis[0]=0(因为 0 ∗ y % x = 0 0*y\%x=0 0y%x=0
求完 d i s [ i ] dis[i] dis[i]之后便得到了每个同余类中的最小值 p i p_i pi,很明显 p i + x p_i+x pi+x p i p_i pi属于同一个同余类中,并且 p i + t x ≠ p j + t x , 0 ≤ i , j ≤ x − 1 , i ≠ j p_i+tx\ne p_j+tx,0≤i,j≤x-1,i\ne j pi+tx=pj+tx,0i,jx1,i=j,这样就得得到了 a x + b y = k ax+by=k ax+by=k组成的所有的不同的 k k k值。
对于 a x + b y + c z = k ax+by+cz=k ax+by+cz=k这种式子,按照同样的思路建图,边的信息为:
( i , ( i + y ) % x , y ) (i, (i+y)\%x, y) (i,(i+y)%x,y) ( i , ( i + z ) % x , z ) (i, (i+z)\%x, z) (i,(i+z)%x,z)
对应的 d i s [ i ] dis[i] dis[i]表示满足 b y + c z % x = i by+cz\%x=i by+cz%x=i的最小的 b y + c z by+cz by+cz
为了使图中的节点数最少,取这 n n n个数中的最小值作为 x x x,那么图中的边数为 x ( n − 1 ) x(n-1) x(n1)

P3403

传送门

  • 题意:
    相当于求 a x + b y + c z + 1 ax+by+cz+1 ax+by+cz+1能够组成多少个小于 h h h的不同的数。
  • 思路:
    因为 b y + c z + 1 by+cz+1 by+cz+1 b = 0 , c = 0 b=0,c=0 b=0,c=0时得到 1 1 1,所以根据 d i s [ i ] dis[i] dis[i]的定义有 d i s [ 1 % x ] = 1 dis[1\%x]=1 dis[1%x]=1
    求出 d i s [ ] dis[] dis[]后,可知 a n s = ∑ i = 0 x − 1 ( h − d i s [ i ] x + 1 ) \displaystyle ans=\sum_{i=0}^{x-1} (\frac{h-dis[i]}{x}+1) ans=i=0x1(xhdis[i]+1) ( h > = d i s [ i ] ) (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

传送门

  • 题目:
    ∑ a i x i \sum{a_ix_i} aixi的值有多少个在 [ l , r ] [l,r] [l,r]内, x i x_i xi已知。
  • 思路:
    先求出 ∑ a i x i \sum{a_ix_i} aixi有多少种小于等于h的值,记为 c n t [ h ] cnt[h] cnt[h](这就是P3403要解决的问题)。
    a n s = c n t [ r ] − c n t [ l − 1 ] ans=cnt[r]-cnt[l-1] ans=cnt[r]cnt[l1]
  • 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

传送门

  • 题目:
    题目转化为求 ∑ a i x i \sum{a_ix_i} aixi不能组成的最大的数(根据题意可知指的是正数), x i x_i xi已知,不存在或最大值不能确定输出 − 1 -1 1
  • 思路:
    • 如果存在 x i = 1 x_i=1 xi=1,那么可以得到任何正数,输出 − 1 -1 1
    • 如果对于 x x x的一个同余类 i i i,得不到相应的 d i s [ i ] dis[i] dis[i],说明对于满足 w % x = i w\%x=i w%x=i w w w,都不能由这些数得到,且 w w w可以无限大,无法确定最大值,输出 − 1 -1 1
    • d i s [ i ] dis[i] dis[i]是同余类 i i i里面的最小值,那么在同余类 i i i中,属于这个同余类但不可得到的最大数为 d i s [ i ] − x dis[i]-x dis[i]x,那么 a n s = m a x { d i s [ i ] − x } , i ∈ [ 0 , x − 1 ] ans=max\{dis[i]-x\},i\in{[0,x-1]} ans=max{dis[i]x},i[0,x1]
  • 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

传送门

  • 题目:
    求满足 a x + b y + c z = k ax+by+cz=k ax+by+cz=k的一组解, a , b , c a,b,c a,b,c已知。
  • 思路:
    裸的同余最短路题,在求最短路的时候记录下 b b b值在最短路的路径中出现的次数。
    当然本地也可以枚举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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值