1. 题面梗概
中译中:给一张 n 个点 m 条边的有向图,边有权 wi∈[1,5] 代表天数。每个点有权 ci 代表每天到达后的收益。在特定的时间 ti 到达特定的点 xi 有额外收益 yi。
求从点 1 出发,恰好在第 T 天回到点 1 的最大总收益。
n≤50,T≤109,wi∈[1,5],k≤200
2. 逻辑推导
最容易想到一版 DP,定义 dp[t][u] 为第 t 天到达城市 u 的最大收益:
dp[t][v]=(u,v,w)∈Emax{dp[t−w][u]+cv}+fest(t,v)
但这只能得到 40pts,原因在于 T=109 时,时间轴太长,无法逐日递推。我们需要优化。
2.1 矩阵快速幂
因为 T 很大而 n,m,w 都较小,容易想到矩阵快速幂优化。
对于传统的矩阵乘法,我们是这样定义的:
C=A×B⟺C[i][j]=k=1∑nA[i][k]×B[k][j]
但在本题中,DP 的转移不是相加而是取 max,我们尝试魔改一下矩阵乘法:
C=A⊕B⟺C[i][j]=k=1maxn(A[i][k]+B[k][j])
也就是 (max,+) 的矩阵,可以证明这个矩阵是符合结合律的,证明略。
2.2 拆点
矩阵快速幂要求:dp[t]→dp[t+1] 的转移是恒定的,但本题边权 w∈[1,5]。
我们把原本的城市 u 拆成 5 个状态节点 (u,0),(u,1),(u,2),(u,3),(u,4)。
状态含义:(u,i) 表示 “还有 i 天到达城市 u”。
内部转移:(u,i)→(u,i−1) 权值为 0
外部转移:对于一条边 (u,v,w),则连边 (u,0)→(v,w−1) 权值为 c[v]。
此时时间复杂度为 O((5n)3logT),预期 65 pts。
以上矩阵快速幂,只能处理 k=0 的情况,也就是没有美食节的情况。
2.3 k 的处理
注意到题目保证了任意两个美食节不在同一天举办。所以可以先将美食节按举办时间从小到大排序。
然后在任意两个美食节之间,做一遍普通的DP转移。例如,第 j 个美食节的举办日期为 tj ,第 j−1 个美食节举办日前为 tj−1 ,则这段的转移就是:
dp[tj]=dp[tj−1]⊕Mtj−tj−1
转移完成后,再令dp[tj][xj]+=yj ,也就是加上了美食节的贡献。
时间复杂度 O(k⋅(5n)3⋅logT),预期 75 pts。
2.4 进一步优化
我们发现所做的 k 次转移,每次都是乘以同一个转移矩阵的若干次幂。
不妨提前预处理 2k 的矩阵,也就是 (M1,M2,M4,…),复杂度为 O(N3logT)。
然后,在做每个美食节的转移时,对 dt=tj−tj−1 这个数做二进制分解,记 dt=2p1+2p2+⋯+2pl,也就是 (p1,p2,…,pl) 这些二进制位上为 1。
那么我们只需要用 dp[tj−1],依次乘以与处理好的 M2p1,M2p2,…,M2pl,即可得到 d[tj]。
由于 dp 是一个 1×5n 的长条(向量),所以乘法的复杂度是 O((5n)2)。
所有的转移复杂度为 O(k⋅(5n)2⋅logT)。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int maxn = 50 + 5; const int maxn2 = 250 + 5; const int maxk = 200 + 5; const ll inf = 1e18;
ll c[maxn]; int n, m, T, k; int sz;
struct Mat { ll mat[maxn2][maxn2]; Mat( ) { memset( mat, -0x3f, sizeof (mat) ); } } pw[31];
Mat mul( const Mat &A, const Mat &B ) { Mat res; for( int i = 1; i <= sz; ++ i ) { for( int k = 1; k <= sz; ++ k ) { if( A.mat[i][k] < 0 ) continue; for( int j = 1; j <= sz; ++ j ) { if( B.mat[k][j] < 0 ) continue; res.mat[i][j] = max( res.mat[i][j], A.mat[i][k] + B.mat[k][j] ); } } } return res; }
void vec_mul( ll *V, const Mat &M ) { static ll tmp[maxn2]; for( int i = 1; i <= sz; ++ i ) tmp[i] = -inf;
for( int i = 1; i <= sz; ++ i ) { if( V[i] < 0 ) continue; for( int j = 1; j <= sz; ++ j ) { if( M.mat[i][j] < 0 ) continue; tmp[j] = max( tmp[j], V[i] + M.mat[i][j] ); } } for( int i = 1; i <= sz; ++ i ) V[i] = tmp[i]; }
struct Festival { int t, x, y; bool operator < ( const Festival &oth ) const { return t < oth.t; } } fes[205];
int main( ) { ios::sync_with_stdio( 0 ); cin.tie( 0 ); cin >> n >> m >> T >> k; sz = 5 * n; for( int i = 1; i <= n; ++ i ) cin >> c[i];
Mat base; for( int i = 1; i <= n; ++ i ) for( int j = 1; j <= 4; ++ j ) base.mat[i + j * n][i + (j - 1) * n] = 0;
for( int i = 1; i <= m; ++ i ) { int u, v, w; cin >> u >> v >> w; base.mat[u][v + (w - 1) * n] = c[v]; }
pw[0] = base; for( int i = 1; i <= 30; ++ i ) pw[i] = mul( pw[i - 1], pw[i - 1] );
for( int i = 1; i <= k; ++ i ) cin >> fes[i].t >> fes[i].x >> fes[i].y; sort( fes + 1, fes + k + 1 );
ll V[maxn2]; for( int i = 1; i <= sz; ++ i ) V[i] = -inf; V[1] = c[1];
int lst = 0; fes[k + 1] = { T, 1, 0 };
for( int i = 1; i <= k + 1; ++ i ) { int dt = fes[i].t - lst; for( int p = 30; p >= 0; -- p ) if( dt >> p & 1 ) vec_mul( V, pw[p] ); if( V[fes[i].x] >= 0 ) V[fes[i].x] += fes[i].y; lst = fes[i].t; }
if( V[1] < 0 ) cout << -1 << '\n'; else cout << V[1] << '\n'; return 0; }
|
4. 复盘
关联笔记:树图DP | [[优化DP#矩阵快速幂]]