【APIO2013】TASKSAUTHOR 提交答案\构造(数据构造)

明天就要wc比赛了,然而现在也只做了三道提答题,十分慌张 这是一道数据构造类型的提答题。 UOJ109 当今世界上各类程序设计竞赛层出不穷。而设计一场好比赛绝非易事,比如给题目设计测试数据就是一项挑战。一组好的测试数据需要对不同的程序有区分度:满足所有要求的程序自然应该得到满分,而那些貌似正确的程序则会在某些特殊数据上出错。 在本题中,你在比赛中的角色反转啦!作为一名久经百战的程序员,你将帮助 Happy Programmer Contest 的命题委员会设计这次比赛的测试数据。本次比赛命题委员会选择了两个图论问题,分为 $8$ 个子任务。委员会写了一些貌似可以解决这些子任务的代码。在给任务设计数据的时候,命题委员会期望其中的一些源程序能够得到满分,而另外的一些则只能得到 $0$ 分或者少许的部分分。现在你将会获得这些源程序(C, C++, Pascal 版本)。对于每个子任务,你需要去产生一组数据 $X$ 使得它能将该任务给定的 $2$ 种源程序 $A$ 和 $B$ 区分开来。更具体地说,生成的数据必须满足如下两个条件:
  1. 输入 $X$ 对于源程序 $A$ 一定不会出现超出时间限制(TLE)的问题。
  2. 输入 $X$ 一定会导致源程序 $B$ 产生超出时间限制的问题。
此外,命题委员喜欢较小规模的测试数据,希望测试数据最好能够包含不超过 $T$ 个整数。 本题中只关心源程序 $A$ 和 $B$ 是否超时,不关心是否结果正确。 命题委员会选择了单源最短路(SSSP)以及一个被称之为神秘问题(Mystery)的两个图论问题来作为比赛的题目。我们将命题委员会完成的伪代码列在了附录中,而具体的 C、C++ 和 Pascal 源程序见源程序下载。

子任务

参见下表。表中每一行描述了一个子任务。其中前六个子任务与单源最短路相关,子任务 7,8 与神秘问题相关。每个任务所占分数见下表。
测试点编号 分数 $S$ 目标 $T$ 问题 源程序 $A$ 源程序 $B$
13$107$SSSPModifiedDijkstraFloydWarshall
27$2222$SSSPFloydWarshallOptimizedBellmanFord
38$105$SSSPOptimizedBellmanFordFloydWarshall
417$157$SSSPFloydWarshallModifiedDijkstra
510$1016$SSSPModifiedDijkstraOptimizedBellmanFord
619$143$SSSPOptimizedBellmanFordModifiedDijkstra
711$3004$MysteryGamble1RecursiveBacktracking
825$3004$MysteryRecursiveBacktrackingGamble2
对于每个子任务,你的程序给出的输入 $X$ 需要能够将源程序 $A$ 和 $B$ 区分开来,这有这样你才能够得到相应的分数。具体说来,你的分数将由输入 $X$ 中数的个数决定。假设 $X$ 中包含了 $F$ 个整数,子任务的满分为 $S,T$ 是该任务的目标大小,则该测试点的分数将由下式给出: \begin{equation} \lfloor 0.5 + S \times \min\{T / F, 1\} \rfloor \end{equation} 也就是说,如果你的测试数据 $X$ 中含有不超过 $T$ 个整数,则你将得到该任务的全部得分。 你需要把你的 $8$ 个测试数据命名为 tasksauthor1.out ~ tasksauthor8.out。对于每个子任务 $X$,评测系统将根据如下步骤来确定你将会得到多少分:
  1. 如果未提交数据,则不得分
  2. 若数据不满足输入格式要求,则不得分
  3. 对源程序 $A$ 运行输入,若发生超时现象,则不得分
  4. 对源程序 $B$ 运行输入,若发生超时现象,则按照前文所述的公式给出该测试点的分数。
题目提供的所有源代码均会维护一个计数器来统计程序的操作次数。在源程序的运行过程中,当该计数器超过了 $10^6$ 次时,那么我们认为程序运行超时。

问题 1:单源最短路(SSSP)

给定一个带权有向图 $G$,以及 $G$ 中的两个节点 $s$ 与 $t$,令 $p(s, t)$ 为 $G$ 中从 $s$ 至 $t$ 的最短路长度。如果 $s$ 与 $t$ 不连通,则认为 $p(s, t)=10^9$。在本题中,输入为图 $G$ 以及 $Q$ 个询问 $(s_1, t_1), (s_2, t_2), \dots, (s_Q, t_Q)$。输出则是对这 $Q$ 个询问的相应输出 $p(s_1, t_1), p(s_2 , t_2), \dots, p(s_Q, t_Q)$。

