题解 CF1383E Strange Operation

题目大意

题目链接

给你一个长度为\(n\)\(01\)\(s\)。你可以进行不超过\(n-1\)次操作。每次操作,你可以选择一个位置\(i\) (\(1\leq i<|s|\)),令\(s_i:=\max(s_i,s_{i+1})\),然后删掉\(s_{i+1}\)。删掉后,左右两边会自动拼接起来,并且\(s\)的长度会减\(1\)。请求出,有多少种不同的串,能通过对\(s\)进行不超过\(n-1\)次操作得到。答案对\(10^9+7\)取模。

数据范围:\(1\leq n\leq 10^6\)

本题题解

这个操作,相当于每个\(1\),可以“吃掉”它旁边的任何一个东西。当然,两个\(0\)在一起时,一个\(0\)也可以“吃掉”另一个,不过这不重要:原串全是\(0\)时我们可以特判,其他情况下只需要用到“拿\(1\)吃别人”的操作。

假设给你一个结果串\(t\)。如何判断\(t\)能不能由\(s\)经过操作得来呢?贪心地,依次考虑\(t\)的每一位。假设现在考虑到了\(t\)的前\(j\)位,已经用掉了\(s\)的前\(i\)位(显然\(i\)越小,“匹配”成功的机会越大,所以我们的贪心就是在每一步让\(i\)尽可能小)。

  • 如果\(t\)的第\(j+1\)位是\(1\)。我们找出\(s\)\(i\)后面的第一个\(1\),也就最小的\(i'\)满足\(i'>i\)\(s_{i'}=1\),然后用\(i'\)吃光\([i+1,i'-1]\)里所有的数。再令\(i:=i'\)
  • 如果\(t\)的第\(j+1\)位是\(0\)。假设从\(t\)里的上一个\(1\)开始(如果没有就从开头开始),直到\(j+1\),这段区间里共有\(k\)\(0\)。在\(s\)里找到下一个有\(k\)\(0\)的段:也就是最小的\(i'\)满足\(i'>i\)\(\forall0\leq k'<k:s_{i'-k'}=0\)。如果\(i'=i+1\)(也就是\(i\)所在的这段\(0\)本身就够长),直接把这个\(0\)接在后面即可,如果\(i'>i+1\)(说明跳到了后面的某一段),那用\(i'-k\)(一定是个\(1\))把从\(s\)里上一个\(1\),一直到\(i'-k-1\)的这段区间,全部吃掉。

以上是已经确定\(t\)的时候,可以做这样一个贪心。现在我们要求出有多少满足条件的\(t\),那么可以对这个贪心进行DP。

\(dp[i]\)表示有多少串\(t\)能贪心匹配到\(s\)的第\(i\)位。转移时,分别考虑\(t\)的下一位是\(0\)\(1\)的情况,转移到两种\(i'\)。如果先预处理出,每个\(0\)和它前一个\(1\)的距离,然后暴力向后找\(i'\),复杂度是\(O(n)\)的。但我们可以先从\(n\)\(1\)倒着推一遍,就能预处理出每个\(i\)对应的\(i'\)了,转移的复杂度变成\(O(1)\)

求答案时,枚举一个\(i\)作为结尾,把这样的\(dp[i]\)加起来就是答案了。不过,并不是所有\(i\)都能作为结尾。我们发现,一个\(i\)能作为结尾,当且仅当它与上一个\(1\)的距离小于等于\(s_n\)与上一个\(1\)的距离(如果某个位置本身就是\(1\),可以认为距离为\(0\)),否则,它后面的东西消不掉,这个\(i\)自然就不能作为结尾了。

还有一个值得思考的问题。我们的DP是每次在已经有的串后面添加一个字符,得到新的串,那会不会重复呢?比如,已有\(101\)\(10\)两个串,如果此时在后面添加一个\(1\),得到两个新串\(1011\)\(101\),而\(101\)就与前面的串重复了。其实这种情况不会出现,因为每个结果串\(t\),在这种贪心下,会唯一对应一个匹配到结尾的\(i\)。所以算答案时,每个\(t\)只在唯一一个\(dp[i]\)里被统计到。那么“同一个\(dp[i]\)”里会不会有重复呢?也不会。因为把\(t\)的最后一位去掉后,还是一个串,还是匹配到唯一的\(i'\),所以\(t\)只会被从\(i'\)\(i\)转移一次,如果\(dp[i']\)里没有重复,\(dp[i]\)里也不会有重复,这样归纳下去即可。

时间复杂度\(O(n)\)

参考代码:

//problem:CF1383E
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}

const int MAXN=1e6;
int n,dp[MAXN+5],dist[MAXN+5],pos[MAXN+5],nxt_1[MAXN+5],nxt_d[MAXN+5];
char s[MAXN+5];
int main() {
	cin>>(s+1); n=strlen(s+1);
	int start_pos=0;
	for(int i=1;i<=n;++i){
		if(s[i]=='1'){
			start_pos=i;
			break;
		}
	}
	if(!start_pos){
		//全是0
		cout<<n<<endl;
		return 0;
	}
	for(int i=1;i<=n;++i){
		//dist[i]: i到上一个1的距离
		if(s[i]=='0'){
			dist[i]=dist[i-1]+1;
		}
		else{
			dist[i]=0;
		}
	}
	for(int i=0;i<=n+1;++i)
		pos[i]=n+1;
	for(int i=n;i>=1;--i){
		nxt_1[i]=pos[0];
		nxt_d[i]=pos[dist[i]+1];
		
		pos[dist[i]]=i;
	}
	dp[start_pos]=start_pos;
	for(int i=start_pos;i<n;++i){
		if(nxt_1[i]!=n+1){
			add(dp[nxt_1[i]],dp[i]);
		}
		if(nxt_d[i]!=n+1){
			add(dp[nxt_d[i]],dp[i]);
		}
	}
	int ans=0;
	for(int i=start_pos;i<=n;++i){
		if(dist[i]<=dist[n]){
			add(ans,dp[i]);
		}
	}
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-07-25 13:47  duyiblue  阅读(436)  评论(0编辑  收藏  举报