【题目描述】
ZGY 有一个三角,就像下面这样(每一个点都有一个权值)。
第1 层有1 个,第2 层有2 个,第i 层有i 个。
这个三角一共有n 层,ZGY 每次可以从第i 层的第j 个走到第i + 1 层的第j 个或是第j +1 个,直到走到第n 层。从第1 层走到第n 层的一种方案成为一条路径,路径的权值为路径上点权值之和。
现在ZGY 想知道,权值前k 大的路径(存在多个正确答案)。
【输入格式】
第一行,两个整数n, k 表示三角一共有n 层,ZGY 想知道权值前k 大的路径。
接下来n 行:
其中第i 行包含i 个整数,其中第j 个整数Wij 表示第i 层第j 个点权值为Wij
【输出格式】
输出数据包含k 行,每行表示一条路径包含一个由“L” 和“R”组成的字符串,长
度为n - 1 其中第i 个字符表示在第i 层时向下一层走的方向。假设当前在第i 行第j 个点,
如果为“L”则走向第i + 1 行第j 个点,如果为“R”则走向第i + 1 行第j + 1 个点
解析:
60% :每个点用堆或数组记录前k大模拟即可。
100%:发现第k大的路径长度满足单调性。二分第k大路径的长度,每次check的时候查是否有k条路径的长度大于mid。问题变成了求图中第一层到最后一层有多少条路径长度大于某个值。怎么处理呢?预处理出每个点到最后一层的最大距离,记为f[i][j],跑dfs的时候如果已经经过的路径长度加上这个f[i][j]还小于mid值,直接return掉就ok了。
总结:
看到第k大不能只想到跟排序有关的算法,还应想到二分答案。
代码:
#include <cstdio> #include <iostream> using namespace std; const int MAXN = 2005; int w[MAXN][MAXN]; int n, k; int f[MAXN][MAXN]; int mid, cnt; char s[MAXN]; bool output = false; inline int read() { int ret = 0, f = 1; char c = getchar(); while(c < '0' || c > '9') {if(c == '-') f = -f; c = getchar();} while(c >= '0' && c <= '9') {ret = ret * 10 + c - '0'; c = getchar();} return ret * f; } void prework() { for(int i = 1; i <= n; ++ i) f[n][i] = w[n][i]; for(int t = n - 1; t >= 1; -- t) for(int i = 1; i <= t; ++ i) f[t][i] = max(f[t + 1][i], f[t + 1][i + 1]) + w[t][i]; } void dfs(int x, int y, int dis) { if(cnt >= k) return; if(dis + f[x][y] < mid) return; if(x == n) { ++ cnt; if(output){ for(int i = 1; i < n; ++ i) printf("%c", s[i]); printf("\n"); } return; } s[x] = 'L'; dfs(x + 1, y, dis + w[x][y]); s[x] = 'R'; dfs(x + 1, y + 1, dis + w[x][y]); } int check() { cnt = 0; dfs(1, 1, 0); return cnt; } void solve() { int l = 0, r = 1000 * 1000 + 1; while(l < r) { mid = (l + r + 1) >> 1; if(check() >= k) l = mid; else r = mid - 1; } mid = l; output = true; cnt = 0; dfs(1, 1, 0); } int main() { freopen("tri.in", "r", stdin); freopen("tri.out", "w", stdout); n = read(); k = read(); for(int i = 1; i <= n; ++ i) for(int j = 1; j <= i; ++ j) w[i][j] = read(); prework(); solve(); return 0; }