问题 1 输入输出格式

输入数据包含两部分,其中第一部分使用邻接表来描述带权有向图 $G$。第二部分则描述对 $G$ 的最短路径的查询。 数据第一部分的第一行包含一个整数 $V$,表示 $G$ 中点的个数,所有点的编号为 $0, 1, \dots, V – 1$。 接下来 $V$ 行,每行描述一个点的所有边。行中的第一个整数 $n_i$ 描述了节点 $i$ 的出边数量,接下来有 $n_i$ 个整数对 $(j, w)$ 表示有一条从 $i$ 到 $j$,边权为 $w$ 的边。 数据第二部分的第一行包含一个整数 $Q$,表示询问的组数。 接下来 $Q$ 行,第 $k$ 行包含两个整数 $s_k, t_k$,为该询问对应的起点与终点位置。 同一行中任意两个相邻的整数均需要至少一个空格将他们分开。除此之外,数据还需满足如下条件:
  1. $0 < V \leq 300$
  2. $n_i$ 是一个非负整数
  3. $0 \leq j < V$
  4. $\lvert w \rvert < 10^6$
  5. $0 \leq \sum_{i = 0}^{V−1} n_i \leq 5000$
  6. $0 < Q ≤ 10$
  7. $0 \leq s_k < V, 0 \leq t_k < V$
  8. 所有询问中的起点 $s_k$ 都不能达到任何一个负权圈。
程序将会输出 $Q$ 行,每行一个整数,表示对应的 $p(s_k , t_k)$。而在输出的最后,所有提供的程序都会给出计数器对此输入的数值。

问题 1 样例

input

3
2 1 4 2 1
0
1 1 2
2
0 1
1 0

output

3
1000000000
The value of counter is: 5

问题 2:神秘问题

给定一个包含 $V$ 个节点 $E$ 条边的无向图 $G$,要求将所有的节点进行编号(编号范围为 $[0, X-1]$),使得所有直接相连的节点均有不同的编号。找出符合题意的最小的 $X$。

问题 2 输入输出格式

输入数据的第一行包含两个整数 $V$ 和 $E$。 接下来 $E$ 行,每行两个整数 $a, b$,表示 $a$ 与 $b$ 在 $G$ 中直接相连。此外,输入数据应满足如下限制条件:
  1. $70 < V < 1000$
  2. $1500 < E < 10^6$
  3. 对于所有的边 $(a, b)$,有 $a \neq b, 0 \leq a < V, 0 \leq b < V$,不会重复描述一条边。
程序将在第一行输出 $X$,即最小的编号范围,接下来在第二行中给出 $V$ 个整 数,依次描述节点 $0$ 至 $V – 1$ 的编号。在输出的最后,所有提供的程序都会给出计数器对此输入的数值。

问题 2 样例

input

4 5
0 1
0 2
0 3
1 2
2 3

output

3
0 1 2 1
The value of counter is: 18

大致就是让你构造数据,卡一个放一个。比较恶心的是数据要求的边界有点死,很容易挂掉。

构造最短路

T1 , T3

卡floyd放dijksta或bellmaxn-ford 发现floyd的cnt是严格n^3,构造点101个就可以了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>

using namespace std;


int main() {
    freopen("tasksauthor1.out","w",stdout);
    cout<<101<<endl;
    for(int i=0;i<101;i++) {
        cout<<0<<endl;//102
    }
    cout<<1<<endl;
    cout<<0<<' '<<1<<endl;
}

T2

卡bellman-ford,放floyd, floyd只要点不超100就放过去了,bellman-ford每次是边增广,如果我们倒序放一条链他就卡满了。再套上一些自环或重边就可以了。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
int V=100;
int cnt,JJ=9;
vector<int>ve[1005];
int main() {
    freopen("tasksauthor2.out","w",stdout);
    cout<<V<<endl;
    cnt+=1;
    for(int i=V-1;i>=0;i--) {
        if(i!=0) ve[i].push_back(i-1);
        for(int j=1;j<=JJ;j++) ve[i].push_back(i);
        if(i==0) {
            for(int j=1;j<=50;j++) ve[i].push_back(i);
        }
    }
    for(int i=0;i<V;i++) {
        cout<<ve[i].size()<<' ';cnt++;
        for(int j=0;j<ve[i].size();j++) cout<<ve[i][j]<<' '<<1<<endl;
        cnt+=2*ve[i].size();
    }
    cout<<10<<endl;;cnt++;
    for(int i=1;i<=10;i++) {
        cout<<V-1<<' '<<0<<endl;
        cnt+=2;
    }
    cerr<<cnt<<endl;
}

