Codeforces 题目传送门 & 洛谷题目传送门

Yet another 自己搞出来的难度 \(\ge 2800\) 的题

介绍一个奇奇怪怪的 \(n\log n\) 的做法。首先特判掉字符串中全是 \(0\) 的情况,这种情况答案显然为 \(n\)。我们假设字符串中 \(1\) 的位置为 \(p_1,p_2,\cdots,p_k\)。考虑当我们已经求出了 \(s[1...p_i]\) 可以得到多少个不同的 01 串后,怎样求出 \(s[1...p_{i+1}]\) 可以得到多少个不同的 01 串。

  • 显然原来 \(s[1...p_i]\) 可以得到的子串,也可以通过 \(s[1...p_{i+1}]\) 得到,因为我们可以将 \(p_{i+1}\) 与 \(p_{i+1}-1\) 合并,\(p_{i+1}-1\) 与 \(p_{i+1}-2\) 合并,……,\(p_i+1\) 与 \(p_i\) 合并,这样我们就合并得到了 \(s[1...p_i]\)。对于 \(s[1...p_i]\) 可以得到的子串 \(t\),再通过 \(s[1...p_i]\to t\) 的合并方式即可合并得到 \(t\)。
  • 对于原来 \(s[1...p_i]\) 可以得到的子串 \(t\),在其后面添上 \(j(0\le j\le p_{i+1}-p_i-1)\) 个 \(0\) 和一个 \(1\) 得到的字符串也可以被合并出来,因为我们可以按照 \(s[1...p_i]\to t\) 的合并方式合并 \(s[1...p_{i+1}]\) 的前 \(p_i\) 位,然后把最后一段 \(00...01\) 合并只剩 \(j\) 个 \(0\) 和 \(1\) 个 \(1\)。

假设 \(dp_i\) 为 \([1...p_i]\) 可以得到多少个 01 串,照这样讲,难道就有 \(dp_{i+1}=(p_{i+1}-p_i)\times dp_i\) 吗?

非也,不然您觉得这题咋就能放到 D1E 呢/ww

这样做的问题是,会出现重复计算的字符串,比方说 \(s=\texttt{10101}\),那么当我们计算 \(s[1...p_i]\) 可以得到的子串(第一类)时会统计到字符串 \(\texttt{101}\),而 \(s[1...p_i]\) 还可以得到一个字符串 \(\texttt{1}\),在 \(\texttt{1}\) 后面添上一个 \(0\) 和一个 \(1\) 后也能得到 \(\texttt{101}\),也就是说 \(\texttt{101}\) 被统计了两次。

怎样避免这种情况呢?显然这种算重的情况只会发生在第一类与第二类之间,因为每一类内部的字符串是不会算重的(对于上述转移中的第一种情况,原来 \(s[1...p_i]\) 根据我们 \(dp\) 状态的定义已经“本质不同”了,不会重复计算,而对于上述转移中的第二种情况,要么最后一段 \(0\) 的个数 \(j\) 不同,要么去掉最后一段 \(00...01\) 之后,得到的可以通过 \(s[1...p_i]\) 合并得到的子串不同,也不会算重),也就是说算重的字符串只可能是对于某个 \(s[1...p_i]\) 得到的 \(t\),它加上 \(j\) 个 \(0\) 和一个 \(1\) 后得到的字符串照样可以通过 \(s[1...p_i]\) 得到。因此我们可以转化为统计有多少个可以被 \(s[1...p_i]\) 的字符串,它最后一段 \(00...01\) 中 \(0\) 的个数在 \([0,p_{i+1}-p_i-1]\) 中。

考虑换个状态设计,\(dp_{i,j}\) 表示考虑 \([1...p_i]\) 的字符串,有多少个满足最后一段 \(00...01\) 中 \(0\) 的个数为 \(j\),这样一来转移就比较明显了,记 \(S=\sum\limits_{j}dp_{i,j}\),对于 \(j>p_{i+1}-p_i-1\),显然用第二种情况转移得来的字符串不可能得到最后一段 \(j\) 个 \(0\) 的 01 串,故 \(dp_{i+1,j}=dp_{i,j}\),对于 \(j\le p_{i+1}-p_i-1\),第一种情况贡献 \(dp_{i,j}\) 个字符串,第二种情况贡献 \(S\) 个字符串,但根据上面的推论还有 \(dp_{i,j}\) 个字符串被算重,故 \(dp_{i+1,j}=S+dp_{i,j}-dp_{i,j}=S\),还有一个小问题,就是对于形如 \(000...01\) 的字符串,它不能通过某个可以通过 \(s[1...p_i]\) 得到的字符串加上若干个 \(0\) 和一个 \(1\) 得到(因为它去掉最后一段 \(00...01\) 后就得到空串了,而空串不可能被我们得到),而显然如果 \(\begin{matrix}\underbrace{000...00}1\\j\text{个}0\end{matrix}\) 能够被 \(s[1...p_i]\) 得到,那么 \(j\le p_1-1\)。也就是说对于 \(j\le p_1-1\),字符串 \(\begin{matrix}\underbrace{000...00}1\\j\text{个}0\end{matrix}\) 被包含在了 \(dp_{i,j}\) 中,但事实上它没有被重复统计,故被重复统计的只有 \(dp_{i,j}-1\) 个,即 \(dp_{i+1,j}=S+dp_{i,j}-(dp_{i,j}-1)=S+1\),于是我们有状态转移方程:

