• bzoj 4044: Virus synthesis 回文自动机


    题目大意:

    你要用ATGC四个字母用两种操作拼出给定的串:

    1. 将其中一个字符放在已有串开头或者结尾
    2. 将已有串复制,然后reverse,再接在已有串的头部或者尾部
      一开始已有串为空。求最少操作次数。
      len<=100000

    题解:

    我们一看 ! 这道题跟回文自动机没有半毛钱关系啊 !!
    仔细分析一下第二个操作,我们发现这个操作过后实际上就产生了一个大回文串
    所以我们可以考虑如何在回文自动机上搞

    一开始想歪了,一直倒着想:如何把给定字符串消掉,然后一直没有想出来


    其实这道题应该正着想
    (f_i)表示达到回文自动机中的第(i)个节点所需要的最小操作.
    那么我们有:
    对于所有的通过添加一个字符可以转移到(i)(f_j)
    (f_i = min{f_j + 1})即在得到(j)倍增之前添加一个字符于是转移到i
    注意 : 最优化下最后一步一定是倍增
    我们还有(f_i = min{ frac{len_i}{2} + f_{fail_i} - len_{fail_i} + 1})
    就是考虑这个串由所有回文后缀转移过来.
    但是这样转移其实是(O(n^2))的,我们应到考虑优化
    其实我们发现:如果跳fail指针,那么(f_{fail_i})会变小,但是相应的另一部分会变大
    由反证法可得,一定不会出现决策时选择(fail_i)(fail_{fail_i})更优的情况
    所以我们只需要考虑取到的第一个可行决策即可.
    但是这样还是不够:可行决策的两个条件:

    • s[len-T[x].len-1] == s[len]
    • (T[x].len+2)*2 <= T[cur].len

    找到第一个可行决策的时间过长,依然会TLE
    所以我们考虑记录一下每个节点的第一个可行决策
    在找最优决策的时候,我们跳转last的可行决策,然后跳fail继续查找即可
    不难发现这样一定是对的:

    证明:
    我们设当前新建状态为(cur)那么当我们利用第二个dp方程转移时一定有(T[cur].len < T[last].len)
    所以对于(last)不成立的状态即((T[x].len+2)*2 > T[last].len)
    一定也有((T[x].len+2)*2 > T[last].len)
    故一定可以直接从(last)的可行决策点向前取.

    所以我们在正常地找出第一个可以拓展成目前最长回文后缀的状态
    对着这个状态的决策点一直向前跳即可.

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    const int maxn = 100010;
    inline int idx(char ch){
    	if(ch == 'A') return 0;
    	if(ch == 'T') return 1;
    	if(ch == 'C') return 2;
    	if(ch == 'G') return 3;
    	return 0;
    }
    struct Node{
    	int nx[4];
    	int fail,len,p;
    	void clear(){
    		nx[0] = nx[1] = nx[2] = nx[3] = 0;
    		fail = len = p = 0;
    	}
    }T[maxn];
    int last,nodecnt,len,s[maxn];
    inline void init(){
    	last = nodecnt = 0;
    	T[0].clear();T[1].clear();
    	T[++nodecnt].len = -1;
    	T[0].fail = 1;
    	s[len=0] = -1;
    }
    inline void insert(int c){
    	s[++len] = c;int cur,p,x;
    	for(p = last;s[len-T[p].len-1] != s[len];p = T[p].fail);
    	if(T[p].nx[c] == 0){
    		T[nodecnt+1].clear();T[cur = ++nodecnt].len = T[p].len + 2;
    		for(x = T[p].fail;s[len-T[x].len-1] != s[len];x = T[x].fail);
    		T[cur].fail = T[x].nx[c];
    		T[p].nx[c] = cur;
    		if(T[cur].len <= 2) T[cur].p = T[cur].fail;
    		else{
    			for(x = T[p].p;s[len-T[x].len-1] != s[len] || (T[x].len+2)*2 > T[cur].len;x = T[x].fail);
    			T[cur].p = T[x].nx[c];
    		}
    	}last = T[p].nx[c];
    }
    char str[maxn];
    int q[maxn],l,r,f[maxn];
    int main(){
    	int n;read(n);
    	while(n--){
    		init();scanf("%s",str);
    		int len = strlen(str);
    		for(int i=0;i<len;++i) insert(idx(str[i]));
    		for(int i=2;i<=nodecnt;++i){
    			if(T[i].len&1) f[i] = T[i].len;
    		}
    
    		l = 0;r = -1;q[++r] = 0;f[0] = 1;
    		int ans = len;
    		while(l <= r){
    			int u = q[l++],v;
    			for(int i=0;i<4;++i){
    				v = T[u].nx[i];
    				if(v == 0) continue;
    				f[v] = min(f[u] + 1,T[v].len/2 + f[T[v].p] - T[T[v].p].len + 1);
    				ans = min(ans,f[v] + (len - T[v].len));
    				q[++r] = v;
    			}
    		}printf("%d
    ",ans);
    	}
    	getchar();getchar();
    	return 0;
    }
    
  • 相关阅读:
    VUE(vue对象的简单属性)
    使用Dockerfile封装Django镜像
    Django路由小知识
    字符编码小知识
    python值的引用传递和go语言的值传递
    centos输入正确的账号和密码登陆不进去
    迅为4412开发平台Zigbee模块在物联网智能家居中的应用
    全新升级4412开发板项目学习实战资料
    迅为4418开发板平台应用于智能门禁系统
    【分享】iTOP-iMX6UL开发板驱动看门狗 watchdog 以及 Linux-c 测试例程
  • 原文地址:https://www.cnblogs.com/Skyminer/p/6533854.html
Copyright © 2020-2023  润新知