zoukankan      html  css  js  c++  java
  • P4965 薇尔莉特的打字机

    题目

    P4965 薇尔莉特的打字机

    快到十二点了正在颓废突然发现了一道好题

    虽然毒瘤,但确实是容斥原理的好题啊,做法也特别巧妙(标程

    思路

    题目大意(怕自己突然忘)

    n个初始字符,m个操作(加入或删除),任何一个操作都可能无效,求最后不同的字符方案数((n,m<=5*10^6))

    先考虑无删除操作

    这里的(t_{0..i})为一个字串,(widetilde{t_{0..i}})指该字串不同的子序列个数,(dp_i)(widetilde{t_{0..i-1}})

    (t_{i})这个字符在循环时第一次出现,即(t_{0...i-1})没出现过(t_{i}),显然我们考虑的状态有三种

    (~~~~~1.widetilde{t_{0..i-1}}),有 (dp_{i-1})(Longrightarrow)理解:插入操作无效或只考虑前面字符的方案数

    (~~~~~2.widetilde{t_{0..i-1}} + {t_i}),有(dp_{i-1})(Longrightarrow)理解:插入操作有效,且与前面字符组合起来的方案数

    (~~~~~3.widetilde{t_{i..i}})(Longrightarrow)理解:插入操作有效(t_{i})单独组成一种方案

    综上,(t_{i})这个字符在循环时第一次出现:(dp_{i}=2*dp{i-1}+1)

    (egin{aligned} \ end{aligned})

    那不是第一次出现呢?显然会出现重复的子序列

    (lst[c])表示字符(c) 上一次出现的位置

    1.(widetilde{t_{0..lst[t_i]-1}}+{t_{lst[t_i]}})(widetilde{t_{0..lst[t_i]-1}}+{t_i}) 重复
    2.({t_{lst[t_i]}})({t_i}) 重复

    综上,(dp_i)要去掉(dp_{lst[t_i]-1}+1)
    ( herefore dp_i=egin{cases}2*dp_{i-1}+1quad(t_i ext{第一次出现})\2dp_{i-1}-dp_{lst[t_i]-1}quad (t_i ext{出现过})end{cases})
    目前为止,时间复杂度为(O(m)),毒瘤的出题人不可能就这样放过我们嘛(emmm)

    考虑删除操作
    其实删除操作只用考虑删除前面的文本串,为什么?删除插入操作无异与:删除与插入两个操作同时无效,而前面的方程已经将此情况考虑进去了

    故我们只用考虑文本串与插入操作中间的删除操作

    当能作为有效删除操作为(cnt)个时,我们枚举有(k(k<=cnt))个有效操作

    则此时新增子序列(s_{0..n-k-1} + widetilde{t_{pre[p_k]..m-1}})

    发现没有?从小到大枚举(k),时间复杂度瞬间指数加(1)成了(O(m^2)),那我们就从(m-1)~(0)逆推,又变成线性的了!!

    (dp_i)(widetilde{t_{i..m-1}})(lst[c])为倒推时字符(c)上一次出现的位置,由于删除操作的存在,方程中的(dp_{i-1}) 改为(dp_{pre[i]})(这些应该都好理解吧)

    (egin{aligned} \ end{aligned})

    同样地,我们还得考虑重复部分,

    若退格所删去的最后一个字符即(s_{n-k})(t_{pre[p_k]..m-1}) 中出现过,则会产生重复的答案:第(k)删除在文本串(s)中删除的字符是(s_{n-k})

    而在第(k-1) 个删除 时,(s_{n-k})不会被删除,则一旦 (s_{n-k})(t_{pre[p_k]..m-1}) 中出现过,就意味着在只有(k-1)个删除时(s_{n-k})会与后面的发生重复

    重复子序列为(s_{0..n-k}+widetilde{t_{pre[lst[s_{n-k}]]..m-1}})以及(s_{0..n-k}),个数为 (dp_{pre[lst[s_{n-k}]]}+1),计算答案时要将这部分减去

    My complete code

    短得可怜的代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int p=0x125E591;
    const int maxn=5000000+9;
    int n,m,pos,cnt,ans;
    int lst[maxn],pre[maxn],dp[maxn]; 
    char s[maxn],t[maxn];
    inline int Get(char x){
        return (lst[x])?p-dp[pre[lst[x]]]:1;
    }
    int main(){
        scanf("%d%d",&n,&m);
        scanf(" %s %s",s,t);
        for(int i=0;i<m;++i)
            cnt+=(t[i]=='u');
        for(int i=m-1;i>=0;--i)
        	if(t[i]=='u'){
        		if(n-cnt>=0) 
                    ans=(ans+dp[pos]+Get(s[n-cnt]))%p;
                --cnt;
            }else{
                dp[i]=(2*dp[pre[i]=pos]+Get(t[i]))%p;
                pos=lst[t[i]]=i;
            }
        printf("%d",(ans+dp[pos]+1)%p);
        return 0;
    }
    
    

    总结

    动规啊容斥啊这些真的得完全弄懂再去写代码,几次想写代码了还是回过头自己纯手推了一下

    能想到写篇博客竟然花了快一个多小时,凌晨一点半 其实也还早啊,睡觉

  • 相关阅读:
    Tornado Web框架
    使用django实现自定义用户认证
    设置DNS 代理
    Docker
    TCP/IP详解学习笔记(1)-基本概念
    IMS知识学习路径浅谈
    Word文档不能编辑解决方法
    P2P网络“自由”穿越NAT的“秘密”原理
    斗战神 拳猴刷图加点
    斗战神 装备精炼
  • 原文地址:https://www.cnblogs.com/y2823774827y/p/10261702.html
Copyright © 2011-2022 走看看