[Codeforces 147B Smile House] DP+倍增+二分
1. 题目链接
2. 题意描述
给定顶点数为
n
,边数
(1≤n≤300,0≤m≤n∗(n−1)2,−104≤边长aij≤104)
.
3. 解题思路
首先对无向图进行如下预处理:
- 给每个顶点添加一条边(自环),指向自己;
- 对于原图没有指定的边,我们统统认为它的权值为 −∞ .
这样处理之后,我们就可以对答案进行二分了。
很容易想到,比较朴素的DP是用
dp[s][i][j]
表示从
i
到
采用倍增的思想,令dp[s][i][j]表示从
i
到
然后对答案进行二分,如何判断经过
step
条边是否能达到权值之和
>0
?。
首先,将
step
二进制展开。(如
step=5=1+4
,那么肯定是先走1条边然后走4条边)。然后同样的
O(n3logn)
时间内,利用
dp[][][]
数组拼凑起来得到
ans[step][i][j]
(表示从
i
到
4. 实现代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 300 + 5;
const int MBIT = 15 + 5;
const int INF = 0x3f3f3f3f;
int n, m, MT, dp[MBIT][MAXN][MAXN];
int ans[2][MAXN][MAXN];
inline void umax(int& a, const int &b) { a = max(a, b); }
void init() {
MT = (int)floor(log2((long double)n));
for(int s = 0; s <= MT; s++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
dp[s][i][j] = -INF;
}
dp[s][i][i] = 0;
}
}
}
void clr(int z) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
ans[z][i][j] = -INF;
}
ans[z][i][i] = 0;
}
}
bool check(int step) {
int z = 0;
clr(z);
for(int s = 0; s <= MT; s++) {
if(((step >> s) & 1) == 0) continue;
z ^= 1;
clr(z);
for(int k = 1; k <= n; k++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
umax(ans[z][i][j], ans[z ^ 1][i][k] + dp[s][k][j]);
}
}
}
}
for(int i = 1; i <= n; i++) {
if(ans[z][i][i] > 0) return true;
}
return false;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
int u, v, a, b;
while(~scanf("%d %d", &n, &m)) {
init();
for(int i = 1; i <= m; i++) {
scanf("%d %d %d %d", &u, &v, &a, &b);
dp[0][u][v] = a, dp[0][v][u] = b;
}
for(int s = 1; s <= MT; s++) {
for(int k = 1; k <= n; k++) {
for(int i = 1; i <= n; i++) {
if(dp[s - 1][i][k] == -INF) continue;
for(int j = 1; j <= n; j++) {
umax(dp[s][i][j], dp[s - 1][i][k] + dp[s - 1][k][j]);
}
}
}
}
int lb = 2, ub = n + 1, mid;
while(lb <= ub) {
mid = (lb + ub) >> 1;
if(check(mid)) ub = mid - 1;
else lb = mid + 1;
}
if(lb >= n + 1) lb = 0;
printf("%d\n", lb);
}
return 0;
}