分组被分配到了主攻字符串,计算几何方面的问题,接下来一段时间主要看这些方面的知识了。
这次三个题BZOJ 1030,POJ 2778,hdu2243是同一种套路,但数据范围和题目要求不同,在这里整理一下。
很遗憾BZOJ停运了,所以只能在这里看题面了参考博客
代码:
主要思想,先枚举所有可能性,然后去掉不合理的方案数。
由于数据量较小,可以直接使用DP
代码来自参考博客
#include
#define maxn 105
using namespace std;
int dp[maxn][7000];
char s[500];
const int mod=10007;
int n,m;
struct Trie{
int next[10000][26],fail[10000],End[10000],id,root;
int newnode(){
for(int i=0;i<26;i++){
next[id][i]=-1;
}
End[id]=0;
return id++;
}
void init(){
id=0;
root=newnode();
}
void Insert(char *str){
int len=strlen(str);
int now=root;
for(int i=0;i<len;i++){
if(next[now][str[i]-'A']==-1){
next[now][str[i]-'A']=newnode();
}
now=next[now][str[i]-'A'];
}
End[now]=1;
}
void build(){
queue<int>que;
fail[root]=root;
for(int i=0;i<26;i++){
if(next[root][i]==-1){
next[root][i]=root;
}
else{
fail[next[root][i]]=root;
que.push(next[root][i]);
}
}
while(!que.empty()){
int now=que.front();
que.pop();
for(int i=0;i<26;i++){
if(next[now][i]==-1){
next[now][i]=next[fail[now]][i];
}
else{
fail[next[now][i]]=next[fail[now]][i];
que.push(next[now][i]);
}
}
End[now]|=End[fail[now]];//终点标志也需要转移
}
}
}ac;
int powmod(int a,int n)//普通快速幂
{
int res=1;
while(n){
if(n&1) res=res*a%mod;
a=a*a%mod;
n>>=1;
}
return res;
}
void solve()
{
dp[0][0]=1;
for(int i=1;i<=m;i++)
{
for(int j=0;j<=ac.id;j++)
{
if(ac.End[j]) continue;//如果当前位为某一个串的终点
for(int k=0;k<26;k++)
{
if(ac.End[ac.next[j][k]]) continue;
dp[i][ac.next[j][k]]=(dp[i][ac.next[j][k]]+dp[i-1][j])%mod;
//状态转移方程是个要点,因为初始化时只有dp[0][0]为1,所以基本也以模拟树上DP
}
}
}
int res=0;
for(int i=0;i<=ac.id;i++){
res=(res+dp[m][i])%mod;
}
cout<<(powmod(26,m)-res+mod)%mod<<endl;
}
int main()
{
scanf("%d%d",&n,&m);
ac.init();//不知道为啥,大佬们都喜欢把板子写在结构体里
while(n--){
scanf("%s",s);
ac.Insert(s);
}
ac.build();
solve();
}
原题地址
代码:
由于数据量大了很多(1e9),所以显而易见不能简单DP
离散数学有个非常神奇的知识,即对于一个图的邻接矩阵,这个矩阵的m次方,第i行j列的值代表从i到j路径为m的路径数(左孝凌版离散教材P290)
所以上面的DP过程被转化为矩阵快速幂
代码来自参考博客
//#include
#include
#include
#include
#include
#include
#include
#define maxn 110
using namespace std;
const int mod=100000;
int nn,mm;
char st[maxn];
struct Marix{//矩阵
int mo[maxn][maxn],n;
Marix(){}
Marix(int _n){
n=_n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++) mo[i][j]=0;
}
}
};
Marix mul(Marix a,Marix b){//矩阵乘法
Marix res=Marix(a.n);
for(int i=0;i<a.n;i++){
for(int j=0;j<a.n;j++){
for(int k=0;k<a.n;k++){
int tmp=(long long )a.mo[i][k]*b.mo[k][j]%mod;
res.mo[i][j]=(res.mo[i][j]+tmp)%mod;
}
}
}
return res;
}
Marix powMod(Marix a,int n){//矩阵快速幂
Marix nul;
nul=Marix(a.n);
for(int i=0;i<nul.n;i++){
nul.mo[i][i]=1;
}
while(n){
if(n&1) nul=mul(nul,a);
a=mul(a,a);
n>>=1;
}
return nul;
}
map<char,int>mp;
struct Trie{//AC自动机
int next[maxn][4],fail[maxn],End[maxn],root,id;
int newnode(){
for(int i=0;i<4;i++){
next[id][i]=-1;
}
End[id]=0;
return id++;
}
void init(int nn){
mp['A']=0,mp['C']=1,mp['T']=2,mp['G']=3;
id=0;
root=newnode();
}
void Insert(char *str){
int now=root;
int len=strlen(str);
for(int i=0;i<len;i++){
if(next[now][mp[str[i]]]==-1){
next[now][mp[str[i]]]=newnode();
}
now=next[now][mp[str[i]]];
}
End[now]=1;
}
void build(){
queue<int>que;
for(int i=0;i<4;i++){
if(next[root][i]==-1){
next[root][i]=root;
}
else{
fail[next[root][i]]=root;
que.push(next[root][i]);
}
}
while(!que.empty()){
int now=que.front();
que.pop();
for(int i=0;i<4;i++){
if(next[now][i]==-1){
next[now][i]=next[fail[now]][i];
}
else{
fail[next[now][i]]=next[fail[now]][i];
que.push(next[now][i]);
}
}
End[now]|=End[fail[now]];
}
}
Marix getMarix(){//构造Trie图中的邻接可达性矩阵
Marix Mar=Marix(id);
for(int i=0;i<id;i++){
for(int j=0;j<4;j++){
if(End[next[i][j]]) continue;
Mar.mo[i][next[i][j]]++;
}
}
return Mar;
}
}ac;
int main()
{
scanf("%d%d",&nn,&mm);
ac.init(nn);
for(int i=0;i<nn;i++){
scanf("%s",st);
ac.Insert(st);
}
ac.build();//这里的build其实是getfail
Marix M=ac.getMarix();
Marix tmp=powMod(M,mm);
int res=0;
for(int i=0;i<tmp.n;i++){
res=(res+tmp.mo[0][i])%mod;
}
cout<<res<<endl;
}
理解了原理就很好写,感觉AC自动机已经和网络流一个地位了 。
原题地址
代码:
参考博客
思路和上一题大致相同,不过这一题要求询问路径小于m的方案数之和,这里我们可以增加一列,最后一列全为1,这样最后一列的第一行每次的值为前m-1次第一行的值之和+1(路径长为0的情况有一种)。
代码见参考博客