Codeforces 1383E - Strange Operation(线段树优化 DP or 单调栈+DP)
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\),于是我们有状态转移方程:
\]
这样暴力复杂度是平方的,但借鉴 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)的更多相关文章
- [Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路)
[Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路) 题面 有n个空心物品,每个物品有外部体积\(out_i\)和内部体积\(in_i\),如果\(in_i& ...
- Codeforces 1107G Vasya and Maximum Profit 线段树最大子段和 + 单调栈
Codeforces 1107G 线段树最大子段和 + 单调栈 G. Vasya and Maximum Profit Description: Vasya got really tired of t ...
- CodeForces 834D The Bakery(线段树优化DP)
Some time ago Slastyona the Sweetmaid decided to open her own bakery! She bought required ingredient ...
- CodeForces 558E(计数排序+线段树优化)
题意:一个长度为n的字符串(只包含26个小字母)有q次操作 对于每次操作 给一个区间 和k k为1把该区间的字符不降序排序 k为0把该区间的字符不升序排序 求q次操作后所得字符串 思路: 该题数据规模 ...
- CodeForces 786B Legacy(线段树优化建图+最短路)
[题目链接] http://codeforces.com/problemset/problem/786/B [题目大意] 给出一些星球,现在有一些传送枪,可以从一个星球到另一个星球, 从一个星球到另一 ...
- Codeforces 786B Legacy(线段树优化建图)
题目链接 Legacy 首先对于输入的$n$,建立一棵线段树. 显然线段树有大概$2n$个结点,每个节点对应一段区间 我们把这$2n$个结点加入我们的无向图中,一起跑最短路. 具体连边方案: 我们把 ...
- D - The Bakery CodeForces - 834D 线段树优化dp···
D - The Bakery CodeForces - 834D 这个题目好难啊,我理解了好久,都没有怎么理解好, 这种线段树优化dp,感觉还是很难的. 直接说思路吧,说不清楚就看代码吧. 这个题目转 ...
- Codeforces 1603D - Artistic Partition(莫反+线段树优化 dp)
Codeforces 题面传送门 & 洛谷题面传送门 学 whk 时比较无聊开了道题做做发现是道神题( 介绍一种不太一样的做法,不观察出决策单调性也可以做. 首先一个很 trivial 的 o ...
- 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 ...
随机推荐
- 什么是Spring,SpringMVC,SpringBoot,SpringCloud?通俗易懂
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架.Spring使你能够编写更干净.更可管理.并且更易于测试的代码. Spring MVC是Spring的一个模块,一个web框 ...
- 计算机网络:HTTP
计算机网络基础:HTTP 先验知识 HTTP和其他协议的关系 通过下图,了解IP协议,TCP协议,DNS服务在使用HTTP协议通信过程中各自发挥的作用: 服务器处理流程 接受客户端连接 ------& ...
- UltraSoft - Beta - Scrum Meeting 5
Date: May 21st, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 修复了课程通知链接的bug Liuzh 前端 暂无 Kkkk 前端 增加消息中心板 ...
- TCP之拥塞窗口原理
学过网络相关课程的,都知道TCP中,有两个窗口: 滑动窗口(在我们的上一篇文章中有讲),接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),从而控制发送方的发送速度. 拥塞 ...
- 实验7:基于REST API的SDN北向应用实践
一.实验目的 1.能够编写程序调用OpenDaylight REST API实现特定网络功能: 2.能够编写程序调用Ryu REST API实现特定网络功能. 二.实验环境 下载虚拟机软件Oracle ...
- Django(72)Django认证系统库--djoser
djoser是什么? 作用:Django认证系统的REST实现.djoser库提供了一组Django Rest Framework视图,用于处理注册.登录.注销.密码重置和帐户激活等基本操作.它适 ...
- STM32程序异常——中断处理要谨慎
问题背景 最近有一个新项目(车载项目),板子上除了原来的ARM + STM32F030K6Tx又多了一个8bit的mcu的单片机,这可真是嵌入式全家福了. 系统的主要核心工作是由arm来完成,但是在开 ...
- F. Mattress Run 题解
F. Mattress Run 挺好的一道题,对于DP的本质的理解有很大的帮助. 首先要想到的就是将这个拆成两个题,一个dp光求获得足够的夜晚的最小代价,一个dp光求获得足够的停留的最小代价. 显然由 ...
- 前端需要了解的颜色模型,RGB、HSL和HSV
颜色模型,是用来表示颜色的数学模型.比如最常见的 RGB模型,使用 红绿蓝 三色来表示颜色. 一般的颜色模型,可以按照如下分类: 面向硬件设备的颜色模型:RGB,CMYK,YCrCb. 面向视觉感知的 ...
- 变量命名网站 Codelf
程序员最头疼的事情除了头发以外就是给变量或函数命名,一开始学编程语言的时候还可以 abc.a1.x2 等方式命名,等到工作过程中开始真正的项目开发时,如果还是这样随意的命名,即使同事可以忍受你的 ab ...