题解:P17325 [ICPC 2018 Nanjing R] Huge Discount

· · 题解

题意简述

给定一个只含 0,1,2 的数字串。每次可以删除一对相邻且不同的数字。对每个后缀求能够得到的最小价格,并输出所有价格之和。

解题思路

对字符串 t,记数字 d 的出现次数为 c_d(t),定义它相对其余数字的数量优势:

b_d(t)=2c_d(t)-|t|

每次删除两个不同数字,相当于把两个不同种类的字符配对。若 b_d(t)>0,其他字符总数不足以与全部 d 配对,所以至少会剩下 b_d(t)d。反复让 d 与相邻的其他字符消去,并在剩余部分继续归纳,可以恰好只留下这些 d

若没有数字超过一半,偶数长度字符串可以全部删空。奇数长度字符串至少留下一个字符,但不会被迫留下两个以上的同种字符。

因此,一个后缀 t 的最小价格先分成三类:

只剩 |t| 为奇数且 b_1(t),b_2(t)\le0 的情况。此时最小价格只可能是 01。若首位就是 0,保留它并处理后面的偶数长度部分,价格一定可以降到 0

设首位不是 0。价格能够降到 0,当且仅当存在一个奇数长度后缀 u,满足:

先证必要性。若 t 最后只剩 0,选择一个最终被保留的 0,令 u 从它开始。u 自身不能被迫留下 12,所以满足第二条。设 u 之前的偶数长度前缀为 p,则:

b_d(t)-b_d(u)=b_d(p) 再证充分性。第三条说明 $p$ 中的 $1$ 和 $2$ 均不超过其余字符总数,所以 $p$ 可以消成空串或若干个 $0$。$u$ 以 $0$ 开头,且其中的 $1,2$ 也都不占多数,所以它同样可以只留下 $0$。两部分合起来,$t$ 的价格为 $0$。若不存在这样的 $u$,奇数长度又不能删空,最小价格就是 $1$。 从右向左枚举 $t$,并维护所有满足前两条的候选后缀 $u$。每个候选对应二维点: $$ (b_1(u),b_2(u)) $$ 当前只需查询是否存在一个点同时满足两个坐标不小于 $(b_1(t),b_2(t))$。这是动态二维支配查询。 把第一坐标反向映射为: $$ q(x)=n-x+1 $$ 使用树状数组维护每个前缀中出现过的最大第二坐标。插入点 $(x,y)$ 时,在位置 $q(x)$ 更新值 $y+n+1$;查询当前点时,计算 `ask(q(x))`。候选的第一坐标不小于当前值,当且仅当它的映射位置不大于当前查询位置。若前缀最大第二坐标也不小于当前 $y$,所需候选就存在。 最后还要累加最长可达 $10^5$ 位的价格。由 $k$ 个相同数字 $d$ 组成的价格,会让十进制的第 $1$ 至第 $k$ 个低位各增加 $d$。在低位在前的数组上做区间差分,处理完全部后缀后求前缀和,再统一向高位进位即可。 每个后缀进行常数次树状数组操作,时间复杂度为 $O(n\log n)$,空间复杂度为 $O(n)$。 ## 正确性证明 若某个数字超过字符串长度的一半,每次删除至多消去一个该数字,故至少剩下它的数量优势;通过不断与其他数字配对可以达到这个下界。没有多数数字时,偶数长度可以完全配对,奇数长度只能留下一个字符。前三类后缀的最小价格因此正确。 对剩余奇数后缀,前述三条件的必要性来自任取一个最终保留的零,并把它之前、之后分别观察;充分性来自两部分都能消成空串或纯零串。故价格为零与存在支配候选点完全等价,不存在时最小正价格只能是 $1$。 树状数组查询覆盖所有第一坐标不小于当前值的已插入候选,并返回其中最大的第二坐标。因此,它判定成功当且仅当存在同时满足两维不等式的候选。候选只在首位为零、长度为奇数且两个优势不为正时插入,恰好对应判定中的前两条。 差分数组给每个后缀的最小价格逐位增加正确的数字,十进制进位保持数值不变。因此,最终输出就是所有后缀最小价格之和。 ## 参考代码 ```cpp #include <bits/stdc++.h> using namespace std; const int N=300005; int tr[N],ans[N]; void add(int x,int v) { for(;x<N;x+=x&-x)tr[x]=max(tr[x],v); } int ask(int x) { int res=0; for(;x;x-=x&-x)res=max(res,tr[x]); return res; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; string s; cin>>n>>s; int x=0,y=0; for(int i=n-1;i>=0;i--) { x+=s[i]=='1'?1:-1; y+=s[i]=='2'?1:-1; if(x>0) { ans[1]++; ans[x+1]--; } else if(y>0) { ans[1]+=2; ans[y+1]-=2; } else if((n-i)&1) { int p=n-x+1; if(s[i]=='0')add(p,y+n+1); else if(ask(p)<y+n+1) { ans[1]++; ans[2]--; } } } for(int i=1;i<N-1;i++)ans[i]+=ans[i-1]; for(int i=1;i<N-1;i++) { ans[i+1]+=ans[i]/10; ans[i]%=10; } int p=N-1; while(p>1&&!ans[p])p--; for(int i=p;i;i--)cout<<ans[i]; cout<<'\n'; return 0; } ```