1.Dijkstra:单源最短路
bool vis[MAXN]; int d[MAXN]; priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > Q; void Dijestra(int begin){ memset(d, 127, sizeof(d)); d[begin] = 0; Q.push(make_pair(0, begin)); while(!Q.empty()){ int x = Q.top().second; int cost = Q.top().first; Q.pop(); if(vis[x]) continue; vis[x] = true; for(int l=head[x]; l; l=next[l]){ int y = last[l]; if(!vis[y] && d[y] > val[l] + cost) d[y] = val[l] + cost, Q.push(make_pair(d[y], y)); } } }
时间复杂度:$O((n+m)log(n+m))$
2.SPFA:单源最短路,判断负环
int d[MAXN]; bool inQ[MAXN]; queue<int> Q; void SPFA(int begin){ memset(d, 127, sizeof(d)); Q.push(begin), inQ[begin] = true, d[begin] = 0; while(!Q.empty()){ int x = Q.front(); inQ[x] = false, Q.pop(); for(int l=head[x]; l; l=next[l]){ int y = last[l]; if(d[y] > d[x] + val[l]){ d[y] = d[x] + val[l]; if(!inQ[y]) Q.push(y), inQ[y] = true; } } } }
时间复杂度:随机数据$O(km)$,最坏情况$O(nm)$
3.Floyd:多源最短路,矩阵乘法
for(int k=1; k<=n; k++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
时间复杂度:$O(n^3)$
经典例题:
1.在Dp转移决策需要预处理时,可将一个Dp看作图中一个点,决策代价看作边权,起始状态看作出发点,最终状态看作终点。
例题:Online 1503 BZOJ2259 新型计算机
Dp转移方程十分容易推:$Dp[i] = min(Dp[i+a[i]+1], Dp[j]+abs(a[i]-(i-j-1)))$,然而转移却需要$O(n)$的时间,面对$n<=10^6$的数据是过不了的(如果用线段树可以$O(logn)$转移)
看到这里,我想到用图论,在$i$和$i+a[i]+1$之间连一条长度为$0$的边,从$i+a[i]+1$开始向两边的点连长度为$1$的边(如果已经连过就跳过),平均连边时间复杂度为$O(n)$,最多不超过4n条点,起点$0$,终点为$n+1$,求最短路即可。
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <queue> using namespace std; long long read(){ long long ans = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar(); while(isdigit(ch)); return ans * f; } const int MAXN = 1000003, MAXM = 4 * MAXN; int head[MAXN], next[MAXM], last[MAXM], val[MAXM], lineNum = 0; void add(int x,int y,int v){ lineNum++, next[lineNum] = head[x], head[x] = lineNum, last[lineNum] = y, val[lineNum] = v; } int a[MAXN]; bool conneL[MAXN], conneR[MAXN], inQ[MAXN]; bool vis[MAXN]; int d[MAXN]; priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > Q; void Dijestra(int begin){ memset(d, 127, sizeof(d)); d[begin] = 0; Q.push(make_pair(0, begin)); while(!Q.empty()){ int x = Q.top().second; int cost = Q.top().first; Q.pop(); if(vis[x]) continue; vis[x] = true; for(int l=head[x]; l; l=next[l]){ int y = last[l]; if(!vis[y] && d[y] > val[l] + cost) d[y] = val[l] + cost, Q.push(make_pair(d[y], y)); } } } int main(){ int n = read(); for(int i=1; i<=n; i++) a[i] = read(); for(int i=1; i<=n; i++){ if(i+a[i] <= n) add(i, i+a[i]+1, 0); if(i+a[i] > n){ add(i, n+1, a[i]+i-n); continue; } for(int j=a[i]+i+1; j>i && !conneL[j]; j--) add(j, j-1, 1), conneL[j] = true; for(int j=a[i]+i+1; j<=n && !conneR[j]; j++) add(j, j+1, 1), conneR[j] = true; } Dijestra(1); printf("%d", d[n+1]); return 0; }
2.Dp转移决策无法确定是否计算时
例题:Online 1292 NOIP2018模拟 过河
这道题的关键转移的决策不好推,一般的Dp都是顺序递推,而像这道题的决策却有很多情况,考虑用图论,只需要保证图中存在一条路径是正确答案即可。
首先从假想的原点连到可以到岸边的板子上,对终点同样考虑,之后考虑版子之间的连接,最后对于一个板子,可以花费一些代价使自己变得更大,同样进行连边(重要),因为你不可能两次经过同一个板子,所以花费代价再跳到其他板子是正确的决策。
#include <iostream> #include <cstdio> #include <algorithm> #include <queue> #include <cstring> using namespace std; const int MAXN = 255,MAXPOINT = 255*255,MAXLINE = 255*255*255*2; struct point { long long X,Y; }; struct code { long long R,V; }; bool cmp(code a1,code a2) { if(a1.R == a2.R) return a1.V < a2.V; else return a1.R > a2.R; } int head[MAXPOINT], next[MAXLINE], value[MAXLINE], last[MAXLINE], LineNum = 0; int PointNum, CodeNum, Breath; long long Cost[MAXPOINT]; point P[MAXN]; code C[MAXN]; void add(int x,int y,int v) { LineNum++, value[LineNum] = v,last[LineNum] = y,next[LineNum] = head[x], head[x] = LineNum; } void init() { memset(head, 0, sizeof(head)); LineNum = 0; memset(Cost, 127, sizeof(Cost)); scanf("%d%d%d",&PointNum,&CodeNum,&Breath); for(int i=1; i<=PointNum; i++) scanf("%lld%lld",&P[i].X,&P[i].Y); for(int i=1; i<=CodeNum; i++) scanf("%lld%lld",&C[i].R,&C[i].V); return; } void preduct() { sort(C+1, C+1+CodeNum, cmp); bool cut[CodeNum+1]; long long cost = C[1].V; for(int i=2; i<=CodeNum; i++) { if(C[i].R == C[i-1].R) cut[i] = true; else if(C[i].V >= cost) cut[i] = true; else cost = C[i].V, cut[i] = false; } int p1 = 1, p2 = 1; while(p2 <= CodeNum) { if(p1 != p2) C[p1] = C[p2]; p1++, p2++; while(p2 <= CodeNum && cut[p2]) p2++; } CodeNum = p1-1; } void addLine() { for(int i=1; i<=PointNum; i++) { for(int j=1; j<=PointNum; j++) { if(i == j) continue; int r = CodeNum; long long d = (P[i].Y-P[j].Y)*(P[i].Y-P[j].Y) + (P[i].X-P[j].X)*(P[i].X-P[j].X); for(int k=1; k<=CodeNum; k++) { while(r>0 && (C[r].R+C[k].R)*(C[r].R+C[k].R) < d) r--; if(r != 0) add((i-1)*CodeNum+k, (j-1)*CodeNum+r, C[r].V); } } } for(int i=1; i<=PointNum; i++) for(int j=CodeNum-1; j>=1; j--) add((i-1)*CodeNum+j+1, (i-1)*CodeNum+j, C[j].V-C[j+1].V); for(int i=1; i<=PointNum; i++) { for(int j=1; j<=CodeNum; j++) { if(C[j].R < P[i].Y) break; add(0, (i-1)*CodeNum+j, C[j].V); } } for(int i=1; i<=PointNum; i++) { for(int j=1; j<=CodeNum; j++) { if(C[j].R < Breath - P[i].Y) break; add((i-1)*CodeNum+j, CodeNum*PointNum+1, 0); } } } void SPFA() { queue<int> q; q.push(0); Cost[0] = 0; while(!q.empty()) { int t = q.front(); q.pop(); for(int i = head[t]; i; i = next[i]) { if(Cost[last[i]] > Cost[t] + value[i]) { Cost[last[i]] = Cost[t] + value[i]; q.push(last[i]); } } } } int main() { int T; scanf("%d",&T); while(T--) { init(); preduct(); addLine(); SPFA(); if(Cost[CodeNum * PointNum + 1] == 9187201950435737471) printf("impossible\n"); else printf("%lld\n",Cost[CodeNum * PointNum + 1]); } return 0; }
3.Floyd与矩阵乘法的结合
例题:Online 1119 奶牛接力
看着Floyd的代码,是否觉得有些熟悉,这不就是矩阵乘法的翻版嘛。其实从意义上来说,如果结果单独存在一个数组中,就有了新的意义——$Dp[i][j]$表示从$i$到$j$经历$k$个点后的图的连通性,而且从意义上来看,矩阵乘法满足结合律(当然不一定满足交换率),所以可以使用快速幂。
#include <iostream> #include <cstdio> #include <map> #include <cstring> #include <climits> using namespace std; long long read() { long long ans = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar(); while(ch >= '0' && ch <= '9'); return ans * f; } class Matrix { public: long long ** value; int size; void reset(int size) { this->size = size; value = new long long * [size]; for(int i=0; i<size; i++) value[i] = new long long [size]; } Matrix(int size) { reset(size); } void operator = (const int * a) { int * index = (int *)a; for(int i=0; i<size; i++) for(int j=0; j<size; j++) value[i][j] = *index, index++; } void operator *= (Matrix &other) { long long ans[size][size]; for(int i=0; i<size; i++){ for(int j=0; j<size; j++){ ans[i][j] = LLONG_MAX >> 1; for(int k=0; k<size; k++) ans[i][j] = min(ans[i][j], value[i][k] + other.value[k][j]); } } for(int i=0; i<size; i++) for(int j=0; j<size; j++) value[i][j] = ans[i][j]; } void operator = (Matrix &other) { for(int i=0; i<size; i++) for(int j=0; j<size; j++) value[i][j] = other.value[i][j]; } void operator ^= (long long n) { Matrix unit(size); unit = (*this); bool flag = false; while(n){ if(n & 1){ if(!flag) flag = true, (*this) = unit; else (*this) *= unit; } unit *= unit; n >>= 1; } } }; const int MAXN = 101; int l[MAXN*2], a[MAXN*2], b[MAXN*2]; int main() { int n = read(), T = read(), begin = read(), end = read(); int pointNum = 0; map<int,int> m; for(int i=1; i<=T; i++) { l[i] = read(), a[i] = read(), b[i] = read(); if(m.find(a[i]) == m.end()) m[a[i]] = pointNum++; if(m.find(b[i]) == m.end()) m[b[i]] = pointNum++; } int a_unit[pointNum][pointNum]; memset(a_unit, 127, sizeof(a_unit)); for(int i=1; i<=T; i++) a_unit[m[a[i]]][m[b[i]]] = a_unit[m[b[i]]][m[a[i]]] = l[i]; Matrix unit(pointNum); unit = a_unit[0]; unit ^= n; printf("%d",unit.value[m[begin]][m[end]]); return 0; }
同样还有一道例题:Online 1118 沼泽鳄鱼 也是用同样的思路
#include <iostream> #include <cstdio> #include <cstring> #include <climits> using namespace std; const int MOD = 10000; long long read(){ long long ans = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = ((ans << 1) + (ans << 3) + (ch ^ 48)), ch = getchar(); while(ch >= '0' && ch <= '9'); return ans * f; } const int MAXN = 50; struct Matrix{ int size; long long value[MAXN][MAXN]; Matrix(int SIZE = 0){ size = SIZE; memset(value, 0, sizeof(value)); } void normal(){ for(int i=0; i<size; i++) for(int j=0; j<size; j++) value[i][j] = (i == j) ? 1 : 0; } void operator = (long long * a){ long long * index = a; for(int i=0; i<size; i++) for(int j=0; j<size; j++) value[i][j] = *index, index++; } void operator *= (Matrix &other){ long long ans[size][size]; memset(ans, 0, sizeof(ans)); for(int i=0; i<size; i++) for(int j=0; j<size; j++) for(int k=0; k<size; k++) ans[i][j] = (ans[i][j] + value[i][k] * other.value[k][j]) % MOD; for(int i=0; i<size; i++) for(int j=0; j<size; j++) value[i][j] = ans[i][j]; } void operator = (Matrix &other){ size = other.size; memcpy(value, other.value, sizeof(value)); } void operator ^= (long long n){ Matrix unit = (*this); this->normal(); while(n){ if(n & 1) (*this) *= unit; unit *= unit; n >>= 1; } } void print() { for(int i=0; i<size; i++){ for(int j=0; j<size; j++) printf("%d ",value[i][j]); putchar('\n'); } } }; int main(){ int N = read(), M = read(), Start = read(), End = read(), K = read(); Matrix g[12]; for(int i=0; i<12; i++){ g[i].size = N; memset(g[i].value, 0, sizeof(g[i].value)); } for(int i=1; i<=M; i++){ int x = read(), y = read(); for(int j=0; j<12; j++){ g[j].value[x][y] = g[j].value[y][x] = 1; } } int NFish = read(); for(int i=1; i<=NFish; i++){ int T = read(); int w[T]; for(int j=0; j<T; j++) w[j] = read(); for(int j=1; j<=12; j++) for(int k=0; k<N; k++) g[j-1].value[k][w[j%T]] = 0; } Matrix ans(N); ans.normal(); for(int i=0; i<12; i++) ans *= g[i]; ans ^= (K / 12); for(int i=1; i<=K%12; i++) ans *= g[i-1]; printf("%lld",ans.value[Start][End]); return 0; }