题解 P3417 【[POI2005]BANK-Cash Dispenser】
oscar
2017-09-08 22:04:10
【POI补全计划#1-POI2005 BAN】
题意花了半个小时才理解。。可能是我语文太差了
为了防止大家理解不了,我来几句话描述一下
定义一串数字的位置序列是将连续的相同数字只留一个后的结果
举例:2333的位置序列为23,552992999的位置序列为52929
给定 $ n $ 个位置序列( $ n \leq 1000 $,序列长度 $ \leq 10000 $ ),
求十进制4位数的个数,使得该密码的位置序列是每个给定位置序列的子串(可以不连续)
然后解释一下样例
$ n = 2 $
给出的位置序列为123和234
找出公共部分为23
那么可能的4位数就为2222,2223,2233,2333,3333,共5种
--------------题解分割线---------------
考虑每个四位数,只需要判断它的位置序列是不是所有给定位置序列的子串
用10个队列预处理出 “给定的位置序列的第 $ k $ 位(含第 $ k $ 位)后第一次出现数字 $ i $ 的位置,若不存在则为 $ -1 $ ” 的信息,
记录在NEXT[10][1000010]数组里
这样就可以 $ O(1) $ 查询一个不超过4位的位置序列是否是给定的位置序列的子串了
只需要对于每个输入位置序列标记一下不满足条件的4位数,
最后统计没有标记的4位数个数就好了
时间复杂度 $ O( \sum{t} * 10 + 10000 * n ) $
具体实现可能需要卡常,不过我没卡常就过了
AC代码见下
```cpp
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int NEXT[10][10010];
queue<int> num[10];
char buf[23333];
int ok[10000];
int main()
{
for(int i=0;i<10000;i++)ok[i]=true;
int n,tmp;
scanf("%d",&n);
for(int t=1;t<=n;t++)
{
scanf("%d %s",&tmp,buf);
int len=strlen(buf);
for(int i=0;i<len;i++)
{
num[buf[i]-'0'].push(i);
}
for(int i=0;i<len;i++)
{
for(int k=0;k<10;k++)
{
if(num[k].empty())NEXT[k][i]=-1;
else NEXT[k][i]=num[k].front();
//cout<<NEXT[k][i]<<' ';
}
num[buf[i]-'0'].pop();
//cout<<endl;
}
int i,j,k,l;
for(int r=0;r<10000;r++)
{
if(ok[r])
{
i=r/1000,j=r/100-i*10,k=r/10-i*100-j*10,l=r%10;
//cout<<i<<j<<k<<l<<endl;
if(NEXT[i][0]==-1||NEXT[j][NEXT[i][0]]==-1||NEXT[k][NEXT[j][NEXT[i][0]]]==-1||NEXT[l][NEXT[k][NEXT[j][NEXT[i][0]]]]==-1)
ok[r]=false;
}
}
}
int ans=0;
for(int i=0;i<10000;i++)
if(ok[i])
++ans;
printf("%d\n",ans);
return 0;
}
```