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;
}

Codeforces 1383E - Strange Operation(线段树优化 DP or 单调栈+DP)的更多相关文章

  1. [Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路)

    [Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路) 题面 有n个空心物品,每个物品有外部体积\(out_i\)和内部体积\(in_i\),如果\(in_i& ...

  2. Codeforces 1107G Vasya and Maximum Profit 线段树最大子段和 + 单调栈

    Codeforces 1107G 线段树最大子段和 + 单调栈 G. Vasya and Maximum Profit Description: Vasya got really tired of t ...

  3. CodeForces 834D The Bakery(线段树优化DP)

    Some time ago Slastyona the Sweetmaid decided to open her own bakery! She bought required ingredient ...

  4. CodeForces 558E(计数排序+线段树优化)

    题意:一个长度为n的字符串(只包含26个小字母)有q次操作 对于每次操作 给一个区间 和k k为1把该区间的字符不降序排序 k为0把该区间的字符不升序排序 求q次操作后所得字符串 思路: 该题数据规模 ...

  5. CodeForces 786B Legacy(线段树优化建图+最短路)

    [题目链接] http://codeforces.com/problemset/problem/786/B [题目大意] 给出一些星球,现在有一些传送枪,可以从一个星球到另一个星球, 从一个星球到另一 ...

  6. Codeforces 786B Legacy(线段树优化建图)

    题目链接  Legacy 首先对于输入的$n$,建立一棵线段树. 显然线段树有大概$2n$个结点,每个节点对应一段区间 我们把这$2n$个结点加入我们的无向图中,一起跑最短路. 具体连边方案: 我们把 ...

  7. D - The Bakery CodeForces - 834D 线段树优化dp···

    D - The Bakery CodeForces - 834D 这个题目好难啊,我理解了好久,都没有怎么理解好, 这种线段树优化dp,感觉还是很难的. 直接说思路吧,说不清楚就看代码吧. 这个题目转 ...

  8. Codeforces 1603D - Artistic Partition(莫反+线段树优化 dp)

    Codeforces 题面传送门 & 洛谷题面传送门 学 whk 时比较无聊开了道题做做发现是道神题( 介绍一种不太一样的做法,不观察出决策单调性也可以做. 首先一个很 trivial 的 o ...

  9. Codeforces Round #426 (Div. 2) D 线段树优化dp

    D. The Bakery time limit per test 2.5 seconds memory limit per test 256 megabytes input standard inp ...

随机推荐

  1. 【Linux命令063】Linux非常简单常用的入门命令

    Linux常用命令 这是一篇我在公众号上发布的文章,还算较为受欢迎. 博客园这边荒废好长时间了,主要是最近一年经常撰写的文章都是Linux相关的入门文章. 不知道是否能通过博客园的首页审核. 1.cd ...

  2. Alpha阶段初始任务分配

    项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 团队项目-计划-Alpha阶段说明书 一.Alpha阶段总体规划 进行服务器相关部署 进行开发相关技术学习 ...

  3. 机器人的运动范围 牛客网 剑指Offer

    机器人的运动范围 牛客网 剑指Offer 题目描述 地上有一个m行和n列的方格.一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大 ...

  4. mac bigsur 安装mysql步骤

    我首先下载的是mysql8.x,安装完后,在偏好设置里面,双击mysql图标,弹窗:未能载入偏好设置面板MySQL,重启无果,查攻略说是要安装5.7.x,在mysql官网上,下载5.7.29 强烈建议 ...

  5. 第36篇-return字节码指令

    方法返回的字节码相关指令如下表所示. 0xac ireturn 从当前方法返回int 0xad lreturn 从当前方法返回long 0xae freturn 从当前方法返回float 0xaf d ...

  6. zabbix web管理页面 中文乱码问题

    1.在自己电脑上找下图文件,C:\Windows\Fonts 2.上传到 /usr/share/zabbix/assets/fonts/ 目录下 可以看到 graphfont.ttf 是 /etc/a ...

  7. 基于消息队列 RocketMQ 的大型分布式应用上云最佳实践

    作者|绍舒 审核&校对:岁月.佳佳 编辑&排版:雯燕 前言 消息队列是分布式互联网架构的重要基础设施,在以下场景都有着重要的应用: 应用解耦 削峰填谷 异步通知 分布式事务 大数据处理 ...

  8. 从0到1使用Kubernetes系列(六):数据持久化实战

    本文是从 0 到 1 使用 Kubernetes 系列第六篇,上一篇<从 0 到 1 使用 Kubernetes 系列(五):Kubernetes Scheduling>介绍了 Kuber ...

  9. url的hash和HTML5的history

    url的hash和HTML5的history 第一种方法是改变url的hash值. **显示当前路径 : **location.href http://localhost:8080/# 切换路径: l ...

  10. let that = this用法解析

    这种情况就是在一个代码片段里this有可能代表不同的对象,而编码者希望this代表最初的对象