关于:昨天H - 康托展开题目的探究。

51nod_3209 康托展开


没有了解康托展开的可以先去看看:都能看懂的康托展开.
题目对康托展开进行了拓展:有重复数字的康托展开。
参考思路如下:在这里插入图片描述
但是上面说的太过粗略,接下来较为严谨的分析下:

  • 题目中的 t o t a l total total指的是总排列数量: s u m ! sum! sum!是所有元素的排列组合的方式,除以每个元素所占的数量的阶乘,就是去除这个元素的重复所带来的多的排列的方式(高中知识)。
    得出:
total = sum !/ cnt1 ! ·····
  • 首先,要注意的是,一定可以除尽,为什么?从客观的角度想,sum!一定是大于后面的,并且一定含有后面的因子,但是这样想未免太过僵硬,不妨主主观上想,要求的是方案数丫,方案数有不是整数的吗?木有!所以,一定能除尽。

  • Pn = total / sum * cntn,这个公式。可以从多方面思考,total 是总排列数,除以总元素数量,乘以某个元素的个数有什么具体含义呢 ? total / sum 可以看作是任意一个元素在首位置时的排列数,乘以这个的数量,就是这个元素在首位的总排列数。

    但是要注意,total / sum 不一定可以整除。但是 Pn = total * cetn / sum 一定可以整除(这个的答案也是方案数,从主观上看,所得一定是个整数解)。
    所以第二个公式更应该这么写:

Pn = total * cntn / sum
  • 这里,n是在不断缩小的,对应的,total也是不断变化的,cnt也是变化的,所以我们要动态处理,每一次,都要重新求一下对应的值。
  • OK,没看懂也没关系,代码如下
#include<bits/stdc++.h>
#define fi first
#define endl "\n"
#define se second
#define PI acos(-1)
#define int long long
#define inf 0x3f3f3f3f
#define mm(a, b) memset(a, b, sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define debug freopen("1.in", "r", stdin), freopen("1.out", "w", stdout);
using namespace std;
typedef long long ll;
typedef pair<int , int> PII;
const int P = 1000000007;
const int N = 100010;

int a[N];
int ck[N];
bool vis[15];

void init()
{
	ck[0] = 0;ck[1] = 1;
	for(int i = 2;i <= 12;i ++)ck[i] = ck[i - 1] * i;
}
signed main()
{
	ios//关流,加快输入速度。
	init();//预处理所有阶乘。
	int t;cin >> t;
	while(t --)
	{
		int n;cin >> n;
		int sum = ck[n];//这里的sum 和 上文提到的 total 一样。
		int cnt[15];//记录每个数字出现的次数。
		mm(cnt , 0);//初始化次数
		mm(vis , false);//初始化数字出现状态。
		for(int i = 0;i < n;i ++)//读入每一个数字,并且记录出现次数。
		{
			cin >> a[i];
			cnt[a[i]]++;
		}
		int nn = n;//备份一下n, n表示当前排列的长度,nn表示总共有多少个数
		int res = 0;//初始化答案。
		for(int i = 0;i < nn;i ++)
		{
		    mm(vis , false);
		    //计算total,如果一个数已经算过了,就没必要除两次,vis数组用于记录状态。
		    for(int k = i;k < nn;k ++)
		        {
		        if(!vis[a[k]])
		        {
		            sum = sum / ck[cnt[a[k]]];
		            vis[a[k]] = true;
		        }
		    }
		    mm(vis , false);
		    //如果后面的数小于当前第一位的数,后面这个数的放在首位的全排列全部都是字典序小于当前排列的答案,每个都要加。
		    for(int j = i + 1;j < nn;j ++)
		    {
		        if(a[j] < a[i] && !vis[a[j]])
		        {
		        	//满足条件,就计算答案。
		            res = res + sum *cnt[a[j]] / n;
		            vis[a[j]] = true;
		        }    
		    }
		    //去下一个排列前的初始化
		    n --;//排列长度减少1.
		    sum = ck[n];//sum 要重新计算
		    cnt[a[i]] --;//当前这个数已经木得价值,就cnt减去1。
		}
		//排序是从0开始,所以答案+1.
	    cout << res + 1 << endl;
	}	
	return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值