路程

路程

题意

构造一个点数不超过 \(18\),边数不超过 \(45\) 的有向无环图,满足:

入度为 \(0\) 的点只有 \(1\),出度为 \(0\) 的点只有 \(114\)

\(1\)\(114\) 的所有路径的边权和恰好为 \([0,n]\)

思路

先想到可以把每个数二进制拆分了,每一位对应 DAG 上的一层。

可以写出一个类似于数位 dp 的记忆化搜索。

\(f_x\) 表示构造出后 \(x\) 位即 \([0,2^x-1]\) 的 DAG 的起点。

搜索时顺便加边即可。

这样构造并不优秀,最大可能有 \(20\) 多个点。

这时想到,能 \(2\) 进制拆分,不就能 \(3\) 进制拆分吗?

进制越大,点数越小,边数越大。

换成 \(3\) 进制后可以通过极限数据,但大部分还是不能通过。

这时可以想到枚举进制,取出满足条件的构造就行。

这样已经有 \(80 \%\) 的正确率了。

部分数据会出现边数只超过限制几条的情况,考虑优化。

如果一个点的入度为 \(1\),这个点就是废物,

因为我们可以把它的前驱连向它的所有后继,这样可以省下 \(1\)\(1\) 边。

跑完数位 dp 后再 dfs 一遍进行入度优化,优化完后可以跑过所有点。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 117;
int base, in[N];

int len, m, a[N], f[N][2], cnt, tot;
vector <pair <int, int>> E[N];
vector <pair <int, int>> e[N];
bool vis[N];

int dfs(int x, bool fp) { // 类似数位 dp 的记搜
	if (x == 0) return 114; // 没了, 返回终点
	if (f[x][fp]) return f[x][fp]; // 建过点了, 返回
	int res = ++ cnt, fpmax = fp ? a[x] : base - 1;
	for (int i = 0; i <= fpmax; i ++) 
		E[res].push_back({dfs(x - 1, fp && (i == fpmax)), i * pow(base, x - 1)}); // 连边
	f[x][fp] = res; // 记忆化
	return res;
}

void dfs2(int x) { // 入度优化
	if (vis[x]) return ;
	vis[x] = 1;
	for (auto [y, z] : E[x]) {
		dfs2(y);
		if (in[y] == 1 && y != 114) { // 入度为 1 注意不能把终点优化了
			for (auto [v, w] : E[y]) 
				e[x].push_back({v, w + z});
			E[y].clear();
			tot --;
		} else e[x].push_back({y, z});
	}
	E[x] = e[x];
}

bool check(int n) {
	for (int i = 0; i < N; i ++) 
		E[i].clear(), e[i].clear();
	memset(f, 0, sizeof(f));
	memset(in, 0, sizeof(in));
	memset(vis, 0, sizeof(vis));
	cnt = m = len = 0;
	while (n) {
		a[++ len] = n % base;
		n /= base;
	}
	dfs(len, 1); // dp
	for (int i = 1; i <= cnt; i ++) 
		for (auto y : E[i]) in[y.first] ++;
	tot = cnt;
	dfs2(1); // 优化
	for (int i = 1; i <= 114; i ++) 
		m += E[i].size();
	return tot + 1 <= 18 && m <= 45;
}

int main() {
	freopen("road.in", "r", stdin);
	freopen("road.out", "w", stdout);
	int n;
	cin >> n;
	for (base = 2; base <= 30; base ++) // 枚举进制
		if (check(n)) break; // 满足条件就输出
	cout << tot + 1 << " " << m << "\n";
	for (int i = 1; i <= 114; i ++) 
		for (auto y : E[i]) {
			cout << i << " " << y.first << " " << y.second << "\n";
		}
	return 0;
}
posted @ 2024-11-04 20:28  maniubi  阅读(2)  评论(0编辑  收藏  举报