题面

已知一个 1 ∼ N 的排列的最长上升子序列长度为 K ,求合法的排列个数。

好题(除了我想不出来我应该找不到缺点), 想一想最长上升子序列的二分做法, 接在序列后面或者替换.

所以对于每一个位置的数, 都只有三种状态, 分别是: 在我们的替换的序列中, 使用过却被序列抛弃的, 未使用过的.

考虑到数据范围比较小, 我们可以使用3进制的状压dp, 0代表未使用过, 1代表被抛弃的, 2表示在替换序列中的.

但是由于这是一个1到n的排列, 所以每个数都只能使用一次, 我们还要记录每个数是否使用过, 继续考虑状压dp, 0代表使用过, 1代表未使用.

因为有两个状压dp, 个人觉得记忆化搜索比较好写, 记忆化搜索有三个参数, \(dfs(s, t, len)\), \(s\)代表替换的序列的状态, \(t\)代表可以使用的数的状态, len表示当前可替换序列的长度, 下面根据dfs代码分析:

long long dfs(int s, int t, int len)//状态已经在上面讲过了, 初始s等于0, 代表替换序列中没有数, 毕竟还没有开始选对不对, t初始为(1 << n) - 1, 既然还没开始选当然每个数都可以选啦, len为长度, 初始为0, 理由仍然是还没有开始选
{
if(t == 0) return len == k; //由于每个位置都只能选一个数, 所以选完了就代表当前序列构造完了
if(f[s] != -1) return f[s]; //当前状态已出现过, 即曾经选过的某些数, 抛弃过某些数的情况曾经出现过, 可以直接用了, 这也是记忆化搜索的精髓
f[s] = 0; //开始选了, 上面写-1而不是0的原因是有些情况并没有合法的序列, 此时f[s]为0, 会重复搜索
int rt = t;//复制一下, 你不可能直接减t对不
int pos = 0; //可以替换的位置
while(rt)
{
int p = rt & (-rt); //取可以使用的数
rt -= p; //减一下, 不然下次减掉还是这个数, 取取弹弹无穷尽也......
while(pos < len && stack[pos + 1] <= num[p]) pos++; //由于你是从t的二进制表示的低位往高位走, 即取的数是从小到大的, 那么当前放的位置肯定不会比上次放的位置前面, 注意题面, 为上升子序列就要放在更后面才能满足上升, 自己想一下吧, 可能解释的不太清楚
int rem = stack[pos + 1]; //记录一下
stack[pos + 1] = num[p]; //替换
f[s] += dfs(s + 2 * pow[num[p]] - pow[rem], t - p, len + (pos == len)); //往下搜索
stack[pos + 1] = rem; //回溯
}
return f[s]; //return
}

还有一些小细节, 比如说由于二进制的第一位是(1 or 0 << 0), 所以我们将1到n的序列变为0到n-1的序列, 这里在代码中会讲.

代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std; int n, k, pow[20], num[1 << 15], stack[20];
long long f[15000000]; inline int read()
{
int x = 0, w = 1;
char c = getchar();
while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * w;
} long long dfs(int s, int t, int len)
{
if(t == 0) return len == k;
if(f[s] != -1) return f[s];
f[s] = 0;
int rt = t;
int pos = 0;
while(rt)
{
int p = rt & (-rt);
rt -= p;
while(pos < len && stack[pos + 1] <= num[p]) pos++;
int rem = stack[pos + 1];
stack[pos + 1] = num[p];
f[s] += dfs(s + 2 * pow[num[p]] - pow[rem], t - p, len + (pos == len));
stack[pos + 1] = rem;
}
return f[s];
} int main()
{
n = read(); k = read();
memset(f, -1, sizeof(f));
for(int i = 0; i < n; i++) { num[1 << i] = i; pow[i] = i ? pow[i - 1] * 3 : 1; }//这里就是上面所说的小技巧
printf("%lld\n", dfs(0, (1 << n) - 1, 0));
return 0;
}

还有哪些不懂的可以在下面问一下...

