同餘最短路經典套路
給定個數,每個數都可以選多次(也可以不選),將所有選中的數相加。問最終能夠組成多少種不同的數或者不能組成的最大數或者。。。
形如,其中表示對應的數的選中次數。
從最簡單的入手分析:
也就是說由組成的值對取餘,可以將這些值根據餘數分爲組,也就是同餘類。並且從餘數到餘數的代價是。
建圖,邊的信息爲,表示從點到點的代價是(當然也存在 這種情況)。
表示滿足的最小的,也就是最少要選取次,才能使,也就是每個同餘類中的最小值。
求就相當於在圖中求最短路(我習慣用spfa),且(因爲)
求完之後便得到了每個同餘類中的最小值,很明顯和屬於同一個同餘類中,並且,這樣就得得到了組成的所有的不同的值。
對於這種式子,按照同樣的思路建圖,邊的信息爲:
對應的表示滿足的最小的。
爲了使圖中的節點數最少,取這個數中的最小值作爲,那麼圖中的邊數爲。
P3403
- 題意:
相當於求能夠組成多少個小於的不同的數。 - 思路:
因爲當時得到,所以根據的定義有。
求出後,可知, - 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
- 題目:
求的值有多少個在內,已知。 - 思路:
先求出有多少種小於等於h的值,記爲(這就是P3403要解決的問題)。
- 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
- 題目:
題目轉化爲求不能組成的最大的數(根據題意可知指的是正數),已知,不存在或最大值不能確定輸出。 - 思路:
- 如果存在,那麼可以得到任何正數,輸出
- 如果對於的一個同餘類,得不到相應的,說明對於滿足的,都不能由這些數得到,且可以無限大,無法確定最大值,輸出。
- 是同餘類裏面的最小值,那麼在同餘類中,屬於這個同餘類但不可得到的最大數爲,那麼
- 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
- 題目:
求滿足的一組解,已知。 - 思路:
裸的同餘最短路題,在求最短路的時候記錄下值在最短路的路徑中出現的次數。
當然本地也可以枚舉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;
}