[SHOI2013]阶乘字符串

题目描述

给定一个由前\(n\)个小写字母组成的串\(S\)。
串\(S\)是阶乘字符串当且仅当前\(n\)个小写字母的全排列(共\(n!\)种)都作为\(S\)的子序列(可以不连续)出现。
由这个定义出发,可以得到一个简单的枚举法去验证,但是它实在太慢了。所以现在请你设计一个算法,在\(1\)秒内判断出给定的串是否是阶乘字符串。

输入格式

输入第\(1\)行一个整数\(T\),表示这个文件中会有\(T\)组数据。
接下来分\(T\)个块,每块\(2\)行:
第\(1\)行一个正整数\(n\),表示\(S\)由前\(n\)个小写字母组成。
第\(2\)行一个字符串\(S\)。

输出格式

对于每组数据,分别输出一行。每行是\(YES\)或者\(NO\),表示该数据对应的串\(S\)是否是阶乘字符串。

样例输入

2
2
bbaa
2
aba

样例输出

NO
YES

【样例解释】

第一组数据中,ab这个串没有作为子序列出现。

数据范围

\[
N \leq 26 \\\T \leq 5 \\\|S| \leq 450 \\\\]

解题报告

题意理解

这道题目,有一点点绕? 可能是我太菜了

我们初步读题可知,题目要求我们判断一个字符串.

给你一个\(N\),如果说一个字符串满足\(N\)的全排列字符串

而且这些字符串,都以序列的形式出现在这个字符串,那么我们称之为合法,否则不合法.

算法解析

这道题目运用的是.状态压缩DP.

首先我们思考一下,这道题目\(N \leq 26\),这个数据范围似乎不太好状态压缩?

数据太大了....

但是我们发现,其实\(N \ge 21\),完全可以判断无解.

这是为什么,有证明吗?

当\(n \ge 21\)的时候

假设\(|S| = 450\)

在|S|中任意取21个数字

$ C(450, 21) < 21!$

说明这\(450\)个字符不能完全凑成\(n!\)个序列。



接下来我们着重分析一下,状态压缩思想.

我们知道状态压缩其实就是 集合二进制枚举 处理.

那么既然如此,我们不妨设置一下 状态表示.

  1. 状态是一个 集合
  2. 题目要求 全排列合法
  3. 一般题目中,总会有 最后一位,也就是转移过来的元素

设\(f[S]\)表示当\(S\)中集合中的字母构成的排列均在原序列\([1,f[S]]\)出现的最小值。

既然如此的话.

我们不妨预处理一下.

\(g[i][j]\)表示从\(i\)开始下一个字母\(j\)出现的位置。

总而言之,我们就是利用 刷表法则,一步步推导状态.

因此我们不妨设置核心程序.

for(int S=1; S<(1<<n); S++)//枚举子集
{
    int cnt=0;
    for(int i=0; i<n; i++)
        if(S & (1<<i) ) //s集合拥有这一位,其实也就是i结尾
            cnt=max(cnt,g[f [S^(1<<i) ]][i] );//排除这一位,然后转移过来
    f[S]=cnt;//更新
}

代码解析

#include <bits/stdc++.h>
using namespace std;
const int N=460;
#define read(x) scanf("%d",&x)
int t,n,m,g[N][32],f[1<<21];
char s[N];
inline void init()
{
    read(t);
    while(t--)
    {
        read(n);
        scanf("%s",s+1);//默认读入从1开始
        m=strlen(s+1);
        if (n>21)//特殊判定无解情况
        {
            puts("NO");
            continue;
        }
        for(int i=m+1; i>=0; i--)//从i开始下一个j出现的位置
        {
            for(int j=0; j<n; j++)
                g[i][j]=( i>=m ? m+1 : g[i+1][j] ); //前面的位置,最近的是当前这位的
            if(i!=m)
                g[i][ s[i+1]-'a' ]=i;//当前位为最近的
        }
        for(int S=1; S<(1<<n); S++)//枚举子集
        {
            int cnt=0;
            for(int i=0; i<n; i++)
                if(S & (1<<i) ) //s集合拥有这一位,其实也就是i结尾
                    cnt=max(cnt,g[f [S^(1<<i) ]][i] );//排除这一位,然后转移过来
            f[S]=cnt;//更新
        }
        printf("%s\n",f[ (1<<n)-1 ] <=m ? "YES":"NO" );//是否存在
    }
}
int main()
{
    init();
    return 0;
}

