康托展开+两道例题

简介

康托展开用于计算某个字符串在所有字典序中的位置。常用于状态压缩和哈希。

公式

一个字符串A1,A2,…,An,假设都是数字0-9
这个字符串在所有字典序中的位置为:

B1*(n-1)!+B2*(n-2)!+...+Bn*(n-n)!
Bn表示这个字符后面比它小的字符的个数
以52134为例,B1=4,因为2134都比5小,B2=1,因为只有12小,...

这么做的理由是后面更小的字符可以换到当前位置上构成更小的字典序,后面的n个字符又有n!种排列方式。

优化

如果每次都要遍历一遍后面的字符,复杂度为O(n2),大部分的题目还是过不了,如果用树状数组进行优化,可以将复杂度降到O(nlogn)级别,可以应对绝大部分的题目。

例题

康托展开-洛谷

//AC代码
#include <bits/stdc++.h>
#define maxn 1000000 + 5
using namespace std;
int n;
long long num[maxn]; //记录输入的数字
long long C[maxn] = {0};

inline int lowbit(int x)
{
    return x & (-x);
}

inline int presum(int x) //presum(i)表示比i小的数字个数
{
    int res = 0;
    while (x > 0)
    {
        res += C[x];
        x -= lowbit(x);
    }
    return res;
}

inline void update(int x, int vaule)
{
    while (x <= n)
    {
        C[x] += vaule;
        x += lowbit(x);
    }
}

int main()
{
    long long f = 1, ans = 1;
    scanf("%d", &n);
    for (int i = n; i > 0; i--)
        scanf("%lld", &num[i]);
    update(num[1], 1);
    //从最右端开始统计,一边统计一边建树
    for (int i = 2; i <= n; i++)
    {
        ans += (presum(num[i]-1) * f) % 998244353;
        ans %= 998244353;
        update(num[i], 1); //每统计一个数,使小于等于num[i]的数加一
        f = (f * i) % 998244353; //阶乘也是一样
    }
    printf("%d", ans);
    return 0;
}

八数码难题

//这题要我们找出最小的步长,那么进行BFS即可
//BFS需要记录某个状态是否出现过,这题如果直接将排列化成数字,数量级将会达到10^9,空间肯定是不够用的
//如果将每种状态用康托展开,9个字符的排列一共只有9!种,大概36万,空间将大大减少
#include <bits/stdc++.h>
using namespace std;

struct condition //表示状态的结构体
{
    int num[3][3];
    int x, y; //记录0的位置
};

int len[362885] = {0}; //记录每个状态的步长
int fc[10] = {1, 1}; //阶乘打表
bool vis[362885] = {0}; //记录这个状态是否出现过

int cantor(condition &c) //将状态进行康托展开,离散化
{
    int res = 0, cnt;
    int tmp[9];
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
            tmp[i * 3 + j] = c.num[i][j];
    }
    for (int i = 0; i < 8; i++)
    {
        cnt = 0;
        for (int j = i + 1; j < 9; j++)
        {
            if (tmp[j] < tmp[i])
                cnt++;
        }
        res += cnt * fc[8 - i];
    }
    return res;
}

int main()
{
    condition d{1,2,3,8,0,4,7,6,5, 2, 2}; //目标状态
    for (int i = 2; i < 10; i++)
        fc[i] = fc[i - 1] * i;
    int des = cantor(d); //目标状态的康托编码
    condition start;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            char c = getchar();
            start.num[i][j] = c - '0';
            if(c=='0')
            {
                start.x = i;
                start.y = j;
            }
        }
    }
    vis[cantor(start)] = 1;
    queue<condition> q;
    q.push(start);
    condition tmp;
    condition nxt;
    int x, y, last;
    while (!q.empty()) //进行BFS搜索
    {
        tmp = q.front();
        q.pop();
        x = tmp.x;
        y = tmp.y;
        last = cantor(tmp);
        if(last==des)
            break;
        if(last)
        nxt = tmp;
        if (x < 2)
        {
            swap(nxt.num[x][y], nxt.num[x + 1][y]);
            int ct = cantor(nxt);
            if (!vis[ct])
            {
                vis[ct] = 1;
                len[ct] = len[last] + 1;
                nxt.x = x + 1;
                nxt.y = y;
                q.push(nxt);
            }
        }
        nxt = tmp;
        if (x > 0)
        {
            swap(nxt.num[x][y], nxt.num[x - 1][y]);
            int ct = cantor(nxt);
            if (!vis[ct])
            {
                vis[ct] = 1;
                len[ct] = len[last] + 1;
                nxt.x = x - 1;
                nxt.y = y;
                q.push(nxt);
            }
        }
        nxt = tmp;
        if (y < 2)
        {
            swap(nxt.num[x][y], nxt.num[x][y + 1]);
            int ct = cantor(nxt);
            if (!vis[ct])
            {
                vis[ct] = 1;
                len[ct] = len[last] + 1;
                nxt.x = x;
                nxt.y = y + 1;
                q.push(nxt);
            }
        }
        nxt = tmp;
        if (y > 0)
        {
            swap(nxt.num[x][y], nxt.num[x][y - 1]);
            int ct = cantor(nxt);
            if (!vis[ct])
            {
                vis[ct] = 1;
                len[ct] = len[last] + 1;
                nxt.x = x;
                nxt.y = y - 1;
                q.push(nxt);
            }
        }
    }
    printf("%d", len[des]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值