\[dp_{i+1,j}=\begin{cases}S+1&j\le\min(p_1-1,p_{i+1}-p_i-1)\\S&p_1\le j\le p_{i+1}-p_i-1\\dp_{i,j}&j>p_{i+1}-p_i-1\end{cases}
\]

这样暴力复杂度是平方的,但借鉴 CF115E 的套路第一维显然可以去掉,用线段树维护第二维,只需实现一个区间赋值和全局求和,复杂度 \(n\log n\)。

又一次抢了最劣解(bushi

const int MOD=1e9+7;
const int MAXN=1e6;
char str[MAXN+5];int n;
struct node{int l,r,sum,lz;} s[MAXN*4+5];
void pushup(int k){s[k].sum=(s[k<<1].sum+s[k<<1|1].sum)%MOD;}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].lz=-1;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(~s[k].lz){
s[k<<1].sum=1ll*(s[k<<1].r-s[k<<1].l+1)*s[k].lz%MOD;
s[k<<1|1].sum=1ll*(s[k<<1|1].r-s[k<<1|1].l+1)*s[k].lz%MOD;
s[k<<1].lz=s[k].lz;s[k<<1|1].lz=s[k].lz;s[k].lz=-1;
}
}
void modify(int k,int l,int r,int x){
if(l>r) return;
if(l<=s[k].l&&s[k].r<=r){
s[k].sum=1ll*(s[k].r-s[k].l+1)*x%MOD;
s[k].lz=x;return;
} pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
pushup(k);
}
int main(){
scanf("%s",str+1);n=strlen(str+1);
int fst=0;for(int i=1;i<=n;i++) if(str[i]^48){fst=i;break;}
if(!fst) return printf("%d\n",n),0;int pre=fst;
build(1,0,n);modify(1,0,fst-1,1);
for(int i=fst+1;i<=n;i++) if(str[i]^48){
int len=i-pre,sum=s[1].sum;
modify(1,0,min(fst,len)-1,(sum+1)%MOD);
modify(1,min(fst,len),len-1,sum);
pre=i;
} printf("%d\n",1ll*s[1].sum*(n-pre+1)%MOD);
return 0;
}

似乎有线性的做法?被微分了/kk

接下来就是大部分题解中提到的线性做法了,考虑什么样的字符串可以被得到,对于一个 \(01\) 串 \(t\),它可以被 \(s\) 得到的充要条件是:

  • \(t\) 第一段 \(0\) 的长度 \(\le\) \(s\) 第一段 \(0\) 的长度
  • \(t\) 最后一段 \(0\) 的长度 \(\le\) \(s\) 最后一段 \(0\) 的长度
  • 假设 \(t\) 中间有 \(y\) 段 \(0\),长度分别为 \(g_1,g_2,\cdots,g_y\),\(s\) 中间有 \(x\) 段 \(0\),长度分别为 \(f_1,f_2,\cdots,f_x\),则存在 \(1\le p_1\le p_2\le\cdots\le p_y\le x\) 满足 \(g_{i}\le f_{p_i}\)。

前两个条件显然可以轻松判断,但是第三个条件貌似有些棘手,我们考虑用一个“匹配”的思想解释上面的内容,我们在扫描 \(t\) 的过程中维护一个指针 \(p\),表示最小的满足当前 \(t\) 的前缀 \(t[1...i]\) 能够从 \(s[1...j]\) 得到的 \(j\)。我们考虑加入一个字符会对 \(p\) 产生什么影响,若加入一个 \(1\),那么 \(p\) 显然会移动到 \(j\) 下一个 \(1\) 的位置,若加入一个 \(0\),则分三种情况:

  • 若 \(s_p='1'\),那么 \(p\) 会移动到下一个 \(0\) 的位置
  • 若 \(s_p='0'\),且 \(s_{p+1}='0'\),那么 \(p\) 显然会移动到 \(p+1\)
  • 若 \(s_p='0'\),且 \(s_{p+1}='1'\),那么意味着这段 \(0\) 的个数超过了当前指针所在的 \(0\) 的段的长度,也就是说这段 \(0\) 不够用了。我们记当前 \(p\) 所在 \(0\) 段为 \(s[L...R]\),那么我们就找到离当前 \(0\) 段最近的 \(0\) 段 \(s[l...r]\),满足 \(r-l+1>R-L+1\),并将指针移动到 \(l+R-L+1\) 的位置。这个稍微想想即可想明白。

