JSOI2009 密码 和 JSOI2007 文本生成器 和 ZOJ3545 Rescue the Rabbit
密码
众所周知,密码在信息领域起到了不可估量的作用。对于普通的登陆口令,唯一的破解 方法就是暴力破解一逐个尝试所有可能的字母组合,但这是一项很耗时又容易被发现的工 作。所以,为了获取对方的登陆口令,在暴力破解密码之前,必须先做大量的准备工作。经 过情报的搜集,现在得到了若干有用信息,形如:
“我观察到,密码中含有字符串***。”
例如,对于一个10位的密码以及串hello与world,可观察到的字符能的密码组合为 helloworld与worldhello;而对于6位的密码以及观察到的字符串good与day,可能的 密码组合为gooday。
有了这些信息,就能够大大地减少尝试的次数了。请编一个程序,计算所有密码组合的可能。密码中仅可能包含a - z之间的小写字母。
对于100%的数据,1<=L<=25,1<=N<=10,每个观察到的字符串长不超过10,并且保证输出结果小于2^63。
LadyLex的题解
我们首先考虑:对于串\(i\)和\(j\),如果\(j\)是\(i\)的子串,那么我们根本不用考虑最初单独插入进来的\(j\)串,因为只要\(i\)串存在,\(j\)串就一定存在
那么我们可以在构建出AC自动机之后,把每个节点从fail指针能达到的节点都设为”不是单词节点“,最后再给单词节点重新编号即可。
那么接下来,我们考虑dp的过程。由于节点数,串数和串长都很小,所以我们考虑状态压缩来解决这个问题。
我们定义状态数组\(f[i][j][k]\)表示当前串长为\(i\),位于\(j\)号节点,模式串出现情况为\(k\)的方案数。
(这种"走\(i\)步到达\(j\)节点”也是AC自动机上的常见套路之一)
那么我们事先把单词节点对应的串用二进制压好,转移到时候我们只需要这样处理即可:
f[i+1][ch[j][u]][k|val[ch[j][u]]]+=f[i][j][k];
这样我们就可以搜出方案数,接下来我们考虑输出小于42的具体方案。
首先我们可以得到一个性质:若总方案数不超过42,那么最终串一定仅由给定串拼接而成。
因为如果随机字母可以存在,哪怕只有1个模式串,并且仅有1个随机字母,合法方案数在这种最小情况下也有2×26=52种>42
因此我们只需要用搜索进行一个dp的逆过程,看合法方案由哪个节点转移过来,并且记录一路上经过的字符,最后排序输出即可。
这真是一道很的题目,方式以及套路很经典,对于状压和搜索的应用都很灵活!
UPD:这题强行组合了两种套路。
时间复杂度\(O(26 L N^2 2^N)\),算出来是7e7。
co int N=11,L=26,K=(1<<10)+10;
int l,n;
char s[N][N];
namespace AC
{
int tot,num;
int ch[N*N][26],fail[N*N];
int val[N*N],meaning[N*N];
void ins(char s[],int n)
{
int u=0;
for(int i=0;i<n;++i)
{
int k=s[i]-'a';
if(!ch[u][k])
ch[u][k]=++tot;
u=ch[u][k],meaning[u]=k;
}
val[u]=1;
}
void getfail()
{
std::queue<int>Q;
for(int i=0;i<26;++i)
if(ch[0][i])
Q.push(ch[0][i]);
while(Q.size())
{
int u=Q.front();Q.pop();
for(int i=0;i<26;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
Q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
for(int i=1;i<=tot;++i)
for(int u=fail[i];u;u=fail[u])
val[u]=0;
for(int i=1;i<=tot;++i)
if(val[i])
val[i]=(1<<num++);
}
ll f[L][N*N][K];
struct sol
{
char s[L];
sol()
{
memset(s,0,sizeof s);
}
void print()
{
puts(s);
}
bool operator<(co sol&b)co
{
for(int i=0;i<l;++i)
if(s[i]!=b.s[i])
return s[i]<b.s[i];
return 0;
}
}stack;
std::vector<sol>str;
void dfs(int len,int i,int state,int now)
{
stack.s[len-1]=now+'a';
if(len==1)
{
str.push_back(stack);
return;
}
for(int j=0;j<=tot;++j)
if(f[len-1][j][state]&&ch[j][now]==i)
dfs(len-1,j,state,meaning[j]);
if(val[i])
for(int j=0;j<=tot;++j)
if(f[len-1][j][state^val[i]]&&ch[j][now]==i)
dfs(len-1,j,state^val[i],meaning[j]);
}
void getsolution()
{
for(int i=1;i<=tot;++i)
if(f[l][i][(1<<num)-1])
dfs(l,i,(1<<num)-1,meaning[i]);
}
void solve()
{
f[0][0][0]=1;
for(int i=0;i<l;++i)
for(int j=0;j<=tot;++j)
for(int k=0;k<(1<<num);++k)
if(f[i][j][k])
for(int u=0;u<26;++u)
f[i+1][ch[j][u]][k|val[ch[j][u]]]+=f[i][j][k];
ll ans=0;
for(int j=0;j<=tot;++j) // edit 1:0
ans+=f[l][j][(1<<num)-1];
printf("%lld\n",ans);
if(ans<=42)
{
getsolution();
sort(str.begin(),str.end());
assert(str.size()==ans);
for(int i=0;i<ans;++i)
str[i].print();
}
}
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(l),read(n);
for(int i=1;i<=n;++i)
{
scanf("%s",s[i]);
AC::ins(s[i],strlen(s[i]));
}
AC::getfail();
AC::solve();
return 0;
}
文本生成器
给出若干个由大写字母构成的单词,问长度为 m ,由大写字母构成的字符串中,包含至少一个单词的数目.对 10007 取模.
jklover的题解
可以先求出不包含任意一个单词的字符串数目,再用总数目26m减去.
将单词建成一个 AC 自动机,类似上题,合并权值即可求出一个节点是否能被走到.
用 \(f[i][j]\) 表示已经走了 \(i\) 步,走到了节点 \(j\) 时的方案数. \(O(n^2)\) dp 即可.
AC自动机上面dp才是AC自动机的精髓。
co int mod=1e4+7;
int add(int x,int y)
{
return (x+y)%mod;
}
int mul(int x,int y)
{
return x*y%mod;
}
int qpow(int x,int k)
{
int res=1;
while(k)
{
if(k&1)
res=mul(res,x);
x=mul(x,x),k>>=1;
}
return res;
}
co int N=7777,S=26;
int n,m;
namespace AC
{
int idx;
int ch[N][S],fail[N],val[N];
int f[101][N];
void init()
{
memset(f,-1,sizeof f);
}
void ins(char*s,int len)
{
int u=0;
for(int i=0;i<len;++i)
{
int k=s[i]-'A';
if(!ch[u][k])
ch[u][k]=++idx;
u=ch[u][k];
}
val[u]=1;
}
void getfail()
{
std::queue<int>Q;
for(int i=0;i<S;++i)
if(ch[0][i])
Q.push(ch[0][i]);
while(Q.size())
{
int u=Q.front();Q.pop();
for(int i=0;i<S;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
Q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
val[u]|=val[fail[u]];
}
}
int dfs(int i,int j)
{
if(f[i][j]!=-1)
return f[i][j];
if(val[j])
return 0;
if(i==m)
return 1;
int&res=f[i][j]=0;
for(int k=0;k<S;++k)
res=add(res,dfs(i+1,ch[j][k]));
return res;
}
void solve()
{
int ans=qpow(26,m);
ans=add(ans,mod-dfs(0,0));
printf("%d\n",ans);
}
}
char buf[N];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
AC::init();
read(n),read(m);
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
AC::ins(buf,strlen(buf));
}
AC::getfail();
AC::solve();
return 0;
}
Rescue the Rabbit
现在有n个基因片段(用包含A、G、T、C的字符串表示),每个基因片段有一个权值,现在求长为L的基因的最大权值(每个基因片段重复出现算一次,不用计算多次)?
n (1 ≤ n ≤ 10),l (1 ≤ l ≤ 100)
分析
未知定长串中不同已知模板串的出现次数问题,一般做法是AC自动机上dp。
考虑背包,\(dp(i,j,k)\)表示当前串长为\(i\),在AC自动机上对应节点\(j\),已匹配的模板串的状态为\(k\)的情况是否出现。用刷表法向后转移。先枚举不定串长度,再枚举AC自动机上节点,然后枚举已知状态,最后枚举字母边转移。
时间复杂度\(O(l \cdot MaxNode \cdot 2^n \cdot SigmaSize)\)。第一维可以滚动,空间复杂度\(O(MaxNode \cdot 2^n)\)
const int MAXN=1010;
const int SigmaSize=4;
bool dp[2][MAXN][1100];
int mp[15];
struct Trie
{
int next[MAXN][SigmaSize];
int fail[MAXN];
int end[MAXN];
int root,ncnt;
int newnode()
{
for(int i=0;i<SigmaSize;++i)
next[ncnt][i]=-1;
end[ncnt++]=0;
return ncnt-1;
}
void init()
{
ncnt=0;
root=newnode();
}
int id(char c)
{
if(c=='A')
return 0;
else if(c=='G')
return 1;
else if(c=='T')
return 2;
else
return 3;
}
void insert(char*str,int v)
{
int now=root;
int len=strlen(str);
for(int i=0;i<len;++i)
{
int c=id(str[i]);
if(next[now][c]==-1)
next[now][c]=newnode();
now=next[now][c];
}
end[now]|=(1<<v);
}
void getfail()
{
queue<int>Q;
fail[root]=root;
for(int i=0;i<SigmaSize;++i)
{
if(next[root][i]==-1)
next[root][i]=root;
else
{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
}
while(!Q.empty())
{
int now=Q.front();
Q.pop();
end[now]|=end[fail[now]];
for(int i=0;i<SigmaSize;++i)
{
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else
{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
}
void solve(int n,int l)
{
memset(dp,0,sizeof(dp));
dp[0][0][0]=1; // dp[len][node][state]
int cur=1;
for(int i=1;i<=l;++i)
{
memset(dp[cur],0,sizeof(cur));
for(int j=0;j<ncnt;++j)
{
for(int k=0;k<(1<<n);++k)
{
for(int q=0;q<SigmaSize;++q)
{
int nxt=next[j][q];
dp[cur][nxt][k|end[nxt]]=(dp[cur][nxt][k|end[nxt]]||dp[cur^1][j][k]);
}
}
}
cur^=1;
}
int ans=-INF;
for(int i=0;i<ncnt;++i)
for(int j=0;j<(1<<n);++j)
{
if(dp[cur^1][i][j])
{
int sum=0;
for(int k=0;k<n;++k)
if(j&(1<<k))
sum+=mp[k];
ans=max(ans,sum);
}
}
if(ans<0)
printf("No Rabbit after 2012!\n");
else
printf("%d\n",ans);
}
}AC;
char s[110];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int n,l,w;
while(~scanf("%d %d",&n,&l))
{
AC.init();
for(int i=0;i<n;++i)
{
scanf("%s %d",s,&w);
AC.insert(s,i);
mp[i]=w;
}
AC.getfail();
AC.solve(n,l);
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
JSOI2009 密码 和 JSOI2007 文本生成器 和 ZOJ3545 Rescue the Rabbit的更多相关文章
- BZOJ 1030: [JSOI2007]文本生成器 [AC自动机 DP]
1030: [JSOI2007]文本生成器 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 3953 Solved: 1614[Submit][Stat ...
- bzoj1030 [JSOI2007]文本生成器
1030: [JSOI2007]文本生成器 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 2654 Solved: 1100[Submit][Stat ...
- JSOI2007文本生成器
1030: [JSOI2007]文本生成器 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 1613 Solved: 656[Submit][Statu ...
- BZOJ 1030 [JSOI2007]文本生成器
1030: [JSOI2007]文本生成器 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 2624 Solved: 1087[Submit][Stat ...
- 2781: [JSOI2007]文本生成器
2781: [JSOI2007]文本生成器 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 8 Solved: 4[Submit][Status][We ...
- BZOJ_1030_[JSOI2007]文本生成器_AC自动机+DP
BZOJ_1030_[JSOI2007]文本生成器_AC自动机+DP Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群, 他 ...
- P4052 [JSOI2007]文本生成器
P4052 [JSOI2007]文本生成器 AC自动机+dp 优秀题解传送门 设f[ i ][ j ]表示串的长度为 i ,当前在 j 点时不可识别的串的方案数 最后用总方案数减去不可识别方案数就是答 ...
- 【BZOJ1030】[JSOI2007]文本生成器 AC自动机+动态规划
[BZOJ1030][JSOI2007]文本生成器 Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文 ...
- [JSOI2007]文本生成器 --- AC自动机 + DP
[JSOI2007]文本生成器 题目描述: JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版. 该软件可以随机 ...
随机推荐
- 如何优雅的处理 async/await 异常
参考链接:https://cloud.tencent.com/developer/article/1470715 参考链接:https://www.jianshu.com/p/2935c0330dd2
- centos7 install docker
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo y ...
- [转帖]Dockerfile: ENTRYPOINT和CMD的区别
Dockerfile: ENTRYPOINT和CMD的区别 https://zhuanlan.zhihu.com/p/30555962 在我们查阅Dockerfile的官方文档时, 有可能发现一些命令 ...
- java当中JDBC当中请给出一个sql server的helloworld例子
[学习笔记] 1.sql server的helloworld例子: import java.sql.*; public class JdbcHelloSqlServer { public stati ...
- 在CentOS7上安装OpenJDK1.8 & OracleJDK1.8
安装OpenJDK1.8 : 1.检查当前机器是否有自带的JDK rpm -qa |grep java rpm -qa |grep jdk rpm -qa |grep gcj 2.如果没有 则跳至安装 ...
- 使用Lombok总结
Lombok学习总结 Project Lombok is a java library that automatically plugs into your editor and build tool ...
- 数据结构:队列queue 函数push() pop size empty front back
队列queue: push() pop() size() empty() front() back() push() 队列中由于是先进先出,push即在队尾插入一个元素,如:可以输出:Hello W ...
- 机器学习-EM算法-GMM模型笔记
GMM即高斯混合模型,下面根据EM模型从理论公式推导GMM: 随机变量X是有K个高斯分布混合而成,取各个高斯分布的概率为φ1,φ2,... ,φK,第i个高斯分布的均值为μi,方差为Σi.若观测到随机 ...
- Kafka 的这些原理你知道吗
如果只是为了开发 Kafka 应用程序,或者只是在生产环境使用 Kafka,那么了解 Kafka 的内部工作原理不是必须的.不过,了解 Kafka 的内部工作原理有助于理解 Kafka 的行为,也利用 ...
- shellexecute的使用和X64判断
bool RunConsoleAsAdmin(std::string appPath, std::string param, bool wait) { LOG_INFO << " ...