引入
先介紹一下大家熟知的差分約束問題,透過建圖將線性規劃問題轉化為圖論問題。
給定多個形如\(a_i - a_j \geq c_{i , j}\)的不定式,找出一種可行解。
這種問題有一種很巧妙的構造方法,就是將這類問題抽象成一個圖論問題來解決。
具體來說,就是將不等式移項,變為\(a_i \geq a_j + c_{i , j}\),發現很像最短(長)路中的式子。
在所有邊都鬆弛完成之後,最短路中每條邊都滿足\(dis_j \leq dis_i + w_{i , j}\)(不存在負環),反之可以繼續鬆弛。同理,在最長路中每條邊都滿足\(dis_j \leq dis_i + w_{i , j}\)(不存在正環)。
然後,我們發現這最短(長)路中的不等式的和原來的不等式很像,於是連線\(i\)與\(j\)之間邊權為\(w_{i , j}\)的邊,跑一遍最長路,就得到了原問題的一種可行解。
與差分約束類似,同餘最短路利用同餘來構造一些狀態,可以達到最佳化空間複雜度的目的。
例題
[ABC077D] Small Multiple
我們可以先考慮如何求出一個數各數位之和。
先看個位,\(1\)的位和為\(1\),\(2\)就在\(1\)的前提下\(+1\),以此類推,就能求出個位位和。
再看其他位,無非就是\(n\)個\(10\)加上若干個\(1\),而且只有\(1\)會對答案產生貢獻。
所以我們一共存在兩種狀態,一種是\(+1\),另外一種是\(\times 10\),其中\(+1\)邊權是\(1\),\(\times 10\)邊權是\(0\)。
但是這種方法還有一個問題,就是有可能會爆\(long\ long\),但是我們考慮因為要求的是\(K\)的倍數的各數位之和,所以每次\(\times 10\)時\(/mod\ K\)就可以讓餘數不變,從而不影響結果。
然後,跑一遍\(SPFA\)或者\(Dijkstra\)或\(01Bfs\)就行了,答案就是\(dis_0\)。
參考程式碼:
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std ;
namespace IO
{
namespace Read
{
#define isdigit(ch) (ch >= '0' && ch <= '9')
#define SIZE (1 << 16)
char buf[SIZE] , *_now = buf , *_end = buf ;
char getchar()
{
if(_now == _end)
{
_now = _end = buf ;
_end += fread(buf , 1 , SIZE , stdin) ;
if(_now == _end)
return EOF ;
}
return *(_now++) ;
}
template<typename T>
void read(T& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = (w << 1) + (w << 3) + (ch ^ 48) , ch = getchar() ;
w *= f ;
}
void read(char& c)
{
char tmp = getchar() ;
while(tmp == ' ' || tmp == '\n')
tmp = getchar() ;
c = tmp ;
}
#define sb(ch) (ch == ' ' || ch == '\n')
void read(char* s)
{
char ch = getchar() ;
while(sb(ch))
ch = getchar() ;
int len = 0 ;
while(!sb(ch))
s[len++] = ch , ch = getchar() ;
s[len] = '\0' ;
}
void read(string& s)
{
s.clear() ;
char ch = getchar() ;
while(sb(ch))
ch = getchar() ;
while(!sb(ch))
s.push_back(ch) , ch = getchar() ;
}
void read(double& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() ;
if(ch == '.')
{
ch = getchar() ;
int len = 0 ;
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
while(len)
w /= 10 , --len ;
}
w *= f ;
}
void read(float& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() ;
if(ch == '.')
{
ch = getchar() ;
int len = 0 ;
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
while(len)
w /= 10 , --len ;
}
w *= f ;
}
void read(long double& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() ;
if(ch == '.')
{
ch = getchar() ;
int len = 0 ;
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
while(len)
w /= 10 , --len ;
}
w *= f ;
}
class qistream
{
public:
template<typename T>
qistream& operator>>(T& a)
{
read(a) ;
return *this ;
}
qistream& operator>>(char* s)
{
read(s) ;
return *this ;
}
} qcin ;
}
namespace Write
{
#define SIZE (1 << 16)
char buf[SIZE] , *p = buf ;
template<typename T>
struct pres
{
T num ;
int len ;
} ;
template<typename T>
pres<T> make_lf(T num , int len)
{
pres<T> ret ;
ret.num = num , ret.len = len ;
return ret ;
}
void flush()
{
fwrite(buf , 1 , p - buf , stdout) ;
p = buf ;
}
void putchar(char ch)
{
if(p == buf + SIZE)
flush() ;
*p = ch ;
++p ;
}
class Flush{public:~Flush(){flush() ;};}_;
template<typename T>
void write(T x)
{
char st[50] ;
int len = 0 ;
if(x < 0)
putchar('-') , x = -x ;
do
{
st[++len] = x % 10 + '0' ;
x /= 10 ;
} while(x) ;
while(len)
putchar(st[len--]) ;
}
void write(char c)
{
putchar(c) ;
}
void write(const char* s)
{
int siz = strlen(s) ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
void write(char* s)
{
int siz = strlen(s) ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
void write(string& s)
{
int siz = s.size() ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
void write(double x , int len = 6)
{
if(x < 0)
write('-') , x = -x ;
long long sb = (long long)(x) ;
x -= sb ;
int spar = 0 ;
for(int i = 0 ; i <= len ; ++i)
{
x *= 10 ;
if(int(x) == 0)
++spar ;
}
long long ssb = (long long)(x) ;
if(ssb % 10 >= 5)
ssb += 10 ;
ssb /= 10 ;
write(sb) , write('.') ;
for(int i = 1 ; i <= spar ; ++i)
write('0') ;
write(ssb) ;
}
void write(float x , int len = 6)
{
if(x < 0)
write('-') , x = -x ;
int sb = int(x) ;
x -= sb ;
int spar = 0 ;
for(int i = 0 ; i <= len ; ++i)
{
x *= 10 ;
if(int(x) == 0)
++spar ;
}
int ssb = int(x) ;
if(ssb % 10 >= 5)
ssb += 10 ;
ssb /= 10 ;
write(sb) , write('.') ;
for(int i = 1 ; i <= spar ; ++i)
write('0') ;
write(ssb) ;
}
void write(long double x , int len = 6)
{
if(x < 0)
write('-') , x = -x ;
__int128 sb = __int128(x) ;
x -= sb ;
int spar = 0 ;
for(int i = 0 ; i <= len ; ++i)
{
x *= 10 ;
if(int(x) == 0)
++spar ;
}
__int128 ssb = __int128(x) ;
if(ssb % 10 >= 5)
ssb += 10 ;
ssb /= 10 ;
write(sb) , write('.') ;
for(int i = 1 ; i <= spar ; ++i)
write('0') ;
write(ssb) ;
}
class qostream
{
public:
template<typename T>
qostream& operator<<(T x)
{
write(x) ;
return *this ;
}
qostream& operator<<(const char* s)
{
write(s) ;
return *this ;
}
template<typename T>
qostream& operator<<(const pres<T> x)
{
write(x.num , x.len) ;
return *this ;
}
} qcout ;
}
using Read::qcin ;
using Write::qcout ;
using Write::make_lf ;
}
using namespace IO ;
int s = 0 ;
struct edg
{
int to , val ;
bool operator<(const edg& b) const
{
return val > b.val ;
}
} ;
edg make_edg(int to , int val)
{
edg ret ;
ret.to = to , ret.val = val ;
return ret ;
}
vector<edg> edge[100005] ;
int dis[100005] = {} ;
bool vis[100005] = {} ;
void dij(int sta)
{
memset(dis , 0x3f , sizeof dis) ;
dis[sta] = 1 ;
priority_queue<edg> Q ;
Q.push(make_edg(sta , 0)) ;
while(!Q.empty())
{
int t = Q.top().to ;
Q.pop() ;
if(vis[t])
continue ;
vis[t] = true ;
int siz = edge[t].size() ;
for(int i = 0 ; i < siz ; ++i)
{
int to = edge[t][i].to , val = edge[t][i].val ;
if(!vis[to] && dis[t] + val < dis[to])
dis[to] = dis[t] + val , Q.push(make_edg(to , dis[to])) ;
}
}
}
bool use[100005] = {} ;
void spfa(int sta)
{
memset(dis , 0x3f , sizeof dis) ;
dis[sta] = 1 ;
queue<int> Q ;
Q.push(sta) ;
while(!Q.empty())
{
int t = Q.front() ;
Q.pop() ;
use[t] = false ;
int siz = edge[t].size() ;
for(int i = 0 ; i < siz ; ++i)
{
int to = edge[t][i].to , val = edge[t][i].val ;
if(dis[t] + val < dis[to])
{
dis[to] = dis[t] + val ;
if(!use[to])
{
use[to] = true ;
Q.push(to) ;
}
}
}
}
}
int main()
{
qcin>>s ;
for(int i = 0 ; i < s ; ++i)
edge[i].push_back(make_edg((i + 1) % s , 1)) , edge[i].push_back(make_edg((i * 10) %s , 0)) ;
// dij(1) ;
// spfa(1) ;
qcout<<dis[0]<<'\n' ;
return 0 ;
}
P9140 [THUPC 2023 初賽] 揹包
我們發現該題中的\(V\)很大但是\(v_i\)很小,而且是完全揹包問題,所以考慮使用同餘最短路。
-
首先考慮貪心,因為揹包容量很大,所以其中佔大部分的一定是價效比最高的物品,我們記該物品編號為\(k\),其體積為\(v_k\),價值為\(c_i\)。
-
我們考慮選一些總體積為\(v_k\)的物品一定不如選一個\(k\)物品,因為\(k\)的價效比最高,但是對於總體積不為\(v_k\)就不一定,最優解可能會比全部取\(k\)並空出剩下容量要優或劣(劣是因為本題要求總體積必須為\(V\))。考慮求出要優或劣多少。
-
考慮\(DP\),設\(V\)為體積,\(C\)為價值,\(dp_i\)為\(i\equiv (V \mod v_k)\)下最大的$C - \lfloor \frac{V}{v_i} \rfloor \times c_k \(,也就是最優解比全部取\)k\(優或劣多少。可以證明\)V\(不會大於實際詢問的\)V$
(但是我懶)。轉移方程式即為:
\[dp_i = \max_{1 \leq j \leq n}(dp_j + c_j - \lfloor \frac{i + v_j}{v_k} \rfloor \times c_k) \] -
然後,我們發現:這個\(DP\)轉移是有後效性的,因此無法直接轉移,需要建圖跑最長路。
-
查詢結果就是\(\frac{V}{v_k} \times c_k + dp_{V \ mod \ v_k}\)。
我看題解都是轉圈寫的,但是我不會,所以只能跑\(SPFA\)。
參考程式碼:
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std ;
namespace IO
{
namespace Read
{
#define isdigit(ch) (ch >= '0' && ch <= '9')
#define SIZE (1 << 16)
char buf[SIZE] , *_now = buf , *_end = buf ;
char getchar()
{
if(_now == _end)
{
_now = _end = buf ;
_end += fread(buf , 1 , SIZE , stdin) ;
if(_now == _end)
return EOF ;
}
return *(_now++) ;
}
template<typename T>
void read(T& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = (w << 1) + (w << 3) + (ch ^ 48) , ch = getchar() ;
w *= f ;
}
void read(char& c)
{
char tmp = getchar() ;
while(tmp == ' ' || tmp == '\n')
tmp = getchar() ;
c = tmp ;
}
#define sb(ch) (ch == ' ' || ch == '\n')
void read(char* s)
{
char ch = getchar() ;
while(sb(ch))
ch = getchar() ;
int len = 0 ;
while(!sb(ch))
s[len++] = ch , ch = getchar() ;
s[len] = '\0' ;
}
void read(string& s)
{
s.clear() ;
char ch = getchar() ;
while(sb(ch))
ch = getchar() ;
while(!sb(ch))
s.push_back(ch) , ch = getchar() ;
}
void read(double& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() ;
if(ch == '.')
{
ch = getchar() ;
int len = 0 ;
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
while(len)
w /= 10 , --len ;
}
w *= f ;
}
void read(float& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() ;
if(ch == '.')
{
ch = getchar() ;
int len = 0 ;
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
while(len)
w /= 10 , --len ;
}
w *= f ;
}
void read(long double& w)
{
w = 0 ;
short f = 1 ;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch == '-')
f = -1 ;
ch = getchar() ;
}
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() ;
if(ch == '.')
{
ch = getchar() ;
int len = 0 ;
while(isdigit(ch))
w = w * 10 + (ch ^ 48) , ch = getchar() , ++len ;
while(len)
w /= 10 , --len ;
}
w *= f ;
}
class qistream
{
public:
template<typename T>
qistream& operator>>(T& a)
{
read(a) ;
return *this ;
}
qistream& operator>>(char* s)
{
read(s) ;
return *this ;
}
} qcin ;
}
namespace Write
{
#define SIZE (1 << 16)
char buf[SIZE] , *p = buf ;
template<typename T>
struct pres
{
T num ;
int len ;
} ;
template<typename T>
pres<T> make_lf(T num , int len)
{
pres<T> ret ;
ret.num = num , ret.len = len ;
return ret ;
}
void flush()
{
fwrite(buf , 1 , p - buf , stdout) ;
p = buf ;
}
void putchar(char ch)
{
if(p == buf + SIZE)
flush() ;
*p = ch ;
++p ;
}
class Flush{public:~Flush(){flush() ;};}_;
template<typename T>
void write(T x)
{
char st[50] ;
int len = 0 ;
if(x < 0)
putchar('-') , x = -x ;
do
{
st[++len] = x % 10 + '0' ;
x /= 10 ;
} while(x) ;
while(len)
putchar(st[len--]) ;
}
void write(char c)
{
putchar(c) ;
}
void write(const char* s)
{
int siz = strlen(s) ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
void write(char* s)
{
int siz = strlen(s) ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
void write(string& s)
{
int siz = s.size() ;
for(int i = 0 ; i < siz ; ++i)
putchar(s[i]) ;
}
void write(double x , int len = 6)
{
if(x < 0)
write('-') , x = -x ;
long long sb = (long long)(x) ;
x -= sb ;
int spar = 0 ;
for(int i = 0 ; i <= len ; ++i)
{
x *= 10 ;
if(int(x) == 0)
++spar ;
}
long long ssb = (long long)(x) ;
if(ssb % 10 >= 5)
ssb += 10 ;
ssb /= 10 ;
write(sb) , write('.') ;
for(int i = 1 ; i <= spar ; ++i)
write('0') ;
write(ssb) ;
}
void write(float x , int len = 6)
{
if(x < 0)
write('-') , x = -x ;
int sb = int(x) ;
x -= sb ;
int spar = 0 ;
for(int i = 0 ; i <= len ; ++i)
{
x *= 10 ;
if(int(x) == 0)
++spar ;
}
int ssb = int(x) ;
if(ssb % 10 >= 5)
ssb += 10 ;
ssb /= 10 ;
write(sb) , write('.') ;
for(int i = 1 ; i <= spar ; ++i)
write('0') ;
write(ssb) ;
}
void write(long double x , int len = 6)
{
if(x < 0)
write('-') , x = -x ;
__int128 sb = __int128(x) ;
x -= sb ;
int spar = 0 ;
for(int i = 0 ; i <= len ; ++i)
{
x *= 10 ;
if(int(x) == 0)
++spar ;
}
__int128 ssb = __int128(x) ;
if(ssb % 10 >= 5)
ssb += 10 ;
ssb /= 10 ;
write(sb) , write('.') ;
for(int i = 1 ; i <= spar ; ++i)
write('0') ;
write(ssb) ;
}
class qostream
{
public:
template<typename T>
qostream& operator<<(T x)
{
write(x) ;
return *this ;
}
qostream& operator<<(const char* s)
{
write(s) ;
return *this ;
}
template<typename T>
qostream& operator<<(const pres<T> x)
{
write(x.num , x.len) ;
return *this ;
}
} qcout ;
}
using Read::qcin ;
using Write::qcout ;
using Write::make_lf ;
}
using namespace IO ;
#define int long long
struct edg
{
int to , val ;
} ;
edg make_edg(int to , int val)
{
edg ret ;
ret.to = to , ret.val = val ;
return ret ;
}
vector<edg> edge[100005] ;
int n = 0 , T = 0 ;
int w[55] = {} , v[55] = {} ;
int m = 1000000 , t = 0 ;
bool use[100005] = {} ;
int dis[100005] = {} ;
void spfa(int sta)
{
for(int i = 0 ; i <= m ; ++i)
dis[i] = -1e18 ;
dis[sta] = 0 ;
queue<int> Q ;
Q.push(sta) ;
while(!Q.empty())
{
int now = Q.front() ;
Q.pop() ;
use[now] = false ;
int siz = edge[now].size() ;
for(int i = 0 ; i < siz ; ++i)
{
int to = edge[now][i].to , val = edge[now][i].val ;
if(dis[now] + val > dis[to])
{
dis[to] = dis[now] + val ;
if(!use[to])
{
use[to] = true ;
Q.push(to) ;
}
}
}
}
}
signed main()
{
qcin>>n>>T ;
for(int i = 1 ; i <= n ; ++i)
{
qcin>>w[i]>>v[i] ;
if(v[i] * m > t * w[i])
m = w[i] , t = v[i] ;
}
for(int i = 0 ; i <= m ; ++i)
for(int j = 1 ; j <= n ; ++j)
edge[i].push_back(make_edg((i + w[j]) % m , v[j] - (i + w[j]) / m * t)) ;
spfa(0) ;
while(T--)
{
int V = 0 ;
qcin>>V ;
if(dis[V % m] <= -1e17)
qcout<<"-1\n" ;
else
qcout<<dis[V % m] + V / m * t<<'\n' ;
}
return 0 ;
}