小清新 Counting 题,想到转化成序列计数后就不难了
考虑将一个 0/1 串等价转化为一个刻画相邻两个 \(1\) 之间有几个 \(0\) 的序列
比如样例中的 \(00101100011100\) 就可以转化为 \(\{2,1,0,3,0,0,2\}\) 这个序列,显然转化后的序列和原来的 0/1 串等价
考虑此时一次操作相当于将序列中某个数减 \(1\);或者将某个不在末尾的 \(0\) 删去
因此可以用类似自动机的思路判定一个序列是否可以由原始的序列 \(\{a\}\) 生成
即令 \(f_i\) 表示匹配到原序列的第 \(i\) 个位置,对应的序列的总方案数,转移考虑枚举在序列末尾加上的数 \(x\),则可以转移到 \(i\) 之后的第一个位置 \(j\),满足 \(a_j\ge x\)
不妨换一个思路,考虑 \(f_i\) 对 \(f_j\) 的贡献,手玩一下会发现系数就是 \(a_j-\max_\limits{i<k<j} a_k\)
这种涉及到 \(\max\) 的问题很容易想到单调栈,我们从前往后维护一个递减的单调栈,每次弹栈的时候更新下贡献即可
代码是祁神写的,复杂度 \(O(n)\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int INF = (int)1e18+5;
const int MOD = (int)1e9+7;
const int N = (int)1e6+5;
void add(int &x, int a){if ((x+=a)>=MOD) x-=MOD;}
string str;
int dp[N], sum[N];
struct Node{
int pos, bd;
};
Node stk[N]; int top=-1;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> str;
int len = str.length();
vector<int> space;
int cnt0=0;
for (int i=0; i<len; ++i){
if ('0'==str[i]) ++cnt0;
else space.push_back(cnt0), cnt0=0;
}
// printf("space:"); for (int i=0; i<space.size(); ++i) printf("%lld ", space[i]); printf("cnt0=%lld\n", cnt0);
if (0==space.size()){
cout << cnt0 << '\n';
return 0;
}else if (1==space.size()){
cout << (cnt0+1)*(space[0]+1)%MOD << '\n';
return 0;
}
int bis = (cnt0+1)*(space[0]+1)%MOD;
int sz = space.size();
space[0] = INF;
stk[++top] = Node{0, -1};
dp[0] = 1;
for (int i=1; i<sz; ++i){
while (top>=0 && space[stk[top].pos] <= space[i]){
auto [pos, bd] = stk[top--];
add(dp[i], dp[pos]*(space[i]-bd)%MOD);
add(dp[i], (MOD+sum[pos-1]-sum[stk[top].pos])%MOD*(space[i]-space[pos])%MOD);
}
add(dp[i], dp[stk[top].pos]*(space[i]-stk[top].bd)%MOD);
sum[i] = (sum[i-1]+dp[i])%MOD;
stk[top].bd = space[i];
stk[++top] = Node{i, -1};
}
// printf("dp:"); for (int i=0; i<sz; ++i) printf("%lld ", dp[i]); puts("");
int ans=0;
for (int i=0; i<sz; ++i) add(ans, dp[i]);
ans = ans*bis%MOD;
cout << ans << '\n';
return 0;
}
标签:const,int,Strange,str,序列,Operation,CF1383E,MOD
From: https://www.cnblogs.com/cjjsb/p/18359623