与dalao学校的联shou考wan(11.2)(dp+乱搞+树形dp+期望)

61 篇文章 0 订阅
44 篇文章 0 订阅

T1.hanoi

题目描述
众所周知, 汉诺塔是一个古老又经典的游戏. 这个游戏是这样的, 你有 N 个大小不同的盘子和3 根柱子, 一开始所有盘子都叠放在第 1 根柱子上, 你需要把N 个盘子全都移动到第3根柱子上, 每次都可以选择某根柱子最上面的盘子移动到另一根柱子上, 但是任何时候都必须保证没有一个盘子上面放了一个比它大的盘子. 求最少的移动步数.
这个问题太简单了, 乐于寻找挑战的你想要求出当有N 个盘子, M 个柱子且其他条件不变时, 把所有盘子从第1 根柱子移动到第M根柱子的最少步数.

输入格式
一行两个整数分别代表题目中的 N, M.
输出格式
一行一个整数代表答案.

样例
hanoi.in
5 3
hanoi.out
31

数据范围
对于10%的数据, N <= 20, M = 3.
对于30%的数据, M = 3.
对于50%的数据, M <= 4.
对于100%的数据, N <= 63, 3 <= M <= N + 1;

分析:
以前做过汉诺塔变式
所以当M=3的时候,秒出答案:2^N-1

考试的时候玄学卡过70
提交之后听男生们说是O(N^2M)的做法(yhzq还有O(N)做法,%%%)

说正解之前,先看一下我在考场上写的:

else if (m==4)
{
    if (n<=6) printf("%d\n",2*m-3+(n-m+1)*4);
    else 
    {
        ll ans=(ll)(1<<(n-3));
        ans+=9;
        printf("%llu\n",ans);
    }
}

我发现当只有3个盘子的时候,我们可以很简单的完成操作:
这里写图片描述
于是我就手玩了M=4,N<=6的情况,发现是一个等差数列,正当我以为我发现了天地真理的时候,我发现N=7的情况下不符合等差了(答案是25)
但是再快提交的最后十分钟,我发现对于超过6个盘子的情况,都可以通过以下方式得到最优解:
这里写图片描述

实际上这已经提示我们正解了:

设f[i][j]表示有i个盘子,利用j个柱子把ta们有序的排列在一个柱子上的最小步数

我们需要把i个盘子上的k个盘子利用j个柱子(其他的盘子都比ta们大,不影响)移动到另外的一个柱子上
把剩下的盘子利用j-1个柱子(有一个被前k个盘子占了)移动到第j个柱子上,
之后再把那k个也移动到第j个柱子上

f[i][j]=min{f[k][j-1]+f[i-k][j-1]+f[k][j-1]}=min{f[k][j-1]*2+f[i-k][j-1]}

注意枚举顺序:
我们先枚举盘子的个数
不要忘了f[1][j]=1

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll unsigned long long

using namespace std;

int n,m;
ll f[100][100];

int main()
{
    //freopen("hanoi.in","r",stdin);  
    //freopen("hanoi.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        f[i][3]=(1ULL<<i)-1;                   //注意写法,不然会爆掉 
    // f[i][3]=(ll)1<<i,f[i][3]--;   这样也行,但是不要随意就把两个计算合在一起

    for (int j=4;j<=m;j++)
    {
        f[1][j]=1;                             //1~n都要计算 
        for (int i=2;i<=n;i++)
        {
            f[i][j]=(ll)0x7fffffffffffffff+1;
            for (int k=1;k<i;k++)              //k<i
                f[i][j]=min(f[i][j],2*f[k][j]+f[i-k][j-1]);
        }
    }

    printf("%llu",f[n][m]);
    return 0;
}

T2.rank

题目描述
刚刚学会后缀数组的你(不会的先不要着急)在做完模板题”求后缀排名”之后把输入文件弄丢了, 只留下了输出文件, 作为一个强迫症患者, 你一定要找出一个输入文件能够对应上你的输出文件.
题目是这样的: 给出一个只由小写字母(‘a’-‘z’)组成的字符串的每个后缀的排名(也就是这
个后缀在所有后缀中字典序排第几), 你需要找到一个满足这个排名的原串, 原串也只能由小
写字母组成.如果有多个串满足条件, 输出字典序最小的串, 如果无解, 输出-1.

输入格式
第一行一个整数N, 代表字符串的长度. 接下来一行N 个整数, 第i个数代表以i 开头的
后缀的排名, 保证输入的数是一个排列.
输出格式
如果无解,输出-1.
否则输出一个长度为 N 的字符串代表答案.

样例
rank.in
3
1 2 3
rank.out
aab

数据范围
对于20%的数据, N <= 10.
对于50%的数据, N <= 1000.
对于100%的数据, N <= 200000.

分析:
考试的时候,我一直以为这是道图论(就像差分约束之类的),但是乱搞出来只过了一个点

实际上就是一道乱搞:
对于后缀排名最小的那个位置, 不妨令ta为 a
为了让构造出来的字符串字典序最小,我们本能的想到要让使用的最大字符尽量小
也就是说,能够相同的字符就让ta们相同好了

那么什么样的字符能够相同呢?

对于两个排名相邻的位置i, j,rank[i] < rank[j],
要使i,j的字符相同但是排名有先后之分,一定是ta们后面的某个字符造成的
如果rank[i+1] < rank[j+1],那么i和j位置上的字符就能够相同, 否则不能
那么从排名为1的往后推就可以了
因为我们在推的时候遵循的就是一种贪心的原则,所以构造出来的就是字典序最小的解

无解有两种情况:

  • 原本已经确定的答案和之后的条件不符
  • 为了构成输入数据,需要的字符超过26种
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=200010;
int n,rak[N];
struct node{
    int bh,k;
    bool operator < (const node &a) const
    {
        return k<a.k;
    }
};
node a[N];
int ans[N];

int main()
{
    //freopen("rank.in","r",stdin);  
    //freopen("rank.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&rak[i]);
        a[i].bh=i; a[i].k=rak[i];
    }

    sort(a+1,a+1+n);  

    memset(ans,0x33,sizeof(ans));
    ans[a[1].bh]=1;
    for (int i=2;i<=n;i++)
    {
        int pre=a[i-1].bh;
        int nxt=a[i].bh;

        if (rak[pre]<rak[nxt]&&ans[pre]>ans[nxt])      //答案与条件相矛盾
        {
            printf("-1");
            return 0;
        }

        if (rak[pre+1]<rak[nxt+1])          //不用担心+1之后>n,因为rak[编号大于n的元素]=0 
            ans[nxt]=ans[pre];
        else ans[nxt]=ans[pre]+1;

        if (ans[nxt]>26)                   //需要的字符大于26种
        {
            printf("-1");
            return 0;
        }  
    }  

    for (int i=1;i<=n;i++)
        printf("%c",'a'+ans[i]-1);
    return 0;
}

T3.tree

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值