アルゴリズム研究ノート:Cantor展開と逆Cantor展開

アルゴリズム研究ノート:Cantor展開と逆Cantor展開

1。概要

CantorExpansionとInverseCantor Expansionは、完全な順列問題で一般的に使用される2つのアルゴリズムです。

Cantor Expansion:nnを知るn-フルアレイaaを注文するa、これがどの完全な順列であるかを調べます(辞書式順序でソート)。

逆カンター拡張:nnを知るn-フルアレイaaを注文するこの配置全体で得られランキング。

これらの2つの操作は単に双子の兄弟であることがわかります。

2.実現する

2.1カンター拡張

Cantorの展開式:a 1×(n − 1)!+ A 2×(n − 2)!+ ... + An − 1×1!+ An×0!+ 1 a_1 \ times(n-1)! + a_2 \ times(n-2)!+ ... + a_ {n-1} \ times 1!+ a_n \ times 0!+ 1A1××n1 +A2××n2 ++AN - 1××1 +An個××0 +1このブログの規則:0!= 0 0!= 00 =0

上式中, a i a_i Aaia_iよりも良い意味A小さくてiiにはないi位置の前の数字の数。

したがって5 2 3 1 4、たとえば、拡張シミュレーションステップCantorを実行する必要があります。

  1. 5小さい4よりも0前にあるので、1 = 4 a_1 = 4A1=4この4数は4×4!4 \ x 4!を生成できます4××4 フルアレンジ。
  2. 正面2にある小さな数よりも、2 = 1 a_2 = 10A2=11を考慮して決定された、1×3!1 \ x 3!を生成できる残りの数1××3 フルアレンジ。
  3. (ここでは一部の単語が省略されています)
  4. 最終結果は4×4!+ 1×3!+ 1×2!+ 0×1!+ 0×0!+ 1 = 105 4 \ times 4!+ 1 \ times 3!+ 1 \ times 2!+ 0 \ times 1!+ 0 \ times 0!+ 1 = 1054××4 +1××3 +1××2 +0××1 +0××0 +1=1 0 5

あなたはなぜこれをやっているのですか?

最初の例のように、次のことがわかります。1 2 3 4これらの4つの数値について5は、最初に配置全体を計算する必要はありません。順列と組み合わせの知識によると、結果は4×4×3×2×1 = 4×4!4 \ times 4 \ times 3 \ times 2 \ times 1 = 4 \ times 4!4××4××3××2××1=4××4 残りは同じです。

コードはどうですか?実際、書く方が簡単です。

计算 a i a_i A 重み付き線分ツリー、セットなどを使用して解決できます。

もちろんFHQTreapを使用しましたが、行き詰まりました。

FHQ Treapの学生は、このブログを見ることができます。非常に詳細な説明があります。もちろん、他の実装を使用することもできます。

コード:

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

typedef long long LL;
const int MAXN = 1e6 + 10, P = 998244353;
int n, a[MAXN], cnt, root;
LL ans, f[MAXN];

struct node
{
    
    
	int l, r, size, val, key;
}tree[MAXN];

int read()
{
    
    
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {
    
    if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {
    
    sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}

int Make_Node(int val)
{
    
    
	int now = ++cnt;
	tree[now].size = 1;
	tree[now].val = val;
	return now;
}

void update(int x) {
    
    tree[x].size = tree[tree[x].l].size + tree[tree[x].r].size + 1;}

void split(int now, int val, int &x, int &y)
{
    
    
	if (now == 0) x = y = 0;
	else
	{
    
    
		if (tree[now].val <= val)
		{
    
    
			x = now;
			split(tree[now].r, val, tree[now].r, y);
		}
		else
		{
    
    
			y = now;
			split(tree[now].l, val, x, tree[now].l);
		}
		update(now);
	}
}

int merge(int x, int y)
{
    
    
	if (!x || !y) return x + y;
	if (rand() & 1)
	{
    
    
		tree[x].r = merge(tree[x].r, y);
		update(x); return x;
	}
	else
	{
    
    
		tree[y].l = merge(x, tree[y].l);
		update(y); return y;
	}
}

void Insert(int val)
{
    
    
	int x, y; split(root, val - 1, x, y);
	root = merge(merge(x, Make_Node(val)), y);
}

int Find(int val)
{
    
    
	int x, y, ans;
	split(root, val - 1, x, y);
	ans = tree[x].size;
	root = merge(x, y);
	return ans;
}

int main()
{
    
    
	srand(time(0));
	n = read(); f[0] = 1; cnt = root = 0;
	for (int i = 1; i <= n; ++i) a[i] = read();
	for (int i = 1; i <= n; ++i) f[i] = f[i - 1] * i % P;
	f[0] = 0;
	for (int i = 1; i <= n; ++i)
	{
    
    
		Insert(a[i]); ans = (ans + ((LL)a[i] - 1 - Find(a[i])) * f[n - i]) % P;
//		printf("%d/%d/%d\n", f[n - i], Find(a[i]), ans);//调试用
	}
	printf("%lld\n", ans + 1);
	return 0;
}

2.2カンターの逆展開

著者が弱すぎるので、彼は学びませんでした。

おすすめ

転載: blog.csdn.net/BWzhuzehao/article/details/112744602