原文地址:https://www.cnblogs.com/gzh-red/p/11478482.html

时间: 2024-10-16 03:52:23

[SHOI2013]阶乘字符串的相关文章

bzoj4416: [Shoi2013]阶乘字符串

可以大胆猜想n>21时无解,至于依据,不开O2,1s,n<=21刚好能卡过去= = 并不会证= = #include<cstdio> void up(int& a,int b){ a=a<b?b:a; } int test,n,m,i,j; char t[500]; int f[1<<21],s[500][21]; int main(){ scanf("%d",&test); while(test--){ scanf("

BZOJ 4416 【SHOI2013】 阶乘字符串

题目链接:阶乘字符串 又是一道不会做的题--看了题解后我被吓傻了-- 首先我们可以有一个显然的\(O(2^nn)\)的做法.我们先预处理出\(g_{i,j}\)表示字符串中\(i\)号位置开始第一个\(j\)字符出现在什么位置.然后就可以用\(f_S\)表示使得\(S\)集合内字符的排列全都出现的最小长度,然后就可以递推了. 然后--翻了一波题解,发现当\(n>21\)的时候无解--听说合法的串长应该是\(n^2\)级别的,所以当\(n>21\)的时候就无解了--然后就可以\(O(2^nn)\

大数据阶乘——字符串乘法器

char s[1001]; //字符串s存储乘法得到的大数字,s[0]代表低位 int GetLength()//返回s的最大不为空的元素下标 { int i; for(i=1000; i>=0; i--) { if(s[i]!='0') { break; } } return i; } void cal(int k)//计算大数字s和小数字k相乘 { int add=0;//进位数 int i; int temp; for(i=0; i<=GetLength(); i++)// { temp

BZOJ 4416 阶乘字符串

状压dp.状态有点难想,方程十分简单. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 455 using namespace std; int t,n,g[maxn][23],f[(1<<22)+5],len,regis[23]; char s[maxn]; void work() { memset(g,0,sizeof

计数类问题专题

主要是前两天被uoj的毛爷爷的题虐的不轻,心里很不爽啊,必须努力了,, 计数类问题分为:1.组合数学及数论计数 2.dp:状态压缩dp,插头轮廓线dp,树形dp,数位dp,普通dp 3.容斥原理 4.polya原理 5.图论计数 6.生成函数 7.其它(生成树计数等等) 本文主要研究前3个内容 考虑基本计数原理:加法原理,减法原理,乘法原理,除法原理 计数的基本原则:结果不重不漏 加法原理比较自然,中间过程有时减法原理 考虑到无向,有向图的各种量值(生成树之类)计数,状态压缩dp解决 论文:ht

递归函数之阶乘和字符串反转-基于R和Python

Python课第五周开始讲函数了.递归函数.递归在python中不能超过900多层,否则报错内存溢出什么的.同样在R中递归太深也会报错,阈值和python中大概一样,900多次就报错了. error message: 错误: 评估嵌套太深:无穷递归/ options(expressions=)?收捲时出错: 评估嵌套太深:无穷递归/ options(expressions=)? 基于Python # 递归函数 阶乘 def fact(n): if n==0: return 1 else: ret

计算 num 的 n 次幂、n 的阶乘、斐波那契数列、字符串的字节长度、去除字符串中的重复字符

1 //计算 num 的 n 次幂 2 function numPow(num, n) { 3 if (n == 1) { 4 return num; 5 } 6 return num * numPow(num, n - 1); 7 } 8 9 //计算 n 的阶乘 10 function nFactorial(n) { 11 if (n == 1) { 12 return 1; 13 } 14 return n * nFactorial(n - 1); 15 } 16 17 //斐波那契数列,

NYOJ139 我排第几个(字符串的解码)

题目信息:http://acm.nyist.net/JudgeOnline/problem.php?pid=139 现在有"abcdefghijkl"12个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的? 输入 第一行有一个整数n(0<n<=10000); 随后有n行,每行是一个排列: 输出 输出一个整数m,占一行,m表示排列是第几位: 样例输入 3 abcdefghijkl hgebkflacdji gfkedhjblcia 样

字符串中各字符出现次数的统计

要求:统计一个字符串中 各字符串出现的次数.比如 "a1a35b5" 统计出来应该是 a-2, 1-1, 3-1, b-1, 5-2. 思想一:用split方法将key字符切除,比较源字符串长度和新字符串长度,得出key字符个数. 图例: 代码: 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <