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 ...
随机推荐
- Kali安装OWASP
我是2019版的kali,里面并没有自带OWASP工具,因为OWASP不再更新的因素,所以新版kali将它移除了 安装OWASP apt-get install zaproxy #以下都是安装软件时 ...
- 每日一题,是否存在(c语言)
每日一题:1.是否存在 是否存在描述猫咪非常喜欢饼干,尤其是字母饼干.现在,她得到一些字母饼干,她希望选择他们中的一些拼写某些单词. 你的任务是确定她是否可以拼出自己想要的单词. 输入输入包含若干测试 ...
- 微信小程序的发布流程
一.背景 在中大型的公司里,人员的分工非常仔细,一般会有不同岗位角色的员工同时参与同一个小程序项目.为此,小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团队的协同工作 以往我们在开发 ...
- the Agiles Scrum Meeting 8
会议时间:2020.4.16 20:00 1.每个人的工作 今天已完成的工作 个人结对项目增量开发组:完成个人项目创建的部分功能 issues:增量组:准备评测机制,增加仓库自动创建和管理 团队项目增 ...
- cf17A Noldbach problem(额,,,素数,,,)
题意: 判断从[2,N]中是否有超过[包括]K个数满足:等于一加两个相邻的素数. 思路: 枚举. 也可以:筛完素数,枚举素数,直到相邻素数和超过N.统计个数 代码: int n,k; int prim ...
- linux 内核源代码情景分析——linux 内核源代码中的C语言代码
linux 内核的主体是以GNU的C语言编写的,GNU为此提供了编译工具gcc.GNU对C语言本身作了不少扩充. 1) gcc 从 C++ 语言中吸收了"inline"和" ...
- 面试官:能手写实现call、apply、bind吗?
1 call.apply.bind 用法及对比 1.1 Function.prototype 三者都是Function原型上的方法,所有函数都能调用它们 Function.prototype.call ...
- Swift-技巧(四)设置照片尺寸和格式
摘要 平时实现拍照功能时,都是网上一通搜索,整体复制粘贴,自称无脑实现.但是当要求照片是不同的尺寸和格式( JPEG)时,就费力搞照片.其实在设置拍照时,就可以直接设置照片的尺寸和格式,用直接的方法来 ...
- 安装RedHat和Centos后做的15件事情
由于之前的Centos 7不支持无线网络连接,我尝试着将内核升级至4.8还是无效,遂决定换回RedHat 7,目前系统已经安装好,版本是Red Hat Enterprise Linux 7.3,下面是 ...
- LeetCode刷题 二分专题
二分专题 二分的题目类型 对于满足二段性的题目的两套模板 模板一 模板如下 模板二 模板如下 解决二分题目的一般流程 LeeCode实战 LC69.x的平方根 解法思路 LC35.搜索插入位置 解法思 ...