[洛谷P3014][USACO11FEB]牛线Cow Line (康托展开)(数论)
如果在阅读本文之前对于康托展开没有了解的同学请戳一下这里: 简陋的博客 百度百科
题目描述
N(1<=N<=20)头牛,编号为1...N,正在与FJ玩一个疯狂的游戏。奶牛会排成一行(牛线),问FJ此时的行号是多少。之后,FJ会给牛一个行号,牛必须按照新行号排列成线。
行号是通过以字典序对行的所有排列进行编号来分配的。比如说:FJ有5头牛,让他们排为行号3,排列顺序为:
1:1 2 3 4 5
2:1 2 3 5 4
3:1 2 4 3 5
因此,牛将在牛线1 2 4 3 5中。
之后,奶牛排列为“1 2 5 3 4”,并向FJ问他们的行号。继续列表:
4:1 2 4 5 3
5:1 2 5 3 4
FJ可以看到这里的答案是5。
FJ和奶牛希望你的帮助玩他们的游戏。他们需要K(1<=K<=10000)组查询,查询有两个部分:C_i将是“P”或“Q”的命令。
如果C_i是'P',则查询的第二部分将是一个整数A_i(1 <= A_i <= N!),它是行号。此时,你需要回答正确的牛线。
如果C_i是“Q”,则查询的第二部分将是N个不同的整数B_ij(1 <= B_ij <= N)。这将表示一条牛线,此时你需要输出正确的行号。
输入格式:
* Line 1: Two space-separated integers: N and K
* Lines 2..2*K+1: Line 2*i and 2*i+1 will contain a single query.
Line 2*i will contain just one character: 'Q' if the cows are lining up and asking Farmer John for their line number or 'P' if Farmer John gives the cows a line number.
If the line 2*i is 'Q', then line 2*i+1 will contain N space-separated integers B_ij which represent the cow line. If the line 2*i is 'P', then line 2*i+1 will contain a single integer A_i which is the line number to solve for.
输出格式:
* Lines 1..K: Line i will contain the answer to query i.
If line 2*i of the input was 'Q', then this line will contain a single integer, which is the line number of the cow line in line 2*i+1.
If line 2*i of the input was 'P', then this line will contain N space separated integers giving the cow line of the number in line 2*i+1.
下面是正文:
这是一个康托展开的(模板)题
对于n个数的全排列,这个地方我们可以运用 STL 中的next_permunation进行优化
ai代表第i个元素在未出现的元素中是第几大 即在第i~n位的元素中的rank
对与 2 1 3
对于 2 只有1比它大 所以排第1大;
对于 1 没有比它大的 所以排第0大 ;
对于 3 没有和它比较的 所以排第0大;
a3=3,a2=0,a1=0
所以ans=3;
康托展开逆运算
已知某一全排列在所有排列中排第x
可以求得 这个全排列
例如 ans=20; 求它的全排列
第一次 20/(3!) =3……2 说明在第一个元素后面有3个比现在的元素小 这个元素就是4;
第二次 2/(2!) =1……0 说明在这个元素后面有一个比它小 这只能是2;
第三次 0/(1!) =0……0 说明这个元素后面没有比它小的 只能是 1;
第四次 0/(0!) =0……0 只剩3了;
简而言之就是通过不断地向下除来实现一个降低复杂度
那么我们就可以通过这个来得到他们的序列排序了,不过这里我们要注意的一点就是这是对于整个序列所进行的一个排序,以及排序之间的空格!
下面上代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; long long n,m,fac[28],vst[30],a[30],x; char op; void init() //初始化 { for(int i=0;i<=28;i++) // 这里到了28就已经十分大了,所以无需再往后运算 fac[i]=1; } ll contor(ll x[]) //Cantor的运算 { long long ans=0; for(int i=1;i<=n;i++) { int tag=0; //标记为0 for(int j=i+1;j<=n;j++) { if(x[i]>x[j]) tag++; //更新标记 } ans+=tag*fac[n-i]; } return ans+1; } void recontor(ll x) //Cantor的逆运算 { memset(vst,0,sizeof(vst)); x--; ll k; for(int i=1;i<=n;i++) { ll ans=x/fac[n-i]; //Cantor的实际操作1 for(int j=1;j<=n;j++) { if(!vst[j]) { if(!ans) //加入ans为0的话就赋值 { k=j; break; //时间上的优化 } ans--; } } printf("%d ",k); vst[k]=1; //回溯 x%=fac[n-i]; //Cantor的实际操作2 } printf("\n"); } int main() { scanf("%lld%lld",&n,&m); init(); for(int i=1;i<=n;i++) fac[i]=i*fac[i-1]; //康托展开的预处理 for(int i=1;i<=m;i++) { cin>>op; //判断题目所给的标记 if(op=='P') { scanf("%lld",&x); recontor(x); //康托展开逆运算,进行加进去 } if(op=='Q') { for(int i=1;i<=n;i++) scanf("%lld",&a[i]); //康托展开运算,运算 printf("%lld\n",contor(a)); } } return 0; }