T4

放floyd,卡dijkstra dijkstra居然计算cnt是按照入队次数而没有算上边,也没有算上堆的时间orz...正常的图最多入队n+m次。我们想到了dijkstra无法处理的负权图,稍微诱导一下,构造类似三角形和spfa一样的卡法就能使他达到指数了。
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>

using namespace std;

int V=33,cnt;
int nowa,nowb;
int main() {
    freopen("tasksauthor4.out","w",stdout);
    cout<<V<<endl;cnt++;
    nowa = (1<<16); nowb = (1<<15);
    for(int i=0;i<V-1;i++) {
        if(i&1) printf("1 %d %d\n",i+1,-nowa),nowa/=2,cnt+=3;
        else printf("2 %d %d %d 0\n",i+1,nowb,i+2),nowb/=2,cnt+=5;
    }
    cout<<0<<endl; cnt++;
    int Q = 10;
    cout<<Q<<endl;; cnt++;
    while(Q--) {
        cout<<"0 32"<<endl;
        cnt+=2;
    }
    cerr<<cnt<<endl;
}

T5

卡bellman放dij 一样的卡法。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
int V=300;
int cnt,JJ=0;
vector<int>ve[1005];
int main() {
    freopen("tasksauthor5.out","w",stdout);
    cout<<V<<endl;
    cnt+=1;
    for(int i=V-1;i>=0;i--) {
        if(i!=0) ve[i].push_back(i-1);
        for(int j=1;j<=JJ;j++) ve[i].push_back(i);

        if(i==0) {
            for(int j=1;j<=48;j++) ve[i].push_back(i);
        }

    }
    for(int i=0;i<V;i++) {
        cout<<ve[i].size()<<' ';cnt++;
        for(int j=0;j<ve[i].size();j++) cout<<ve[i][j]<<' '<<1<<endl;
        cnt+=2*ve[i].size();
    }
    cout<<10<<endl;;cnt++;
    for(int i=1;i<=10;i++) {
        cout<<V-1<<' '<<0<<endl;
        cnt+=2;
    }
    cerr<<cnt<<endl;
}

T6

卡dij放bellmaxn 一样的卡法
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>

using namespace std;

int V=33,cnt;
int nowa,nowb;
int main() {
    freopen("tasksauthor6.out","w",stdout);
    cout<<V<<endl;cnt++;
    nowa = (1<<16); nowb = (1<<15);
    for(int i=0;i<V-1;i++) {
        if(i&1) printf("1 %d %d\n",i+1,-nowa),nowa/=2,cnt+=3;
        else printf("2 %d %d %d 0\n",i+1,nowb,i+2),nowb/=2,cnt+=5;
    }
    cout<<0<<endl; cnt++;
    int Q = 6;
    cout<<Q<<endl;; cnt++;
    while(Q--) {
        cout<<"0 32"<<endl;
        cnt+=2;
    }
    cerr<<cnt<<endl;
}

图染色

给一张图染色,找出最少颜色和方案保证一边两头颜色不同 只给了一份暴力染色程序。

T7

卡掉暴力 另一份程序是保证V种颜色直接输入完就过那种。卡准范围,直接构造一个半完全(原本要完全图但是有部分由于限制不建立出来)就可以了。
#include<iostream>
#include<cstdio>

using namespace std;
int V=71;
int cnt=2;
int main() {
    freopen("tasksauthor7.out","w",stdout);
    cout<<V<<' '<<1501<<endl;
    for(int i=0;i<V;i++) {
        for(int j=i+1;j<V;j++) {
            if(cnt==3004) continue;
            printf("%d %d\n",i,j);
            cnt+=2;
        }
    }
//    cerr<<V*(V-1)/2<<' '<<cnt;
}

T8

放暴力过去。 那么我们最好构造二分图就可以过去。那么直接奇向偶连,偶向奇连。由于一些限制,可能要稍做微调
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int V=78,E=1501;
int  cnt=2;
int main() {
    freopen("tasksauthor8.out","w",stdout);
    cout<<V<<" "<<E<<endl;
    for(int i=0;i<V-1;i++) {
        for(int j=i+1;j<V-1;j+=2) {
            cout<<i<<' '<<j<<endl;
        }
    }
    for(int i=1;i<=19;i++) {
        int x = (i-1)*2;
        cout<<x<<' '<<V-1<<endl;
    }
}
OWO,其实这道题本质应该是一道构造题
posted @ 2019-01-28 13:31  Newuser233  阅读(9)  评论(0)    收藏  举报