P3640 [APIO2013] 出题人 题解

原题链接

题意简述

提交答案题

你是一个出题人,有两个问题:

  • \(\text{SSSP}\):最短路

    点数不超过 \(300\),边权的绝对值小于 \(10^6\),总边数不超过 \(5000\),询问不超过 \(10\)

  • \(\text{Mystery}\):无向图染色

    点数小于 \(1000\) 且大于 \(70\),边数小于 \(10^6\) 且大于 \(1500\)

对于每个测试点:

给定源程序 \(A\) 和源程序 \(B\),需要造一组数据使得源程序 \(A\) 能跑过而源程序 \(B\) 不行且整数个数小于 \(T\)

一个程序是否能跑过一个问题不在意其答案是否正确,仅关心是否超时

测试点编号 \(T\) 问题 源程序 \(A\) 源程序 \(B\)
\(1\) \(107\) \(\text{SSSP}\) \(\text{ModifiedDijkstra}\) \(\text{FloydWarshall}\)
\(2\) \(2222\) \(\text{SSSP}\) \(\text{FloydWarshall}\) \(\text{OptimizedBellmanFord}\)
\(3\) \(105\) \(\text{SSSP}\) \(\text{OptimizedBellmanFord}\) \(\text{FloydWarshall}\)
\(4\) \(157\) \(\text{SSSP}\) \(\text{FloydWarshall}\) \(\text{ModifiedDijkstra}\)
\(5\) \(1016\) \(\text{SSSP}\) \(\text{ModifiedDijkstra}\) \(\text{OptimizedBellmanFord}\)
\(6\) \(143\) \(\text{SSSP}\) \(\text{OptimizedBellmanFord}\) \(\text{ModifiedDijkstra}\)
\(7\) \(3004\) \(\text{Mystery}\) \(\text{Gamble1}\) \(\text{RecursiveBacktracking}\)
\(8\) \(3004\) \(\text{Mystery}\) \(\text{RecursiveBacktracking}\) \(\text{Gamble2}\)

解法

首先我们明确一下每个代码算法及其时间复杂度:

代码名称 算法 时间复杂度
\(\text{FloydWarshall}\) \(\text{Floyd}\) \(\Theta(V^3+Q)\)
\(\text{OptimizedBellmanFord}\) \(\text{Bellman-Ford}\) \(\Theta(VEQ)\)
\(\text{ModifiedDijkstra}\) \(\text{Dijkstra}\) \(\Theta(QE+Q\log_2 E)\)
\(\text{Gamble1}\) \(\text{never TLE}\) \(\Theta(1)\)
\(\text{Gamble2}\) \(\text{always TLE}\) \(\Theta(\infty)\)
\(\text{RecursiveBacktracking}\) \(\text{Backtrack}\) 懒得算,挺大的

注意这里 \(V\) 是点数,\(E\) 是边数,\(Q\) 是询问数

point 1

目的

\(\text{Floyd}\)

做法

构造 \(V=101\)

显然因为要输出 \(101\) 个点,至少就要 \(101\) 个数。所以我们把每一个点的出度都描述为 \(0\)

最后询问只需要一个,问题随你,这里使用 0 99

共计 \(105\) 个数

code

为了防止抄袭,所有代码均无 print 函数,望周知。

#include<bits/stdc++.h>
using namespace std;
const int maxv=305,maxq=15;
int V,E,Q;
vector<pair<int,int>> G[maxv];
int s[maxq],t[maxq];
int main(){
    V=101;
    Q=1;
    for(int i=1;i<=Q;i++){
        s[i]=0;
        t[i]=99;
    }
    print();
    return 0;
}

point 2

目的

\(\text{Bellman-Ford}(\Theta(VEQ))\)

且需要让 \(\text{Floyd}(\Theta(V^3))\)

做法

这个点其实看着难卡,实际上不难。

就是要让 \(V\) 恰好为 \(100\)(让 \(\text{Floyd}\) 过),便尽量多加,在 \(10\) 次询问就行了(主要是询问上限为 \(10\)

可以去 oi-wiki 上看一下这个算法的原理,就可以发现如果我们在一条链上狂加自环,这个算法就会被卡飞。

最后的询问要按照你建边的方向,否则卡不掉。

最后算一下边能放多少边:\(E_{\max}=\frac{2222(T)-1(V)-100(出度)-1(Q)-2\times10(询问)}{2(点编号及权值)}-99(链)=951\)

code

#include<bits/stdc++.h>
using namespace std;
const int maxv=305,maxq=15;
int V,E,Q;
vector<pair<int,int>> G[maxv];
int s[maxq],t[maxq];
int main(){
    V=100;
    E=951;
    for(int i=0;i<V;i++){
        int k=min(E,10);
        E-=k;
        if(i!=0){
            G[i].push_back(make_pair(i-1,rand()%114514+114514));
        }
        for(int j=1;j<=k;j++){
            G[i].push_back(make_pair(i,rand()%114514+114514));
        }
    }
    Q=10;
    for(int i=1;i<=Q;i++){//由于我是反着建的链,所以输出99 0
        s[i]=99;
        t[i]=0;
    }
    print();
    return 0;
}

point 3

同 point 1,这里不加赘述

point 4

目的

\(\text{Dijkstra}(\Theta(QE+Q\log_2 E))\)

且需要让 \(\text{Floyd}(\Theta(V^3))\)

做法

不了解 \(\text{Dijkstra}\) 的可以去 oi-wiki 上看看。我们可以看到:

在稀疏图中,\(m=\Theta(n)\),使用二叉堆实现的 Dijkstra 算法较 Bellman-Ford 算法具有较大的效率优势;而在稠密图中,\(m=\Theta(n^2)\),这时候使用暴力做法较二叉堆实现更优。

但是这个题没有给我们建稠密图的机会,我们不能这么卡。

于是,我们只能从 \(\text{Dijkstra}\) 松弛操作的漏洞来卡。

就如 Abzilurtahv 大佬所说的那样,只要构造出如下图的情况, \(\text{Dijkstra}\) 就去世了。

那么为什么?这里是很多题解没有说到的。

我们来简单的证明一下:


我们截取一个片段:

那么整个算法会按如下步骤进行:

当前点 当前操作 当前队列
\(0\) \(pop(0),push(1),push(2)\) \(\{2,1\}\)
\(2\) \(pop(2),push(3),push(4)\) \(\{4,3,1\}\)
\(4\) \(pop(4),push(5),push(6)\) \(\{6,5,3,1\}\)
\(6\) \(pop(6)\) \(\{5,3,1\}\)
\(5\) \(pop(5),push(6)\) \(\{6,3,1\}\)
\(6\) \(pop(6)\) \(\{3,1\}\)
\(3\) \(pop(3),push(4)\) \(\{4,1\}\)
\(4\) \(pop(4),push(5),push(6)\) \(\{6,5,1\}\)
\(6\) \(pop(6)\) \(\{5,1\}\)
\(5\) \(pop(5),push(6)\) \(\{6,1\}\)
\(6\) \(pop(6)\) \(\{1\}\)
\(1\) \(pop(1),push(2)\) \(\{2\}\)
\(2\) \(pop(2),push(3),push(4)\) \(\{4\}\)
\(4\) \(pop(4),push(5),push(6)\) \(\{6,5\}\)
\(6\) \(pop(6)\) \(\{5\}\)
\(5\) \(pop(5),push(6)\) \(\{6\}\)
\(6\) \(pop(6)\) \(\text{empty}\)

而发现节点 \(V\) 外每一个节点都拥有这个性质,所以时间复杂度为 \(\Theta(2^{\frac{V-1}{2}})\),忽略常数相当于 \(\Theta(2^V)\)

证毕。


所以对于这道题,构造一组 \(V=33,Q=10\) 的数据即可

code

#include<bits/stdc++.h>
using namespace std;
const int maxv=305,maxq=15;
int V,E,Q;
vector<pair<int,int>> G[maxv];
int s[maxq],t[maxq];
int main(){
    V=33;
    int dis=200000;
    for(int i=0;i<V;i++){
        if(i==V-1){
            continue;
        }
        if(i&1){
            dis/=2;
            G[i].push_back(make_pair(i+1,-dis));
        }
        else{
            G[i].push_back(make_pair(i+1,-1));
            G[i].push_back(make_pair(i+2,-2));
        }
    }
    Q=10;
    for(int i=1;i<=Q;i++){
        s[i]=0;
        t[i]=32;
    }
    print();
    return 0;
}

point 5

目的

卡掉 \(\text{Bellman-Ford}(\Theta(VEQ))\)

且需要让 \(\text{Dijkstra}(\Theta(QE+Q\log_2 E))\)

做法

同 point 2,我们需要在一条链上狂加自环。

另外我们发现其实 \(\text{Dijkstra}\) 的时间复杂度是不会受 \(V\) 的影响的,然后我们就让 \(V\) 尽量大,也就是 \(V=300\)\(Q\) 也设为 \(10\)

简单计算发现 \(E_{\max}=\frac{1016-1(V)-300(出度)-1(Q)-2\times10(询问)}{2}-299(链)=48\)

code

#include<bits/stdc++.h>
using namespace std;
const int maxv=305,maxq=15;
int V,E,Q;
vector<pair<int,int>> G[maxv];
int s[maxq],t[maxq];
int main(){
    V=300;
    E=48;
    for(int i=0;i<V;i++){
        int k=min(E,10);
        E-=k;
        if(i!=0){
            G[i].push_back(make_pair(i-1,rand()%114514+114514));
        }
        for(int j=1;j<=k;j++){
            G[i].push_back(make_pair(i,rand()%114514+114514));
        }
    }
    Q=10;
    for(int i=1;i<=Q;i++){
        s[i]=299;
        t[i]=0;
    }
    print();
    return 0;
}

point 6

同 point 4,只是 \(Q\) 过大导致超出限制,\(Q\) 改为 \(6\) 即可。

point 7

目的

卡掉 \(\text{Backtrack}\),让 \(\text{Gamble1}\) 过。

做法

首先明确一点,这个 \(\text{Gamble1}\) 是个永远不会被卡掉的程序,所以你只需要管 \(\text{Backtrack}\)

而你其实可以很显然的发现 \(\text{Backtrack}\) 的时间复杂度大无比,所以可以直接随机一组数据。

点数是随意的,而 \(E_{\max}=\frac{3004-1(V)-1(E)}{2}=1501\)

所以我们让 \(V=999,E=1501\)

code

#include<bits/stdc++.h>
using namespace std;
const int maxv=1005;
int V,E;
bool G[maxv][maxv];
vector<pair<int,int>> e;
int main(){
    V=999;
    E=1501;
    for(int i=1;i<=E;i++){
        int x=rand()%V;
        int y=rand()%V;
        while(G[x][y]||G[y][x]||x==y){
            x=rand()%V;
            y=rand()%V;
        }
        G[x][y]=G[y][x]=1;
        e.push_back(make_pair(x,y));
    }
    reverse(e.begin(),e.end());
    print();
    return 0;
}

point 8

目的

卡掉 \(\text{Gamble2}\)\(\text{Backtrack}\) 让过。

做法

首先明确以下, \(\text{Gamble2}\) 是一个永远都过不去的算法,所以不用管。

而这个 \(\text{Backtrack}\) 是暴力染色,所以我们良心一点,让它能够一遍染完。

所以我们再造数据的时候先提前染好色,这样就能过了。

注意这里 \(V\) 不能过大,否则会把 \(\text{Backtrack}\) 卡掉。

code

#include<bits/stdc++.h>
using namespace std;
const int maxv=1005;
int V,E;
int col[maxv];
bool G[maxv][maxv];
vector<pair<int,int>> e;
int main(){
    V=101;
    E=1501;
    for(int i=1;i<=V;i++){
        col[i]=rand()%2;
    }
    for(int i=1;i<=E;i++){
        int x=rand()%V;
        int y=rand()%V;
        while(col[x]==col[y]||G[x][y]||G[y][x]||x==y){
            x=rand()%V;
            y=rand()%V;
        }
        G[x][y]=G[y][x]=1;
        e.push_back(make_pair(x,y));
    }
    reverse(e.begin(),e.end());
    print();
    return 0;
}

完结撒花!

后记

这篇题解我是边写题边写的

一共提交 \(26\) 次才获得 AC

下面是通过各个测试点的时间:

测试点编号 时间 失败次数
\(1\) \(\texttt{2022-07-27 10:22:54}\) \(0\)
\(2\) \(\texttt{2022-07-27 11:16:21}\) \(0\)
\(3\) \(\texttt{2022-07-27 11:14:03}\) \(0\)
\(4\) \(\texttt{2022-07-27 12:25:53}\) \(10\)
\(5\) \(\texttt{2022-07-27 14:12:41}\) \(0\)
\(6\) \(\texttt{2022-07-27 14:18:34}\) \(3\)
\(7\) \(\texttt{2022-07-27 14:37:29}\) \(1\)
\(8\) \(\texttt{2022-07-27 14:51:48}\) \(4\)

历时共 \(16134\text{s}\),合计 \(268.9\text{min}\),相当于约 \(4.18167\text{h}\)

全文共 \(11664\) 个字。

码字不易,点个赞再走吧~

posted @ 2022-07-27 20:17  little_cindy  阅读(137)  评论(0)    收藏  举报