Remember the Word
UVA - 1401
算法:Tire树,DP
注释:
- 本题是一道DP,可以使用Tire树进行优化,正序的思考方式很容易想到DFS加一个记忆化搜索,由此可以转化成逆序方向的DP,d[i]表示以第i个字符为起点,可以分解的策略数。
- 状态转移方程d[i]=sum{d[i+len(x)]},单词x是S[i…L]的前缀.
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define ll long long
#define MAXN 26
#define MOD 20071027
#define N 3000010
#define S 4010
using namespace std;
struct Tire{
bool isStr;
Tire *next[MAXN];
Tire()
{
this->isStr=0;
for(int i=0;i<MAXN;i++)
this->next[i]=NULL;
}
};
ll cnt=0,d[N];
char s[N],dan[110];
void insert(Tire *root,const char *s)
{
if(root==NULL||*s=='\0')
return;
Tire *p=root;
while(*s!='\0')
{
if(p->next[*s-'a']==NULL)
{
Tire *temp=new Tire();
p->next[*s-'a']=temp;
cnt++;
}
p=p->next[*s-'a'];
s++;
}
p->isStr=1;
}
void dfs(Tire *root)
{
if(root==NULL)return ;
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
//cout<<(char)(i+'a')<<" "<<root->next[i]->isStr<<endl;
dfs(root->next[i]);
}
}
void del(Tire *root)
{
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
del(root->next[i]);
}
delete root;
}
int sum(Tire *root,const char *s,int t)
{
Tire *p=root->next[s[t]-'a'];
ll tot=0;
while(p!=NULL&&s[t]!='\0')
{
if(p->isStr)
tot+=d[t+1];
t++;
if(s[t]!='\0')p=p->next[s[t]-'a'];
}
return tot%MOD;
}
int main()
{
int t=0,n;
while(scanf("%s",s)!=EOF)
{
scanf("%d",&n);
Tire *root=new Tire();
memset(d,0,sizeof(d));
for(int i=0;i<n;i++)
{
scanf("%s",dan);
insert(root,dan);
}
//dfs(root);
d[strlen(s)]=1;
for(int i=strlen(s)-1;i>=0;i--)
{
d[i]=sum(root,s,i);
//cout<<d[i]<<" ";
}
printf("Case %d: %d\n",++t,d[0]);
del(root);
}
return 0;
}
“strcmp()” Anyone?
UVA - 11732
算法:Tire树
注释:
- 暴力一个一个的比较会超时,考虑到有许多多余的比较,所以我们可以建立一个Tire数来减少比较次数。
- 当比较新一个字符时只需要遍历一遍字符即可,在Tire树的每个节点维护一个val代表经过此节点的字符数,插入一个字符串的时候遇到分支点进行一次计算,最后的位置进行一次计算。
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 4010
#define MAXN 63
using namespace std;
struct Tire{
int isEnd,val;
Tire* next[MAXN];
Tire()
{
isEnd=val=0;
for(int i=0;i<MAXN;i++)
next[i]=NULL;
}
};
char s[N][1010];
ll tot=0;
int conver(char c)
{
if(c>='0'&&c<='9')return c-'0';
if(c>='a'&&c<='z')return c-'a'+10;
if(c>='A'&&c<='Z')return c-'A'+10+26;
}
int insert(Tire *root,const char *s)
{
if(root==NULL||*s=='\0')
return 0;
Tire* p=root;
int len=strlen(s),ans=0;
for(int i=0;i<len;i++)
{
int c=conver(s[i]);
if(p->next[c]==NULL)
{
ans+=p->val*(2*i+1);
p->next[c]=new Tire();
}
else
{
ans+=(p->val-p->next[c]->val)*(2*i+1);
}
p->val++;
p=p->next[conver(s[i])];
}
ans+=p->isEnd*(len+1)*2;
ans+=(p->val-p->isEnd)*(2*len+1);
p->val++;
p->isEnd++;
return ans;
}
void del(Tire* root)
{
for(int i=0;i<MAXN;i++)
{
if(root->next[i]!=NULL)
del(root->next[i]);
}
delete root;
}
int main()
{
//freopen("1.in","r",stdin);
int n,t=0;
while(scanf("%d",&n)!=EOF&&n)
{
Tire *root=new Tire();
tot=0;
for(int i=0;i<n;i++)
{
scanf("%s",s[i]);
tot+=insert(root,s[i]);
}
printf("Case %d: %lld\n",++t,tot);
//cout<<tot;
del(root);
}
return 0;
}
Period
算法:KMP
注释:
- 如果一个字符串连个以上的循环节,那么其最长公共前后缀的错位部分一定是最小循环节。根据公共前后缀的性质,公共部分与前面错位部分存在相同的部分,所以如果j%(j-next[j])==0则一定存在循环节。
#include<iostream>
#include<cstdio>
#define N 1000010
using namespace std;
int nex[N],k[N];
char s[N];
void getNext(char s[],int n)
{
int k=-1,j=0;
nex[j]=k;
while(s[j]!='\0')
{
if(k==-1||s[k]==s[j])
{
k++;
j++;
nex[j]=k;
}
else k=nex[k];
}
}
int main()
{
int n,kase=0;
while(cin>>n&&n)
{
cin>>s;
getNext(s,n);
for(int j=1;j<=n;j++)
{
if(j%(j-nex[j])==0)
k[j]=j/(j-nex[j]);
else k[j]=1;
}
printf("Test case #%d\n",++kase);
for(int i=1;i<=n;i++)
{
if(k[i]>1)printf("%d %d\n",i,k[i]);
}
printf("\n");
}
return 0;
}