淺談同餘最短路

Torrentolf發表於2024-11-29

引入

先介紹一下大家熟知的差分約束問題,透過建圖將線性規劃問題轉化為圖論問題。

給定多個形如\(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 ;
}

完結★,°:.☆( ̄▽ ̄)/$:.°★ 。~~~~~~

相關文章