Wiki 2746(末日传说-打表找字典序最小逆序对=m的序列)

本文介绍了一种基于特定排列和逆序数计算的世界末日预测算法。通过分析日历元素的排列变化及其逆序数,找出与给定值匹配的日期,即所谓的世界末日。文章还提供了一个简洁的程序实现,并探讨了其背后的规律。

    在古老的东方,人们都采用一种奇妙的方式记录日期:他们用一些特殊的符号来表示从1开始的连续整数,1表示最小而N表示最大。创世纪的第一天,日历就被赋予了生命,它自动的开始计数,就像排列在不断地增加。

    我们用1——N来表示日历的元素,第一天的日历是:1,2,3,……N-1,N

                                                  第二天,日历自动变为1,2,3,……N,N-1

    每次他都生成一个以前从未出现过的“最小”的排列——将它转为(N+1)进制后数的数值最小。

    有一天,一个预言者出现了——虽然没人让他出现——他预言道,当这个日历到达某个上帝安排的时刻(……),这个世界就会崩溃……他还预言,假如一个某一个日子的逆序到达一个指定数值M时,世界末日将来临。

    逆序是什么?日历中两个不同的符号,假如排在前面的那个比后面的那个大,就是一个逆序(不一定相邻)。人们期待一个贤者来预见那一天。

只包含一行两个正整数,分别为N和M。

输出一行,为世界末日的日期,每个数字间用一个空格隔开。

5 4

1 3 5 4 2

对于10%的数据有N<=10。

对于40%的数据有N<=1000。

对于100%的数据有N<=50000。

所有数据均有解。

为避免出现错误,请在程序最后加上一个 writeln;


这题又是打表找规律。。。

我换了和FJ省选二试一样的错误。。。又手推公式。。结果各种推错。。果然这种题目不打表都不行。。

好了,我写了一个简洁的dabiao程序:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<cctype>
#include<ctime>
using namespace std;
#define For(i,n) for(int i=1;i<=n;i++)
#define Fork(i,k,n) for(int i=k;i<=n;i++)
#define Rep(i,n) for(int i=0;i<n;i++)
#define ForD(i,n) for(int i=n;i;i--)
#define RepD(i,n) for(int i=n;i>=0;i--)
#define Forp(x) for(int p=pre[x];p;p=next[p])
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define MEMI(a) memset(a,127,sizeof(a));
#define MEMi(a) memset(a,128,sizeof(a));
#define INF (2139062143)
#define F (100000007)
#define MAXN (50000+10)
long long mul(long long a,long long b){return (a*b)%F;}
long long add(long long a,long long b){return (a+b)%F;}
long long sub(long long a,long long b){return (a-b+(a-b)/F*F+F)%F;}
typedef long long ll;
int n,a[MAXN];
bool b[MAXN]={0};
int main()
{
	n=5;
	For(i,n) a[i]=i;
	For(i,5*4*3*2)
	{
		int tot=0;
		For(j,n) Fork(k,j+1,n) if (a[j]>a[k]) tot++;
		if (!b[tot]) {For(i,n) cout<<a[i]<<' ';cout<<':'<<tot<<endl;b[tot]=1;}
		next_permutation(a+1,a+1+n);		
	}	
	return 0;
}

差不多就是这样。。。

1 2 3 4 5 :0
1 2 3 5 4 :1
1 2 4 5 3 :2
1 2 5 4 3 :3
1 3 5 4 2 :4
1 4 5 3 2 :5
1 5 4 3 2 :6
2 5 4 3 1 :7
3 5 4 2 1 :8
4 5 3 2 1 :9
5 4 3 2 1 :10

不管你们发现没,我是发现了。。

差不多规律是这样——后一段反序,然后一个数按顺序从小到大提到前面,其它不变(仍反序)。。

PS:反序就是 5 4 2 1 这样的单调递减序列

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<cctype>
#include<ctime>
using namespace std;
#define For(i,n) for(int i=1;i<=n;i++)
#define Fork2(i,k,n) for(int i=k;i<=n;i++)
#define Rep(i,n) for(int i=0;i<n;i++)
#define ForD(i,n) for(int i=n;i;i--)
#define RepD(i,n) for(int i=n;i>=0;i--)
#define Forp(x) for(int p=pre[x];p;p=next[p])
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define MEMI(a) memset(a,127,sizeof(a));
#define MEMi(a) memset(a,128,sizeof(a));
#define INF (2139062143)
#define F (100000007)
#define MAXN (50000+10)
long long mul(long long a,long long b){return (a*b)%F;}
long long add(long long a,long long b){return (a+b)%F;}
long long sub(long long a,long long b){return (a-b+(a-b)/F*F+F)%F;}
typedef long long ll;
int n,m;
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	cin>>n>>m;
	int p=0;
	while (p*(p+1)/2<=m) p++;
	For(i,n-p-1) cout<<i<<' ';
	int sum=p*(p-1)/2;
	cout<<n-p+(m-sum);
	//if (sum==m)
	{
		int t=n;
		Fork2(i,n-p+1,n) 
		{
			while (t==n-p+m-sum) t--;
			cout<<' '<<t--;
		}
	}
	puts("");
	return 0;
}






要在一个排列中保持逆序对数量不变的情况下字典序最小的排列,需要综合考虑逆序对字典序的特性。以下是一个基于这些特性的解决方案: ### 法思想 1. **逆序对定义**:对于一个排列 $ A $,如果存在 $ i < j $ 且 $ A[i] > A[j] $,则称这对元素构成了一个逆序对。 2. **字典序定义**:字典序是通过从左到右比较两个排列的每个元素来决定的。较小的元素出现在较早的位置时,该排列的字典序更小。 为了在保持逆序对数量不变的前提下字典序最小的排列,可以采取如下步骤: - **贪心策略**:逐步构建排列,每次选择当前可用的最小数字,并确保其插入位置不会改变整体的逆序对数量。 - **调整机制**:当某个数字插入后导致逆序对变化时,通过局部调整(如交换)恢复原始的逆序对数量。 ### 实现法示例 ```python def min_lex_with_inversions(n, inv_count): # 初始化排列 [1, 2, ..., n] perm = list(range(1, n + 1)) # 计初始逆序对的数量 def count_inversions(arr): return sum(1 for i in range(len(arr)) for j in range(i + 1, len(arr)) if arr[i] > arr[j]) current_inversions = count_inversions(perm) # 如果当前逆序对于目标,则无法直接构造 if current_inversions > inv_count: raise ValueError("目标逆序对数不能小于初始排列的逆序对数") # 调整排列以达到指定的逆序对数 for i in range(n - 1, 0, -1): for j in range(i): if current_inversions >= inv_count: break # 交换元素以增加逆序对 perm[j], perm[j + 1] = perm[j + 1], perm[j] current_inversions += 1 if current_inversions != inv_count: raise ValueError("无法精确达到指定的逆序对数") return perm # 示例调用 print(min_lex_with_inversions(5, 4)) # 输出一个长度为5、逆序对为4的字典序最小排列 ``` ### 关键点分析 - **初始排列**:从升序排列开始,因为这是字典序最小的排列形式。 - **逆序对**:使用双重循环计当前排列中的逆序对数量。 - **调整过程**:从右向左进行相邻元素交换,逐步增加逆序对,直到达到指定数量为止。 - **异常处理**:如果目标逆序对数小于初始值或无法精确匹配,则抛出异常。 这种方法能够在保证逆序对数量不变的前提下,生成字典序尽可能小的排列。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值