CJOJ 【DP合集】最长上升序列2 — LIS2的更多相关文章

  1. 9.15 DP合集水表

    9.15 DP合集水表 显然难了一些啊. 凸多边形的三角剖分 瞄了一眼题解. 和蛤蛤的烦恼一样,裸的区间dp. 设f[i][j]表示i~j的点三角剖分最小代价. 显然\(f[i][i+1]=0,f[i ...

  2. 9.14 DP合集水表

    9.14 DP合集水表 关键子工程 在大型工程的施工前,我们把整个工程划分为若干个子工程,并把这些子工程编号为 1. 2. --. N:这样划分之后,子工程之间就会有一些依赖关系,即一些子工程必须在某 ...

  3. dp合集 广场铺砖问题&&硬木地板

    dp合集 广场铺砖问题&&硬木地板 很经典了吧... 前排:思想来自yali朱全民dalao的ppt百度文库免费下载 后排:STO朱全民OTZ 广场铺砖问题 有一个 W 行 H 列的广 ...

  4. 题解 最长上升序列2 — LIS2

    最长上升序列2 - LIS2 Description 已知一个 1 ∼ N 的排列的最长上升子序列长度为 K ,求合法的排列个数. Input 输入一行二个整数 N , K ( K ≤ N ≤ 15) ...

  5. 【CJOJ2498】【DP合集】最长上升子序列 LIS

    题面 Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的n个整数组成的序列 ...

  6. 【DP合集】m-knapsack

    给出 n 个物品,第 i 个物品有重量 w i .现在有 m 个背包,第 i 个背包的限重为 c i ,求最少用几个背 包能装下所有的物品. Input 输入的第一行两个整数 n, m ( n ≤ 2 ...

  7. 【DP合集】合并 union

    给出一个 1 ∼ N 的序列 A ( A 1 , A 2 , ..., A N ) .你每次可以将两个相邻的元素合并,合并后的元素权值即为 这两个元素的权值之和.求将 A 变为一个非降序列,最少需要多 ...

  8. 【DP合集】tree-knapsack

    Description 给出一个 N 个节点的有根树,点编号 1 ∼ N ,编号为 i 的点有权值 v i .请选出一个包含树根的,点数 不超过 K 的连通块,使得点权和最大. Input 输入的第一 ...

  9. 【DP合集】背包 bound

    N 种物品,第 i 种物品有 s i 个,单个重量为 w i ,单个价值为 v i .现有一个限重为 W 的背包,求能容 纳的物品的最大总价值. Input 输入第一行二个整数 N , W ( N ≤ ...

随机推荐

  1. JOffice中的权限管理--功能粒度的权限管理配置

    JOffice中的权限管理是基于角色的管理策略,采用Spring Security2的配置方式,同时能够结合EXT3来进行整个系统的权限管理,通过使用配置文件,进行整个系统的功能集中管理,包括系统左边 ...

  2. [日常] Go语言圣经--复合数据类型,数组习题

    go语言圣经-复合数据类型 1.以不同的方式组合基本类型可以构造出来的复合数据类型 2.四种类型——数组.slice.map和结构体 3.数组是由同构的元素组成——每个数组元素都是完全相同的类型——结 ...

  3. 跨域CORS 头缺少 'Access-Control-Allow-Origin'

    今天遇到一个跨域的问题找了好久的资料错误如下: 解决之后: 控制层 加上这两行代码就好啦: @RequestMapping(value = "",method = RequestM ...

  4. Hashmat the brave warrior(UVa10055)简单题

    Problem A Hashmat the brave warrior Input: standard input Output: standard output Hashmat is a brave ...

  5. python 判断字符串是字母 数字 大小写还是空格

    str.isalnum()  所有字符都是数字或者字母,为真返回 Ture,否则返回 False. str.isalpha()   所有字符都是字母(当字符串为中文时, 也返回True),为真返回 T ...

  6. luogu P4108 [HEOI2015]公约数数列——solution

    -by luogu 不会啊.... 然后%了一发题解, 关键是 考虑序列{$a_n$}的前缀gcd序列, 它是单调不升的,且最多只会改变$log_2N$次,因为每变一次至少除2 于是,当我们询问x时: ...

  7. C#/Net定时导出Excel并定时发送到邮箱

    一.定时导出Excel并定时发送到邮箱   首先我们先分析一下该功能有多少个小的任务点:1.Windows计划服务 2.定时导出Excel定指定路径 3.定时发送邮件包含附件   接下来我们一个个解决 ...

  8. Permission denied (publickey),Gitlab & Github 多ssh key 冲突 导致的权限问题

    Github 多ssh key导致的权限问题 :Permission denied (publickey) 公司用gitlib搭建了git服务器,自己已有github账号,用ssh-keygen分别生 ...

  9. Git创建本地仓库并推送至远程仓库

    作为一名测试同学,日常工作经常需要checkout研发代码进行code review.自己极少有机会创建仓库,一度以为这是一个非常复杂过程.操作一遍后,发现也不过六个步骤,so,让我们一起揭开这神秘面 ...

  10. 如何让VB6代码编辑器垂直滚动条随鼠标滚轮滚动

    VB6毕竟是很老的产品了,它的代码编辑器垂直滚动条并不能随鼠标的滚轮而滚动,这个问题会让我们在编写代码的时候觉得很不方便,不过还是有一种方法可以解决这个问题的.    先下载一个微软发布的“VB6ID ...