CCF-CSP 201512-4 送货 100分(解决RE)

  发现网上很多代码(包括一些号称100分的代码)都存在一些技术问题,致使提交之后出现RE并且只有80分,因此写了这篇笔记。

  针对这些代码的问题进行了简单的分类:

  1. .bss段溢出。静态分配了规模达到MxN=1e9 B≈1e3 MB的二维数用来存储邻接矩阵(题目所给出的是稠密图)。但最坑的问题是在DevC++/MinGW环境下,即使.bss段溢出仍然可以通过编译,没有任何错误或警告,程序也能在输入数据规模较小的情况下正常运行,这使得问题比较隐蔽。

  2. 递归调用过深,导致爆栈(栈空间溢出)。因为程序调用栈的动态性,这个问题同样非常隐蔽,关键是难以预估系统为我们分配了多大的栈空间。经过实测,在DevC++/MinGW环境下,系统为程序分配的堆栈空间大约只有128KiB,本题中dfs函数递归调用32496帧(1e4)后程序就崩溃了,递归的深度是在程序抛出异常后通过gdb观察到的。

  

  综上解决办法如下:

  针对问题1,改用邻接表或链式前向星存储图即可;

  针对问题2,有两种解决方案:一是改进算法,修改为显式用栈的DFS;二是手动扩栈,相比于传统的#pragma,本文将介绍一种通用性很强的内联汇编扩栈方法,关于该方法的原理在日后会详细介绍(如果有必要的话)。

 

先把原题贴在这里,方便查阅:

试题编号: 201512-4
试题名称: 送货
时间限制: 1.0s
内存限制: 256.0MB
问题描述: 问题描述   为了增加公司收入,F公司新开设了物流业务。由于F公司在业界的良好口碑,物流业务一开通即受到了消费者的欢迎,物流业务马上遍及了城市的每条街道。然而,F公司现在只安排了小明一个人负责所有街道的服务。
  任务虽然繁重,但是小明有足够的信心,他拿到了城市的地图,准备研究最好的方案。城市中有n个交叉路口,m条街道连接在这些交叉路口之间,每条街道的首尾都正好连接着一个交叉路口。除开街道的首尾端点,街道不会在其他位置与其他街道相交。每个交叉路口都至少连接着一条街道,有的交叉路口可能只连接着一条或两条街道。
  小明希望设计一个方案,从编号为1的交叉路口出发,每次必须沿街道去往街道另一端的路口,再从新的路口出发去往下一个路口,直到所有的街道都经过了正好一次。 输入格式   输入的第一行包含两个整数nm,表示交叉路口的数量和街道的数量,交叉路口从1到n标号。
  接下来m行,每行两个整数ab,表示和标号为a的交叉路口和标号为b的交叉路口之间有一条街道,街道是双向的,小明可以从任意一端走向另一端。两个路口之间最多有一条街道。 输出格式   如果小明可以经过每条街道正好一次,则输出一行包含m+1个整数p1p2p3, ..., pm+1,表示小明经过的路口的顺序,相邻两个整数之间用一个空格分隔。如果有多种方案满足条件,则输出字典序最小的一种方案,即首先保证p1最小,p1最小的前提下再保证p2最小,依此类推。
  如果不存在方案使得小明经过每条街道正好一次,则输出一个整数-1。 样例输入 4 5
1 2
1 3
1 4
2 4
3 4 样例输出 1 2 4 1 3 4 样例说明   城市的地图和小明的路径如下图所示。
CCF-CSP 201512-4 送货 100分(解决RE) 样例输入 4 6
1 2
1 3
1 4
2 4
3 4
2 3 样例输出 -1 样例说明   城市的地图如下图所示,不存在满足条件的路径。
CCF-CSP 201512-4 送货 100分(解决RE) 评测用例规模与约定   前30%的评测用例满足:1 ≤ n ≤ 10, n-1 ≤ m ≤ 20。
  前50%的评测用例满足:1 ≤ n ≤ 100, n-1 ≤ m ≤ 10000。
  所有评测用例满足:1 ≤ n ≤ 10000,n-1 ≤ m ≤ 100000。

 

 

本文仅在之前代码的基础上,手动调用malloc分配了一块虚存,然后通过内联汇编直接修改ESP/RSP栈指针寄存器,将栈空间指向新分配的虚拟内存,从而改变当前栈空间。当然事情还没有这么简单,当main函数返回时,程序实际上会回溯最原始的运行时库中去,原始的栈空间中仍有数据,但此时我们新指定的栈已经空了,如果不还原栈指针寄存器的地址为运行时库分配的地址,会导致栈失去平衡,从而使程序崩溃,因此我们事先将原始栈指针保存到一个变量中,在main函数return之前恢复栈指针的值即可实现无缝衔接。

