Codeforces Round #309 (Div. 2) D - Kyoya and Permutation ,和dp相关的xjb搞的题

这种题 看起来题意都会很复杂的:
对于 1-n的序列 :比如4.1.6.2.5.3 。。。。 1对应4,4对应2,所以(142)是一个集合,同理有(36)(5)
现在要将这些集合排序,集合内部按从大到小排列,集合与集合之间按第一个数从小到大排。比如:
上述的序列 变成:(421)(5)(63)

现在定义一些序列,经过上述的操作之后不会发生改变,比如:1 3 2 4 ; (1)(32)(4)
现在给出n,k。
求1-n的序列中 所有不会操作之后不会改变的序列, 并且按字典序排的第k个
比如样例:
n=4 有
1 2 3 4
1 2 4 3
1 3 2 4
2 1 3 4
2 1 4 3
第三个就是1 3 2 4
我刚刚开始看到这道题,想到的就是 前几天做的经典dp,栅栏高低起伏的那道题, 这道题应该也是一个一个数字的确定过去
但是很难想明白 以1 开头的序列到底有多少个

但其实可以写一写,枚举一下n的序列:

>
n=1———n=2———–n=3———–n=4—– - - - - - -n=5
1————1 2 ———–1 2 3———-1 2 3 4———–1 2 3 4 5
———— -2 1 ———–2 1 3——- -2 1 3 4———–2 1 3 4 5
…—————————1 3 2———1 3 2 4———–1 3 2 4 5
.——————————————–1 2 4 3———–1 2 4 3 5
.——————————————–2 1 4 3———–2 1 4 3 5
.————————————————————–1 2 3 5 4
.————————————————————–2 1 3 5 4
.————————————————————–1 3 2 5 4

仔细看看其实不难得出规律吧:
每一位,如果想要造作之后不变,那么他永远都只能和他前一位的数字是出于同一个集合,否则都不能满足不改变这个条件!
那么就分成了两个子问题:
1.如果i和i-1 进行交换,那么dp[i]+=dp[i-2]
2.i 不和 i-1 交换 dp[i]+=dp[i-1]
也许你无法证明,但是仔细看看写的这些东西,感觉还挺有道理的。于是就可以得到每一个i ,yes(和前一位交换)的个数,以及no(不和前一位交换)的个数,并能用递归预处理求出1-50 的所有dp

struct node{
    __int64 yes;
    __int64 no;
    __int64 sum;
}dp[60];

dp[1].yes=dp[1].no=dp[1].sum=1;
    dp[2].yes=dp[2].no=1;dp[2].sum=2;
    dp[3].yes=1;
    for(int i=3;i<=50;i++){
        dp[i].no=dp[i-1].no+dp[i-2].no;
    }
    for(int i=4;i<=50;i++){
         dp[i].yes=dp[i-1].yes+dp[i-2].yes;
        dp[i].sum=dp[i].yes+dp[i].no;
    }```

想到这一步我们就可以开始一步一步的逐个确认了,
仔细看看n=5,是 21 交换的个数 =54交换的个数 =dp[5].yes
cnt初识为2,
若是k<no,那么 21 不会交换,则ans[cnt-1]=cnt-1,让pos--,cnt++ 继续判断下一个
否则 ,21 交换,则ans[cnt-1]=cnt,ans[cnt]=cnt-1 ; 因为交换,所以可以确定两个位置pos-2,cnt+=2
k-=dp[pos].no;

然后我就开始xjb写了,写了大半天还好是写出来了:
ac代码
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
#define ll __int64
#define INF 0x7fffffff   //INT_MAX
#define inf 0x3f3f3f3f   //
const double PI = acos(-1.0);
const double e = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
using namespace std;
#define mod 1000000007
const int maxn=2000001;
struct node{
    __int64 yes;
    __int64 no;
    __int64 sum;
}dp[60];
int ans[55];
int main(){
    freopen("1.txt","r",stdin);
    int n;
    __int64 k;
    dp[1].yes=dp[1].no=dp[1].sum=1;
    dp[2].yes=dp[2].no=1;dp[2].sum=2;
    dp[3].yes=1;
    for(int i=3;i<=50;i++){
        dp[i].no=dp[i-1].no+dp[i-2].no;
    }
    for(int i=4;i<=50;i++){
        dp[i].yes=dp[i-1].yes+dp[i-2].yes;
        dp[i].sum=dp[i].yes+dp[i].no;
    }
//    printf("%I64d %I64d  \n",dp[46].yes,dp[47].yes);
    while(~scanf("%d %I64d",&n,&k)){
          int pos=n;
          int cnt=2;
          mem(ans);
          while(cnt<=n ){
//            printf("pos=%d\n",pos);
             if(dp[pos].no>=k){ //从低位找 不换,再找交换的
                 pos--;
                 ans[cnt-1]=cnt-1;
                 cnt++;
             }
             else if(dp[pos].no<k){
//                printf("yes   %d\n",cnt);
                 k-=dp[pos].no;
                 pos-=2;
                 ans[cnt-1]=cnt;
                 ans[cnt]=cnt-1;
                 cnt+=2;
             }
          }
          for(int i=1;i<=n;i++){
             if(!ans[i]) printf("%d ",i);
             else printf("%d ",ans[i]);
          }
          cout<<endl;
    }
    return 0;
}

“`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值