题解 CF1383E Strange Operation

题目大意

题目链接

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

数据范围:(1leq nleq 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)(forall0leq 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;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/13376474.html