Non Absorbing DFA DP (ASC2A SGU201 ZOJ2337 ACdream1218 Gym100197A)

SGU 201 Non Absorbing DFA DP

( ZOJ 2337 、ACdream 1218 、Gym 100197A)
Andrew Stankevich Contest 2 A题

题意

给定一个DFA(有字符集、初始状态、终态集,每个状态遇到每个字母会转换到哪个状态)。问某个长度的字符串有多少种可以被该DFA接受(从初始状态出发,根据字符串的每个字母进行转移,最后停留在终态)。但这个DFA有个特殊的地方,某些状态遇到某些字母进行转移的时候,下次转移时仍然使用上一个字母,而不是把上个字母移除之后使用下个字母进行转移。

解法

先对每个状态和字母的组合进行DFS,搜到最终转移到的状态,然后进行DP, dp[i][j] 表示串长为i,停留在j状态的字符串数目,对于每个 dp[i][j] ,枚举遇到的下一个字母,如果j状态遇到该字母能够转移到nxt状态而不是陷入死循环,则 dp[i+1][nxt]+=dp[i][j] 。最终 ans=dp[m][i],i

AC代码

(SGU 201 内存391KB 用时46 ms 编译器Visual Studio C++ 2010)

#include<cstring>
#include<cstdio>

using namespace std;

const int N = 1010;     ///最大状态个数

int nextSta[N][26];     ///每个状态在某个字母下的下一个状态
bool notRemove[N][26];  ///为true代表该状态使用该字母进行转移后,下次仍然使用该字母,该字母不移除
int zt[N];              ///终态

int staCnt,alphaCnt,start,ztNum,cc; ///状态数、字母数、初始状态、终态数,串长

///大整形类,支持加法
struct BigInt {
    static const int DIGITAL_PER_LL = 18;///一个long long保存18个十进制位
    static const int MAX_LEN = 85 / DIGITAL_PER_LL + 1; ///根据最大的十进制位数,计算出需要的long long个数
    long long dat[MAX_LEN];

    /// +=运算
    BigInt& operator+=(const BigInt& other) {
        for(int i = 0;i < MAX_LEN;++i){
            dat[i] += other.dat[i];
            if(dat[i] >= 1000000000000000000LL){
                dat[i] -= 1000000000000000000LL;
                dat[i + 1]++;
            }
        }
        return *this;
    }

    ///输出
    void print() {
        ///去前导零
        int start = MAX_LEN - 1;
        while(start > 0 && dat[start] == 0) {
            start--;
        }
        ///第一个long long无前导零,其它每个18位,有前导零
        printf("%I64d",dat[start--]);
        while(start >= 0) {
            printf("%.18I64d",dat[start--]);
        }
        putchar('\n');
    }

    ///赋值
    void setValue(long long num) {
        dat[0] = num;
        for(int i = 1; i < MAX_LEN; ++i) {
            dat[i] = 0;
        }
    }

    ///初始化为0
    BigInt() {
        setValue(0);
    }
};

BigInt dp[2][N];///dp[i][j]表示串长为i,停留在j状态的字符串数目

///输入
void input() {
    char s[100];
    scanf("%s",s);
    alphaCnt = strlen(s);
    scanf("%d%d%d",&staCnt,&start,&ztNum);
    for(int i = 0; i < ztNum; ++i) {
        scanf("%d",&zt[i]);
    }
    for(int i = 0; i < staCnt; ++i) {
        for(int j = 0; j < alphaCnt; ++j) {
            scanf("%d",&nextSta[i][j]);
            nextSta[i][j]--;
        }
    }
    for(int i = 0; i < staCnt; ++i) {
        for(int j = 0; j < alphaCnt; ++j) {
            int t;
            scanf("%d",&t);
            notRemove[i][j] = t == 1;
        }
    }
    scanf("%d",&cc);
}

///状态访问标志
bool visit[N];

///更新每个状态遇到每个字母最终会到达何状态,死循环则为-1
int dfs(int state,int ch) {
    visit[state] = true;
    int nxt = nextSta[state][ch];
    if(!notRemove[state][ch]) {///访问到了一个可以移除该字母的状态,结束
        notRemove[state][ch] = false;
        return nextSta[state][ch] = nxt;
    } else if(visit[nxt]) {     ///访问到了一个已访问的状态,死循环
        notRemove[state][ch] = false;
        return nextSta[state][ch] = -1;
    }
    notRemove[state][ch] = false;   ///取消该标记,下次不用重复搜了
    return nextSta[state][ch] = dfs(nxt,ch);
}

///预处理,调用dfs函数更新nextSta数组
void init() {
    for(int i = 0; i < staCnt; ++i) {
        for(int j = 0; j < alphaCnt; ++j) {
            if(notRemove[i][j]) {
                memset(visit,0,sizeof(visit));
                dfs(i,j);
            }
        }
    }
}

///计算答案
BigInt cal() {
    for(int i = 0; i < staCnt; ++i) {
        dp[0][i].setValue(0);
    }
    dp[0][start - 1].setValue(1);///初始dp[0][初始状态]=1,其它为0
    bool flag = true;
    for(int i = 0; i < cc; ++i) {
        for(int i = 0; i < staCnt; ++i) {
            dp[flag][i].setValue(0);
        }
        for(int st = 0; st < staCnt; ++st) {
            for(int zm = 0; zm < alphaCnt; ++zm) {
                int nxt = nextSta[st][zm];///st状态碰到zm字母最终到达的状态
                if(nxt != -1) {///不会死循环
                    dp[flag][nxt] += dp[!flag][st];///更新到达的状态计算
                }
            }
        }
        flag = !flag;
    }
    ///统计停留在终态的情况
    BigInt res;
    for(int i = 0; i < ztNum; ++i) {
        res += dp[!flag][zt[i] - 1];
    }
    return res;
}

int main() {
    input();
    init();
    cal().print();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值