「笔记」如何优雅的造数据

可以配合如何快速的写出对拍程序食用qwq。

下面讲的方法本着简单快捷的原则,更多是为了方便大家在比赛中快速造出数据进行对拍。也是为了方便我随手取用。

如果有什么更牛逼的造数据方法,也可以跟我说/kel,欢迎补充。

批量数据生成器

感谢 @斜揽残箫 提供/qq。

#include<bits/stdc++.h>
using namespace std;
namespace rad {
	mt19937_64 R(time(0));
	inline int Rand(int l,int r) {
		uniform_int_distribution<int> distribution(l,r);
		return distribution(R);
	}
} using namespace rad;
int main() {
	string in,out;
	for(int i = 1;i <= 3;i ++) { // 序号 
		in.clear();
		int j = i;
		while(j) {
			char m = j % 10 + '0';
			in = m + in;
			j /= 10;
		}
		out = in;
		in += ".in";
		out += ".out";
		string A = "average"; // 文件名 
		string B = "average";
		A = A + in,B = B + out;
		in = A,out = B;
		freopen(in.data(),"w",stdout);
		system("srandom.exe"); // 数据生成器 
		fclose(stdout);
		string Str="average.exe < "+ in +" > "+ out; // 你的 std 
		system(Str.data());
	}
}

rand() 和 srand()

rand() 函数用来产生随机数,但是,rand() 的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。

rand() 会返回一个范围在 0 到 RAND_MAX(一般是32767)之间的伪随机数(整数)。

srand() 函数则是设置随机数种子,如果没有 srand() 函数,rand() 默认的随机数种子为 \(1\)。随机种子相同,每次产生的随机数也会相同。

随机一个数

一般形式为 x = rand()。随机一个数(\(0 \sim 32767\))并存入 \(x\) 内。

一个 long long 范围的数

因为 \(0 \sim 32767\) 在二进制下只占用了 \(15\) 位,所以我们可以把四个 rand() 拼起来。

long long Rand(long long n) { // 限定上界 n,生成一个 [0,n) 范围内的数
    long long x = rand() * (1ll << 45) + rand() * (1ll << 30) + rand() * (1ll << 15) + rand();
    return x % n;
}

注意这里产生的数的范围只在 \(0 \sim 2^{61}-1\),并不是真正意义上的 long long 范围。不过这一般也够用了。

一个 [m, n] 范围的数

把上面的函数稍微修改一下,确定一个下界即可。或者理解为 “在 \(m\) 的基础上在加一个数 \(x\),可加的 \(x\) 的大小为区间长度 \(n-m+1\)”。

long long Rand(long long n) { // 限定上界 n
    long long x = rand() * (1ll << 45) + rand() * (1ll << 30) + rand() * (1ll << 15) + rand();
    return x % (n - m + 1) + m;
}

一个小数

原理:如果你要一个 \(x\) 位小数,你可以先生成一个 \(x\) 位的数,然后除以 \(10^x\)

double Rand(long long n) { // 限定上界 n = 10^x
    long long x = rand() * (1ll << 45) + rand() * (1ll << 30) + rand() * (1ll << 15) + rand();
    return (double)x / n;
}

一个序列

一个长度为 \(n\) 的序列直接输出 \(n\) 个随机数就好了吧。

随机一个排列

这里我使用 random_shuffle(a + 1, a + n + 1) 来打乱一个排列。

不过这个函数好像在 C++ 14 中被弃用了?

一棵树

一棵普通的树

Kruskal

利用 Kruskal 的思想打造即可。

int fa[MAXN];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void Get_A_Tree(int n) {
    int cnt = 0;
    for(int i = 1; i <= n; ++i) fa[i] = i;
    while(cnt < n - 1) {
        int u = Rand(n) + 1, v = Rand(n) + 1;
        while(find(u) == find(v)) v = Rand(n) + 1;
        fa[find(u)] = find(v);
        printf("%d %d\n", u, v);
        ++ cnt;
    }
}

随机排列

感谢 @KnightL 提供/qq。

这里提供另外一种简单的方法:

随机一个排列,然后后面的一个点之和前面的某个点连边。

在这个方法下,内向树和外向树也同样非常方便构造,只需要在输出时确定连边方向即可。

int c[MAXN];
void Get_A_Tree(int n) {
    for(int i = 1; i <= n; ++i) c[i] = i;
    random_shuffle(c + 1, c + n + 1);
    for(int i = 2; i <= n; ++i) {
        printf("%d %d\n", Rand(i - 1) + 1, i); 
    }
}

一棵外(内)向树

在上面的一棵普通的数的基础上,需要还通过 dfs 来确立一个方向。

struct edge { int to, nxt; }e[MAXN << 2];
int head[MAXN], num_edge = 1;
int fa[MAXN];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }
void dfs(int u, int fa) {
    for(int i = head[u]; i; i = e[i],nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        printf("%d %d\n", u, v); // 外向树
        // printf("%d %d\n", v, u); // 内向树
        dfs(v, u);
    }
}
void Get_A_Tree(int n, int m) {
    int cnt = 0;
    for(int i = 1; i <= n; ++i) fa[i] = i;
    while(cnt < n - 1) {
        int u = Rand(n) + 1, v = Rand(n) + 1;
        while(find(u) == find(v)) v = Rand(n) + 1;
        fa[find(u)] = find(v);
        add_edge(u, v), add_edge(v, u);
        ++ cnt;
    }
}

一张图

一张无向联通图

先随机生成一棵树,然后在添加边即可。

避免重边

一个暴力的方法,使用 map 判重。

需要注意的是效率会比较低。

避免自环

随机一条边的时候注意保持 u != v 即可。

一个 DAG (有向无环图)

你可以考虑随机一个拓扑序。

如果要保证图联通的话还是先随机一棵树,然后在随机其他边,否则直接随机若干条边就可以。

注意输出边 \((u,v)\) 的时候你要保证 topo[u] < topo[v],即总是拓扑序前面的点向后面的点连边。

原文地址:https://www.cnblogs.com/Silymtics/p/15527932.html