Codeforces.1029D.Isolation(DP 分块)
\(Description\)
给定长为\(n\)的序列\(A_i\)和一个整数\(K\)。把它划分成若干段,满足每段中恰好出现过一次的数的个数\(\leq K\)。求方案数。
\(K\leq n\leq10^5\)。
\(Solution\)
设\(f[i]\)表示前\(i\)个数的答案,\(g[j]\)表示\(j\sim i\)恰好出现过一次的数的个数。有$$f[i]=\sum_{j\leq i,\ g[j]\leq K}f[j-1]$$
记\(las_i\)为\(A_i\)上次出现的位置下标。每次\(i\)移动时,\(g[j]\)的变化就是,\([las_i+1,i]\)区间\(+1\),\([las_{las_i}+1,las_i]\)区间\(-1\)。
也就是要动态修改\(g[j]\),求\(g[j]\leq K\)的\(f[j-1]\)的和。
数据结构什么的不好搞。考虑直接分块。
一种最简单的想法是,块内sort后维护前缀和,查询的时候二分。复杂度\(O(n\sqrt n\log(\sqrt n))\)(注意确实是\(\log(\sqrt n)\)),但过不去。
设\(s[i][j]\)表示第\(i\)块中,\(g[k]\leq j\)的\(f[k]\)的和,\(tag[i]\)表示第\(i\)块的整体修改标记。
考虑区间修改。对于整块直接打标记。对于零散部分,因为只是\(+1\),容易发现对于第\(i\)块,只有\(s[i][g[j]]\)的值改变了,且只是少掉了\(f[j-1]\)(\(j\)是影响到的下标,显然可以暴力枚举)。那么可以暴力更新\(s[i]\)。
对于整块的查询,假设是第\(i\)块,需要满足\(j+tag[i]\leq k\),即\(j\leq k-tag[i]\),那么\(s[i][k-tag[i]]\)就是答案了。
那么这样就可以啦。
其实还可以优化。
把每个修改拆成前缀修改,即:\([1,i]\)区间\(+1\),\([1,las_i]\)区间\(-2\),\([1,las_{las_i}]\)区间\(+1\)。
这样有什么好处呢。设\(i\)所在的块为\(p\)。那么对\(p\)块零散部分暴力修改,对\(1\sim p-1\)块统一打上标记\(tag\)。
可以发现这样\(s[i][j]\)的第二维是\(O(\sqrt n)\)级别的(只有同块内的会影响它,其它值都打到\(tag\)上了)!也就是空间只需要\(O(n)\)就够了。
而且如果我们把询问也拆成前缀的形式(其实本来就是前缀),那\(tag\)完全不需要直接打到\(1\sim p-1\)上,只需要在\(p\)上打就可以了。查询的时候维护一个\(tag\)的后缀和即可。
这样设块大小为\(B\),修改复杂度就只有\(O(B)\),查询复杂度还是\(O(B+\frac nB)\),但是某些修改比较多查询比较少的题目就可以调整\(B\)的大小解决啦。
虽然在这题复杂度依旧是\(O(n\sqrt n)\),但是常数不知道优秀到了哪里去。
中间过程(包括查询啊=-=)\(s\)的第二维可能是负的,但绝对值在\(\sqrt n\)内。
//233ms 2600KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define B 150
#define mod 998244353
#define Mod(x) x>=mod&&(x-=mod)
#define Add(x,v) (x+=v)>=mod&&(x-=mod)
#define gc() getchar()
typedef long long LL;
const int N=1e5+5,M=N/B+3;
int bel[N],f[N],g[N],tag[M],s[M][B+3<<1];
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now;
}
void Update(int p,int v)
{
int *s=::s[bel[p]];
for(int i=B; i<=B<<1; ++i) Add(s[i],v);
}
void Modify(int p,int v)
{
int bel=::bel[p],*s=::s[bel];
tag[bel]+=v;
for(int i=bel*B+1; i<=p; ++i)
{
if(v==1) Add(s[g[i]+B],mod-f[i-1]);
else Add(s[g[i]-1+B],f[i-1]), Add(s[g[i]-2+B],f[i-1]);
g[i]+=v;
}
}
int Query(int p,int K)
{
int bel=::bel[p],sum=tag[bel]; LL res=0;
for(int i=bel*B+1; i<=p; ++i) g[i]<=K&&(res+=f[i-1]);
while(bel--)
{
// assert(sum>=0);
// if(sum<=K) res+=s[bel][std::min(B<<1,K-sum+B)];//WA:sum may be >K
if(std::abs(sum-K)<=B) res+=s[bel][K-sum+B];
else if(sum<K) res+=s[bel][B<<1];
sum+=tag[bel];
}
return res%mod;
}
int main()
{
static int las[N],pre[N];
int n=read(),K=read();
for(int i=1; i<=n; ++i) bel[i]=(i-1)/B;
f[0]=1;
for(int i=1; i<=n; ++i)
{
int a=read(); las[i]=pre[a], pre[a]=i;
Update(i,f[i-1]), Modify(i,1);
if(las[i])
{
Modify(las[i],-2);
if(las[las[i]]) Modify(las[las[i]],1);
}
f[i]=Query(i,K);
}
printf("%d\n",f[n]);
return 0;
}
Codeforces.1029D.Isolation(DP 分块)的更多相关文章
- Codeforces 1129D - Isolation(分块优化 dp)
Codeforces 题目传送门 & 洛谷题目传送门 又独立切了道 *2900( 首先考虑 \(dp\),\(dp_i\) 表示以 \(i\) 为结尾的划分的方式,那么显然有转移 \(dp_i ...
- Codeforces Round #355 (Div. 2) D. Vanya and Treasure dp+分块
题目链接: http://codeforces.com/contest/677/problem/D 题意: 让你求最短的从start->...->1->...->2->. ...
- CF1129D Isolation(分块+DP)
一个很显然的DP方程式:f[i]=Σf[j],其中j<i且在[j+1,i]中出现1次的数不超过k个 乍一看挺神仙的,只会O(n^2),就是对于每个位置从后向前扫一遍,边扫边统计出现1次的数的个数 ...
- codeforces 682D(DP)
题目链接:http://codeforces.com/contest/682/problem/D 思路:dp[i][j][l][0]表示a串前i和b串前j利用a[i] == b[j]所得到的最长子序列 ...
- codeforces 666A (DP)
题目链接:http://codeforces.com/problemset/problem/666/A 思路:dp[i][0]表示第a[i-1]~a[i]组成的字符串是否可行,dp[i][1]表示第a ...
- Codeforces 176B (线性DP+字符串)
题目链接: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=28214 题目大意:源串有如下变形:每次将串切为两半,位置颠倒形成 ...
- Codeforces 55D (数位DP+离散化+数论)
题目链接: http://poj.org/problem?id=2117 题目大意:统计一个范围内数的个数,要求该数能被各位上的数整除.范围2^64. 解题思路: 一开始SB地开了10维数组记录情况. ...
- Codeforces 264B 数论+DP
题目链接:http://codeforces.com/problemset/problem/264/B 代码: #include<cstdio> #include<iostream& ...
- CodeForces 398B 概率DP 记忆化搜索
题目:http://codeforces.com/contest/398/problem/B 有点似曾相识的感觉,记忆中上次那个跟这个相似的 我是用了 暴力搜索过掉的,今天这个肯定不行了,dp方程想了 ...
随机推荐
- undefined reference的一种case
undefined reference是经常遇到的链接错误,一般是函数未定义或未正确链接引起的问题: but 有一种case,比较坑... c++ 调用 c 的函数,当c函数未加:extern “C” ...
- 第一节: Redis之String类型和Hash类型的介绍和案例应用
一. String类型基础 1.类型介绍 典型的Key-Value集合,如果要存实体,需要序列化成字符串,获取的时候需要反序列化一下. 2. 指令Api说明 3.常用Api说明 (1).StringS ...
- WPF树形菜单--递归与非递归遍历生成树结构的集合
一.新建了WPF项目作为测试,使用TreeView控件进行界面展示. 第一步创建实体类TreeEntity: public class TreeEntity { private int _mid; p ...
- OpenJDK下SpringBoot使用HttpSession时页面打开卡住
近期将一个老项目向ARM版的CentOS7移植时,遇到了SpringBoot启动顺利,但访问页面卡住的问题.由于是aarch64架构,因此使用了openjdk,这个项目之前在x86_64环境下一直是用 ...
- .NET Core RabbitMQ探索(1)
RabbitMQ可以被比作一个邮局,当你向邮局寄一封信时,邮局会保证将这封信送达你写的收件人,而RabbitMQ与邮局最主要的区别是,RabbitMQ并不真的处理信件,它处理的是二进制的数据块,它除了 ...
- LiveBOS Webservice传参类型为list数组
昨天有使用soap传输数据到Webservice,其中字符串类型的都已经传输成功,但是有几个参数传输失败,java服务器端收到的空值. 因为我是php的,然后接收端是java制作的,其中有几个参数是l ...
- Linux帮助——获取帮助
Linux帮助——获取帮助 摘要:本文主要学习了Linux众多命令中最基础的帮助命令. 介绍 作用 Linux的所有操作都可以通过命令行来完成,所以学习Linux最好从命令行开始.因为Linux的命令 ...
- React中的三大属性
一.前言: 属性1:state 属性2:props 属性3:ref 与事件处理 二.主要内容: 属性1:state 1,认识: 1) state 是组件对象中最重要的属性,值是一个对象(可以包含多个数 ...
- 【转】Git使用教程之BUG分支
1.bug分支 在开发中,会经常碰到bug问题,那么有了bug就需要修复,在Git中,分支是很强大的,每个bug都可以通过一个临时分支来修复,修复完成后,合并分支,然后将临时的分支删除掉. 比如我在开 ...
- C++智能指针解析
前言 在C++程序中,内存分为三种静态内存.栈内存.堆内存.其中静态内存和栈内存由系统进行维护,而堆内存则是由程序员自己进行维护,也就是我们在new和delete对象时,这些对象存放的区域.任何有C+ ...