同余最短路经典套路
给定
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=k−ax
b
y
%
x
∈
[
0
,
x
−
1
]
by\%x \in{[0,x-1]}
by%x∈[0,x−1]
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,x−1]
也就是说由
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,x−1],表示从点
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,b≥0,也就是每个同余类中的最小值。
求
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
0∗y%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,0≤i,j≤x−1,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(n−1)。
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=0∑x−1(xh−dis[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[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
- 题目:
题目转化为求 ∑ 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,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
- 题目:
求满足 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;
}