如果在上面的过程中 \(p\) 无法再移动了,则说明无法匹配,否则可以匹配。我们考虑以此为 \(dp\) 状态进行转移。我们先预处理出 \(nxt_{i,j}\) 表示当前指针在 \(i\) 处,新加入一个字符 \(j\) 后指针会移动到哪儿,这显然可以单调栈求出。然后设 \(dp_{i,j}\) 表示当前指针在 \(i\) 处,最后一位为 \(j\) 的方案数,枚举下一位的值 \(k\) 然后转移到 \(dp_{nxt_{i,k},k}\) 即可。最后我们强制令最后一位为 \(1\),即统计所有 \(dp_{i,1}\) 的和,再乘上 \(s\) 中第一段 \(0\) 的长度和最后一段 \(0\) 的长度即可。

时间复杂度线性。

const int MAXN=1e6;
const int MOD=1e9+7;
char s[MAXN+5];pii seg[MAXN+5];int pcnt=0;
int n,nxt[MAXN+5][2],dp[MAXN+5][2];
void add(int &x,int y){((x+=y)>=MOD)&&(x-=MOD);}
int main(){
scanf("%s",s+1);n=strlen(s+1);int fst=0,lst=0;
for(int i=1;i<=n;i++) if(s[i]^48){fst=i;break;}
for(int i=n;i;i--) if(s[i]^48){lst=i;break;}
if(!fst) return printf("%d\n",n),0;
for(int l=1,r;l<=n;l=r+1){
r=l;if(s[l]^48) continue;
while(s[r]^49&&r<=n) r++;seg[++pcnt]=mp(r-1,r-l);
} int pre=n+1;
for(int i=n;i;i--){nxt[i][1]=pre;if(s[i]^48) pre=i;}
pre=n+1;
for(int i=n;i;i--){
if(s[i]^49) pre=i;
else nxt[i][0]=pre;
}
for(int i=1;i<=n;i++) if((s[i]^49)&&(s[i+1]^49))
nxt[i][0]=i+1;
stack<pii> stk;
for(int i=pcnt;i;i--){
while(!stk.empty()&&stk.top().se<=seg[i].se) stk.pop();
if(!stk.empty()) nxt[seg[i].fi][0]=stk.top().fi-stk.top().se+1+seg[i].se;
else nxt[seg[i].fi][0]=n+1;stk.push(seg[i]);
}
// for(int i=1;i<=n;i++) printf("%d %d\n",nxt[i][0],nxt[i][1]);
dp[fst][1]=1;int ans=0;
for(int i=fst;i<=n;i++) for(int j=0;j<2;j++){
for(int k=0;k<2;k++) if(nxt[i][k]<=n) add(dp[nxt[i][k]][k],dp[i][j]);
if(j==1) add(ans,dp[i][j]);
} printf("%d\n",1ll*ans*fst%MOD*(n-lst+1)%MOD);
return 0;
}

最新文章

  1. sys.stdout.write与sys.sterr.write(二)
  2. UVA 1395 (kruskal)
  3. 入门 ARM 汇编(二)—— 寻址方式
  4. GMap.Net开发之地址解析与路径查找
  5. 超好玩!10款神奇的字符图案 &amp; 词汇云生成工具
  6. 进击的Hybrid App,量身定做缓存机制
  7. WPF中的瀑布流布局(TilePanel)控件
  8. html+css学习笔记 2[标签]
  9. 使用intellij idea搭建MAVEN+springmvc+mybatis框架
  10. android开发之自定义AutoCompleteTextView
  11. linux终奌站 信息 格式 更改 /etc/bashrc
  12. [妙味Ajax]第二课:实例:留言板、瀑布流
  13. C# MODBUS协议 上位机(转)
  14. 应对linux下的闰秒
  15. Beta冲刺第六天
  16. Android反编译获取资源文件-android学习之旅(69)
  17. Testbench(转)
  18. MapReduce Notes
  19. 第197天:js---caller、callee、constructor和prototype用法
  20. Spring JDBC主从数据库访问配置

热门文章

  1. Java:并发笔记-02
  2. spring源码分析(二)- 容器基础
  3. camera isp(Image Signal Processor)
  4. Machine learning(2-Linear regression with one variable )
  5. sonar-project.propertie分析参数
  6. 转:BeanFactory和FactoryBean的区别
  7. cURL 命令获取本机外网 IP
  8. python pip whl安装和使用
  9. Luogu P1850 [NOIp2016提高组]换教室 | 期望dp
  10. SI Macro