BZOJ1912: [Apio2010]patrol 巡逻

又是一道看了题解的题

题意:给出一棵边权为 1 的树,要求一次走完所有的边并回到出发点,规定你可以选择连 k 条边权为 1 的边,并且新连的边必须且只能走一次,求最短路程

显然 k = 1 的时候答案为 (2n - 树的直径 - 1)

现在考虑 k = 2 的时候如何连第二条边

由于每条原来的边至少走一次,连边的策略需要尽可能保证所连接的两点及其 lca 之间的边经过次数 > 1,并选择最长的路径

但显然不是所有情况都有 满足条件的路径 能使得答案最优的

那接下来考虑如何在决策选点对时考虑上之前连的边的影响

以下来自题解

若新连出的环与之前连出的环不重叠,则答案会继续减小

若重叠,两环重叠的部分就不会被巡逻到,又因题目要求至少经过一次,我们不得不在合适的时候经过这条边并返回

最终的后果是两环重叠的部分由之前的“只需要经过一次”变成了“需要经过两次”

再想一下,对于原图,每条边需要经过两次,相当于这条边变回去了

我们这时把第一次求出的直径上的边边权取相反数

再求一遍直径,需要注意的是这一次一定要用 dp 求树的直径,因为有负边权了, bfs 第一次找最深的节点的性质不满足,会搞炸,可以手画样例理解一下

设两次求出的直径分别为 l1, l2

则答案为 (2(n - 1) - (l1 - 1) - (l2 - 1)) = 2n - l1 - l2

以下 copy 自题解

如果 直径l2 包含 直径l1 的部分

当减掉 (l1 - 1) 后,重叠的部分变为“只需要经过一次”,减掉 (l2 - 1) 后,重叠的部分就变回了“需要经过两次”,这正是我们想要的


代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cstdio>
#include<queue>
using namespace std;

const int MAXN = 100005;

struct EDGE{
  int nxt, to, val;
  EDGE(int NXT = 0, int TO = 0, int VAL = 0) {nxt = NXT; to = TO; val = VAL;}
}edge[MAXN << 1];
int n, k, totedge = 1, mxpt, p, q, l1, l2 = 0xcfcfcfcf;
int head[MAXN], dst[MAXN], fa[MAXN], frm[MAXN];
bool vis[MAXN];

inline void add(int x, int y, int v) {
  edge[++totedge] = EDGE(head[x], y, v);
  head[x] = totedge;
  return;
}
void dfs(int x) {
  vis[x] = true;
  for(int i = head[x]; i; i = edge[i].nxt) if(!vis[edge[i].to]) {
      int y = edge[i].to;
      dfs(y);
      l2 = max(l2, dst[x] + dst[y] + edge[i].val);
      dst[x] = max(dst[x], dst[y] + edge[i].val);
  }
  return;
}
inline void bfs(int bgn) {
  for(int i = 1; i <= n; ++i) dst[i] = 0x3f3f3f3f, fa[i] = 0, frm[i] = 0;
  queue<int> que;
  dst[bgn] = 0;
  frm[bgn] = 0;
  que.push(bgn);
  while(!que.empty()) {
    int x = que.front(); que.pop();
    if(dst[x] > dst[mxpt]) mxpt = x;
    for(int i = head[x]; i; i = edge[i].nxt) if(dst[edge[i].to] == 0x3f3f3f3f) {
      int y = edge[i].to;
      dst[y] = dst[x] + edge[i].val;
      frm[y] = i;
      fa[y] = x;
      que.push(y);
    }
  }
  return;
}

int main() {
  scanf("%d%d", &n, &k);
  register int xx, yy;
  for(int i = 1; i < n; ++i) {
    scanf("%d%d", &xx, &yy);
    add(xx, yy, 1); add(yy, xx, 1);
  }
  dst[0] = 0xcfcfcfcf;
  bfs(1); p = mxpt; mxpt = 0;
  bfs(p); q = mxpt;
  while(mxpt != p) {
    ++l1;
    edge[frm[mxpt]].val = edge[frm[mxpt] ^ 1].val = -1;
    mxpt = fa[mxpt];
  }
  if(k == 1) {
    printf("%d\n", (n << 1) - l1 - 1);
    return 0;
  }
  if(k == 2) {
    for(int i = 1; i <= n; ++i) dst[i] = 0;
    dfs(1);
    if(l2 < 0) l2 = 0;
    printf("%d\n", (n << 1) - l1 - l2);
  }
  return 0;
}

  

 

posted @ 2018-09-01 21:06  EvalonXing  阅读(131)  评论(0编辑  收藏  举报