【学习笔记】CF1383E Strange Operation

被这道题搞得有点自闭,最后发现计数的地方还是没想清楚。

仔细想一想 如何判断一个字符串 T T T能否被生成。为什么要说仔细想一想,因为 1 1 1的连续段是不能随便删的。然后发现可以将 T T T分段,如果在 T T T的末尾插入一段 0 0 0,那么要求此时 S S S对应匹配的那个位置也是 0 0 0,并且 0 0 0的连续段的长度要更长;如果在 T T T的末尾插入一段 1 1 1,那么注意此时不是截取 S S S中最短的前缀去获得,而是要保证此时 S S S中以 0 0 0开头并且长度足够,在此条件下去截取最短前缀,这是我们在前面已经分析过的。于是我们的一个初步感知是 0 0 0 1 1 1要分开讨论,并且因为是类似 D F A DFA DFA的匹配过程所以 D P DP DP状态是极易的,转移实际上也并不困难,我们不妨细致讨论一下。

显然倒着 D P DP DP更容易处理。设 p i p_i pi表示以 i i i开头前缀 0 0 0的长度, d p z e r o [ i ] dpzero[i] dpzero[i]表示 [ i , n ] [i,n] [i,n]能生成的以 0 0 0开头的字符串的数目,先不管是否包含空串。同理设 d p o n e [ i ] dpone[i] dpone[i]表示 [ i , n ] [i,n] [i,n]能生成的以 1 1 1开头的字符串数目。这样设是为了满足 01 01 01连续段交替的条件。注意这里的定义是 i i i必须选 。并且为了保证不算重,我们规定 d p z e r o [ i ] dpzero[i] dpzero[i] 必须把这一段 0 0 0全部选完,并且后面必须接上至少一个 1 1 1。同时设 l i l_i li表示前缀 0 0 0长度 ≥ i \ge i i的最小的位置。因为 D P DP DP转移并不复杂(这是可以预见的),所以就一边维护信息一边转移就好了。分类讨论:

1.1 1.1 1.1 S [ i ] = 0 S[i]=0 S[i]=0,根据定义有 d p z e r o [ i ] ← d p o n e [ i + p [ i ] ] dpzero[i]\gets dpone[i+p[i]] dpzero[i]dpone[i+p[i]]

1.2 1.2 1.2 S [ i ] = 1 S[i]=1 S[i]=1,考虑替换的思想,设 [ i + 1 , n ] [i+1,n] [i+1,n]能生成串 a a a,那么 1 a ‾ \overline{1a} 1a显然也能被生成,因此有 d p o n e [ i ] ← d p o n e [ i + 1 + l [ i + 1 ] ] dpone[i]\gets dpone[i+1+l[i+1]] dpone[i]dpone[i+1+l[i+1]],但是漏算了 a a a 0 0 0开头的情形,也就是 d p o n e [ i ] ← ∑ d p z e r o [ l [ j ] ] dpone[i]\gets \sum dpzero[l[j]] dpone[i]dpzero[l[j]]。注意还有一种情况是 a a a全为 0 0 0,设末尾连续 0 0 0的长度为 n u m num num,那么 d p o n e [ i ] ← n u m + 1 dpone[i]\gets num+1 dpone[i]num+1

发现可以用数组 O ( 1 ) O(1) O(1)维护,然后就做完了。

最后统计答案的时候也注意不要搞错了。如果写一遍的时候就能把这些细节全部想清楚的话那我觉得真是太酷拉。

复杂度 O ( n ) O(n) O(n)

#include
#define ll long long
#define fi first
#define se second
#define pb push_back
#define db double
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int mod=1e9+7;
const int N=1e6+5;
int n,zeros,p[N];
ll dpzero[N],dpone[N],dp[N],sumzero,res;
string s;
void add(ll &x,ll y){x=(x+y)%mod;}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>s,n=s.size();
    for(int i=n-1;i>=0;i--){
        if(s[i]=='0'){
            p[i]=p[i+1]+1;
        }
    }
    int n2=n-1;while(n2>=0&&s[n2]=='0')n2--;
    if(n2<0){
        cout<<n;
        return 0;
    }
    for(int i=n2;i>=0;i--){
        if(s[i]=='0'){
            add(dpzero[i],dpone[i+p[i]]);
            add(sumzero,-dp[p[i]]);
            add(sumzero,dpzero[i]);
            dp[p[i]]=dpzero[i];
        }
        else{
            add(dpone[i],dpone[i+1+p[i+1]]+sumzero+n-n2);
        }
    }
    for(int i=p[0];i>=0;i--){
        if(s[i]=='1'){
            add(res,dpone[i]);
        }
        else{
            add(res,dpzero[i]);
        }
    }
    cout<<(res+mod)%mod;
}

你可能感兴趣的:(学习,笔记,算法)