洛谷编译时间太长会导致编译失败?
Sunlight_zero · · 个人记录
前几天做洛谷 P1144 最短路计数 时,意外发现一个奇怪的 BUG:提交后会提示“编译失败”,但是不给出任何编译失败的原因。这里给出一个编译失败链接
这次编译失败提交的代码如下:
#include <iostream>
#include <unordered_map>
#include <queue>
using namespace std;
const size_t MAXN = 1e6 + 10;
const unsigned INF = 1u << 31;
struct Edge {
size_t to;
unsigned cnt = 1; // Count how many path between two vertices
Edge *next;
};
struct Node {
Edge *first = nullptr;
unsigned dis = INF;
int cnt = 0;
} nodes[MAXN];
unordered_map<size_t, Edge*> f;
void create_edge(size_t u, size_t v)
{
Edge *e = f[u * MAXN + v];
if (e)
{
e->cnt++;
f[v * MAXN + u]->cnt++;
}
else
{
Edge *e = new Edge;
e->next = nodes[u].first;
e->to = v;
nodes[u].first = e;
f[u * MAXN + v] = e;
e = new Edge;
e->next = nodes[v].first;
e->to = u;
nodes[v].first = e;
f[v * MAXN + u] = e;
}
}
queue<size_t> qu;
bool visited[MAXN];
void dijkstra(size_t s)
{
nodes[s].dis = 0;
nodes[s].cnt = 1;
qu.push(s);
while (!qu.empty())
{
size_t u = qu.front();
qu.pop();
if (visited[u])
{
continue;
}
visited[u] = true;
for (Edge *e = nodes[u].first; e != nullptr; e = e->next)
{
size_t v = e->to;
if (nodes[u].dis + 1 < nodes[v].dis)
{
nodes[v].dis = nodes[u].dis + 1;
nodes[v].cnt = nodes[u].cnt * e->cnt;
}
else if (nodes[u].dis + 1 == nodes[v].dis)
{
nodes[v].cnt = (nodes[v].cnt + nodes[u].cnt * e->cnt) % 100003;
}
qu.push(v);
}
}
}
int main()
{
ios::sync_with_stdio(false);
size_t n, m, s = 1, u, v;
cin >> n >> m;
while (m--)
{
cin >> u >> v;
create_edge(u, v);
}
dijkstra(s);
for (size_t u = 1; u <= n; u++)
{
cout << nodes[u].cnt << "\n";
}
return 0;
}
注意我的码风看起来和主流 OI 码风并不一致,我会使用结构体(类)和结构体指针,而不是开很多个数组。结构体的一个优点就是可以指定初始值,它在编译时会转化成该类的一个构造函数。以下函数构造 Node 类时,会指定属性 dis 的初始值为 INF:
const unsigned INF = 1u << 31;
struct Node {
Edge *first = nullptr;
unsigned dis = INF;
int cnt = 0;
} nodes[1000010];
经过排查,我确认当上式中指定 dis = INF 时,本地编译时间会变长(需要大约 6 秒),而在洛谷上也会提示编译失败。但是,如果去掉 dis 的初始值指定,则编译时间大大缩短,洛谷上编译也能通过(这时候是 WA 而不是 CE)。
因此,可以肯定,结构体指定特殊的初始值会拉长编译时间,从而导致洛谷上编译超时。在一位汇编大佬的帮助下,我们总结出了编译时间拉长的原因:
对于 C++ 结构体而言,如果所有属性的初始值都指定,则编译器会将其优化,将所有初始值全部塞进程序中(是的,所有!这里一共塞了 16 * 1000010 字节的初始值,编译出来的文件接近 16 MB!)。
而如果有部分初始值未指定,例如上面的代码改成
const unsigned INF = 1u << 31;
struct Node {
Edge *first = nullptr;
unsigned dis = INF;
int cnt;
} nodes[1000010];
则编译器会将这段代码优化成循环赋值,而不在编译过程写死初始值。这样编译出来的文件只有几百 KB。