具体实现位于源程序Line 73~82和Line 149~153。这里需要利用预处理宏__i386__区分当前处理器体系是x86还是x86_64,因为32bit体系使用ESP存储栈指针,而64bit体系使用RSP。除非预先知道OJ使用的系统字长,否则应当保守处理,将针对两种体系的版本都写好。

  1 /*
  2  * 欧拉路径问题
  3  *
  4  * 无向图存在欧拉路径的充要条件是:
  5  * 1. 图各个顶点连通(是连通图) 
  6  * 2. 各顶点的度都为偶数,或者至多存在两个顶点度为奇数(则这两个顶点为起点或终点)
  7  *
  8  * 因此先利用并查集判断图中各个顶点是否都连通,然后判断有无度为奇数的顶点,如果有,则这些点要么是起点要么是终点。
  9  * 当符合欧拉路径的条件时,依题意直接从顶点1开始进行DFS(穷举的对象是边,不是顶点)
 10  * 即可找出欧拉路径。假如题目换一下,不能确定起点为1,应该考虑fleury算法。
 11  *
 12  * 本题给出的图为稠密图(M<=1e5),直接DFS出现栈溢出错误。简单的解决方案是手工分配更大的栈空间(malloc),
 13  * 然后通过修改ESP或RSP寄存器使得当前栈指向新分配的空间。这里需要注意当main函数返回之前,应当恢复之前的
 14  * 栈指针寄存器,否则会造成返回到crt时栈不平衡。 
 15  */ 
 16 #include <iostream>
 17 #include <vector>
 18 #include <stack>
 19 #include <queue>
 20 #include <algorithm>
 21 
 22 #include <fstream>
 23 
 24 #define N 10000+2
 25 #define M 100000+2
 26 
 27 using namespace std;
 28 
 29 struct edge {
 30     int u, m;
 31     
 32     inline bool operator<(const edge &e) const {
 33         return u < e.u;
 34     }
 35 };
 36 
 37 static vector<edge> con[N]; 
 38 static int deg[N];
 39 static int parent[N];
 40 
 41 static char vism[M];
 42 static stack<int> path;
 43 
 44 static int uf_find(int a) {
 45     if (parent[a] == a)
 46         return a;
 47     else
 48         return (parent[a] = uf_find(parent[a]));
 49 }
 50 
 51 static inline void uf_union(int a, int b) {
 52     int pa = uf_find(a);
 53     int pb = uf_find(b);
 54     if (pa != pb) {
 55         parent[pa] = pb;
 56     }
 57 }
 58 
 59 static void search(int cur) {
 60     
 61     for(size_t i=0; i<con[cur].size(); ++i) {
 62         const edge &e = con[cur][i];
 63         if (!vism[e.m]) {
 64             vism[e.m] = 1;
 65             search(e.u);
 66         }
 67     }
 68     
 69     path.push(cur);
 70 }
 71 
 72 int main(void) {
 73     register char *sp;
 74     int size = 10 << 20; // 10MB  
 75     char *p = (char*)malloc(size) + size;  
 76 #ifdef __i386__
 77     __asm__("movl %%esp, %0\n" :: "r"(sp));
 78     __asm__("movl %0, %%esp\n" :: "r"(p));
 79 #else
 80     __asm__("mov %%rsp, %0\n" :: "r"(sp));
 81     __asm__("mov %0, %%rsp\n" :: "r"(p));
 82 #endif
 83     ios::sync_with_stdio(false);
 84     
 85     fstream fin("input1.txt");
 86     cin.rdbuf(fin.rdbuf());
 87     
 88     int n,m;
 89     cin >> n >> m;
 90     
 91     for(int i=1; i<=n; ++i) {
 92         parent[i] = i;
 93     }
 94     for(int i=0; i<m; ++i) {
 95         int a,b;
 96         edge e;
 97         cin >> a >> b;
 98         e.m = i;
 99         e.u = b;
100         con[a].push_back(e);
101         e.u = a;
102         con[b].push_back(e);
103         ++deg[a]; ++deg[b];
104         uf_union(a,b);
105     }
106     
107     bool conn = true;
108     int p1 = uf_find(1);
109     for(int i=2;i<=n; ++i) {
110         int pi = uf_find(i);
111         if (pi != p1) {
112             conn = false;
113             break;
114         }
115     }
116     
117     if (!conn) {
118         cout << -1 << endl;
119     } else {
120         vector<int> start;
121         int odd = 0; 
122         for(int i=1; i<=n; ++i) {
123             sort(con[i].begin(), con[i].end());
124             if (deg[i] & 1) {
125                 ++odd;
126                 start.push_back(i);
127             }
128         }
129     
130         if (odd <= 2 && find(start.begin(), start.end(), 1)!=start.end()) { 
131             search(1);
132             
133             while(!path.empty()) {
134                 int t = path.top();
135                 path.pop();
136                 cout << t;
137                 if (path.empty()) {
138                     cout << endl;
139                 } else {
140                     cout << " ";
141                 }
142             }
143             
144         } else {
145             cout << -1 << endl;
146         }
147     } 
148     
149 #ifdef __i386__
150     __asm__("movl %0, %%esp\n" :: "r"(sp));
151 #else
152     __asm__("mov %0, %%rsp\n" :: "r"(sp));
153 #endif
154     return 0;
155 }

 

上一篇:NX二次开发-获取工程图尺寸的值UF_DRF_ask_dim_info


下一篇:NX二次开发-删除功能区工具栏UF_UI_remove_ribbon