[解题报告] AGC016E Poor Turkeys

传送🚪

题意

\(n\) 只火鸡 \((n \le 400)\)\(m\) 个人,每个人都有两个指定的火鸡 \(x_i, y_i\),这 \(m\) 个人会依次进行以下操作。

  • \(x_i, y_i\) 都存活,则随机杀死其中的一只。
  • 若只有一个存活,则杀死存活的那只。
  • 若都死亡,则不进行操作。

问有多少对 \((i,j)\) 满足 \(i\)\(j\) 有可能一起存活到最后时刻。

思路

这是一道妙妙题。

我的解法

假设 \(x\) 最后要存活下来,看看会影响到哪些点。

显然,对于 \((x,i)\)\(i\) 一定得死在 \((x,i)\) 这条边被操作的时刻。

再考虑 \(i\) 对其他点的影响,发现它对其他点的影响是和它的死亡时刻有关的。

所以我们设 \(d[i]\) 为点 \(i\) 的死亡时刻,\(t(x,i)\) 为边 \((x,i)\) 被操作的时刻。

分类讨论一下,

  • \(d[x] > t(x, i)\),则 \(d[i] = t(x,i)\)
  • \(d[x] \le t(x,i)\),则 \(d[i] \le t(x,i)\)

这样跑一遍后,若出现了矛盾,则表示 \(x\) 不能存活到最后时刻,否则它可以和那些 \(d[i]\) 没有限制的点一起存活到最后。

具体写法的话,我们 \(bfs\),只走第一类边,然后以这些点为起点去跑 \(Dijkstra\),找出 \(lim[i]\),看是否有矛盾即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>

#define mkp make_pair
#define fi first
#define se second

using namespace std;

const int _ = 4e2 + 7;
const int __ = 2e5 + 7;

int n, m, d[_], lim[_];
int lst[_], nxt[__], to[__], t[__], tot = 1;

void Add(int x, int y, int tm) {
  nxt[++tot] = lst[x]; to[tot] = y; t[tot] = tm; lst[x] = tot;
  nxt[++tot] = lst[y]; to[tot] = x; t[tot] = tm; lst[y] = tot;
}

void Init() {
  cin >> n >> m;
  for (int i = 1, x, y; i <= m; ++i) {
    scanf("%d%d", &x, &y);
    Add(x, y, i);
  }
}

int ans;
priority_queue<pair<int, int> >h;
queue<int> q;
bool b[_], dd[_], con[_][_], vis[__];

void Bfs(int x) {
  memset(d, 0, sizeof d);
  memset(lim, 0x3f, sizeof lim);
  memset(b, 0, sizeof b);
  memset(vis, 0, sizeof vis);
  while (!q.empty()) q.pop();
  while (!h.empty()) h.pop();

  d[x] = lim[x] = m + 1, q.push(x);
  while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int i = lst[u]; i; i = nxt[i]) {
      int v = to[i];
      if (d[u] <= t[i] or vis[i]) continue;
      if (d[v]) { dd[x] = 1; return; }
      vis[i] = vis[i ^ 1] = 1;
      d[v] = lim[v] = t[i];
      h.push(mkp(-lim[v] ,v));
      q.push(v);
    }
  }

  while (!h.empty()) {
    int u = h.top().se; h.pop();
    if (b[u]) continue;
    b[u] = 1;
    for (int i = lst[u]; i; i = nxt[i]) {
      if (lim[u] > t[i] or vis[i]) continue;
      vis[i] = vis[i ^ 1] = 1;
      int v = to[i];
      if (lim[v] > t[i]) lim[v] = t[i], h.push(mkp(-lim[v], v));
    }
  }
  for (int i = 1; i <= n; ++i) {
    if (d[i] > lim[i]) { dd[x] = 1; return; }
    if (lim[i] == lim[0]) con[x][i] = 1;
  }
}

void Run() {
  for (int i = 1; i <= n; ++i) Bfs(i);
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j) 
      if (!dd[i] and !dd[j] and con[i][j]) ++ans;
  cout << ans / 2 << endl;
}

int main() {
  Init();
  Run();
  return 0;
}

妙妙解法

如果要让 \(x\) 存活到最后的话,那么对于所有连向它的边 \((x,i,t)\)\(i\) 都必须在 \(t\) 时刻死去,也就是说,\(i\)\(t\) 时刻前必须得活着。

那么我们维护一个「保护集合」,该集合初始时只有 \(x\) 一个点。

按时间顺序从后往前考虑所有边 \((i,j)\)

  • \(i,j\) 中有一个点在「保护集合」中,则把另一个点也加入「保护集合」,因为它不能在之间就被杀死了;
  • \(i,j\) 两个点都在「保护集合」,则不进行任何操作。
  • \(i,j\) 两个点都在「保护集合」中,那么 \(x\) 必死,因为 \(i,j\) 中必须有一个点在该时刻死,但它们都在之前被加入了「保护集合」,所以它们都不能被杀死,产生了矛盾,所以 \(x\) 不能存活到最后。

判断 \(x,y\) 是否能一起存活到最后,只需判断它们的「保护集合」是否有交集,若有,则不行。

简单证明一下。

\(x\)\(y\) 的「保护集合」有交集,则一定存在一个点 \(i\),把它加进 \(x,y\) 集合的两个点 \(u,v\) 不同时存在 \(x,y\) 的集合中,否则就能得到 \(x=y\)

那么 \(i\) 既要在 \((u,i)\) 被指定的时候去死,又要在 \((v,i)\) 被指定的时候去死,而一个点不能死两次,产生了矛盾,所以 \(x,y\) 不能一起存活到最后。

判断集合是否有交直接枚举就好了,复杂度为 \(O(nm + n^3)\)

#include <cstdio>
#include <iostream>

#define mkp make_pair
#define fi first
#define se second

using namespace std;

const int _ = 4e2 + 7;
const int __ = 1e5 + 7;

int n, m, ans;
bool dd[_], pro[_][_];
pair<int, int> e[__];

int main() {
  cin >> n >> m;
  for (int i = 1; i <= m; ++i) scanf("%d%d", &e[i].fi, &e[i].se);

  for (int x = 1; x <= n; ++x) {
    pro[x][x] = 1;
    for (int i = m; i >= 1; --i)
      if (pro[x][e[i].fi] and pro[x][e[i].se]) { dd[x] = 1; break; }
      else if (pro[x][e[i].fi]) pro[x][e[i].se] = 1;
      else if (pro[x][e[i].se]) pro[x][e[i].fi] = 1;
  }

  for (int i = 1; i <= n; ++i)
    for (int j = i + 1; j <= n; ++j) {
      if (dd[i] or dd[j]) continue;
      bool flag = 1;
      for (int k = 1; k <= n; ++k)
        if (pro[i][k] and pro[j][k]) { flag = 0; break; }
      if (flag) ++ans;
    }
  
  cout << ans << endl;
  return 0;
}
posted @ 2020-11-22 15:23  BruceW  阅读(104)  评论(0编辑  收藏  举报