其他分享
首页 > 其他分享> > P3640 [APIO2013]出题人 题解

P3640 [APIO2013]出题人 题解

作者:互联网

一道神仙图论题,很考验各位对最短路以及染色问题的理解。

首先说明 1 点,实质上神秘问题就是经典的染色问题。


这里首先简要分析一下给出的几个代码的特色:


Subtask 1:放 Dijkstra,卡掉 Floyd,\(T=107\)。

Subtask 3:放 Bellman-Ford,卡掉 Floyd,\(T=105\)。

因为计数器大于 1000000 时就会 TLE,因此我们只需要构建一组有 101 个点,但是没有任何边的图就可以了。

询问只需要询问 1 组,随便哪两个点都行。

\(T=1+101+1+2=105\),能够通过这两个点。


Subtask 2:放 Floyd,卡掉 Bellman-Ford,\(T=2222\)。

Subtask 5:放 Dijkstra,卡掉 Bellmam-Ford,\(T=1016\)。

这两组数据需要卡掉 BF。

但是前面已经分析过,只需要来一个简单的负环,然后边数尽量大就好。

对于 Subtask 2:点数 \(V=100\),然后搞一个负环,边数尽量大即可。

对于 Subtask 5:由于 dij 在负权图上容易被卡,因此我们需要一条 \(0->1\) 的单向边,然后 \(2,3,4\) 构成负环,剩下的所有点随便连边,边数尽量大就好。注意 \(0,1\) 这两个点必须是单独构成一个连通块。


Subtask 4:放 Floyd,卡掉 Dijkstra,\(T=157\)。

Subtask 6:放 Bellman-Ford,卡掉 Dijkstra,\(T=143\)。

本题最难的两个点,需要好好研究一下题目中的 dij 代码。

P.S. 如果你是非 C++ 党或者懒得研究代码,请直接跳到两份代码后面的分析。

首先一般的 Dijkstra 代码是这样的:(我写的一份)

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    memset(book, 0, sizeof(book));
    priority_queue <pri> q; q.push((pri){1, 0}); dis[1] = 0;
    while (!q.empty())
    {
        pri x = q.top(); q.pop();
        if (book[x.now]) continue ;
        book[x.now] = 1;
        for (int i = Head[x.now]; i; i = Edge[i].Next)
        {
            int u = Edge[i].to;
            if (dis[u] > dis[x.now] + Edge[i].val)
            {
                dis[u] = dis[x.now] + Edge[i].val;
                if (!book[u]) q.push((pri){u, dis[u]});
            }
        }
    }
}

题目给的是这样的:

while (Q--) {
    scanf("%d %d", &s, &t);

    vector<int> dist(V, INF);
    dist[s] = 0;
    priority_queue< IntPair, vector<IntPair>, greater<IntPair> > pq;
    pq.push(IntPair(0, s));
    while (!pq.empty()) {
      counter++;
      if (counter > 1000000) {
        printf("TLE because iteration counter > 1000000\n");
        return 1;
      }

      IntPair front = pq.top(); pq.pop();
      d = front.first; u = front.second;
      if (d == dist[u]) {
        for (j = 0; j < (int)AdjList[u].size(); j++) {
          IntPair v = AdjList[u][j];
          if (dist[u] + v.second < dist[v.first]) {
            dist[v.first] = dist[u] + v.second;
            pq.push(IntPair(dist[v.first], v.first));
          }
        }
      }
    }

    printf("%d\n", dist[t]);
  }

大致特色是这样的:

那么怎么卡呢?看下图:

在这里插入图片描述

我们从 0 开始找,走 \(0 \to 2 \to 4\),走不了了,返回。

然后 \(2 \to 3 \to 4\),发现能够更新最短路。

在更新完 \(2,3,4\) 之后算法回退到 1,然后走 \(0 \to 1 \to 2\),会发现能够更新 \(2\) 的最短路。

然后又重复上面的过程,傻傻的 Dijkstra 就被我们卡掉了。

总结一下就是:

这样做的原理就是在一个三元环中,\(V\) 将会走到 \(V+2\) 两次:\(V \to V+2\),\(V \to V+1 \to V+2\),而且只要 \(V\) 被走到就会重复上述过程。

当然你也可以不这么构建,反正只要原理相同就可以了。

于是我们只要构建足够多的三元环,就可以顺利的将 Dijkstra 卡成指数级别。

Bellman-Ford 呢?

反正这玩意没有负环,BF 不是随便跑qwq

对于 Floyd,你根本没法构造点数大于 100 的图(\(T\) 很小),而且你也没必要构造,只需要卡掉 Dijkstra 就好了~

这两个 Subtask 有两个需要注意的地方:


Subtask 7:卡掉 RecursiveBacktracking。

Subtask 8:放 RecursiveBacktracking。

暴力染色的代码太好卡了,只要构造一个近似完全图就可以将这玩意卡掉,随机都行。

前提:不是非酋。

放 RB 过也很简单,因为染色问题的暴力代码在二分图上表现良好,于是我们只需要构造一个二分图即可。

但是这两个 Subtask 有最小限制 \(V \geq 71,E \geq 1501\)。

计算一下:\(1 + 1501 \times 2=3003\),要求只能有 3004 个整数。

看样子这个是要到极限了。

然而暴力染色代码复杂度在二分图上跟点数没有太大关系,而对于 Subtask 7 反正你是要卡掉它。

于是这两个点 \(V=100\)(对于 Subtask 8 可以更大),然后 \(E=1501\)。

Subtask 7 随便构造近似的完全图,Subtask 8 构造二分图即可。


Summary:

这道题让我们见识到了毒瘤出题人卡你最短路的若干种方法各种最短路算法(Floyd,优化 Bellman-Ford,SLF 优化 SPFA)的优缺点,是一道非常好的图论题,可以加深对最短路的了解与掌握。

标签:dist,卡掉,题解,Dijkstra,P3640,Ford,Subtask,APIO2013,dis
来源: https://www.cnblogs.com/Plozia/p/16155983.html