先写出朴素的DP方程f[i][j]=f[k][j-1]+h[k+1][i] {k<i}(h表示[k+1,j]有几个不同的数)

    显然时间空间复杂度都无法承受

    仔细想想可以发现对于一个点 i 从 k 转移了,证明了 1~k 中 k 一定是最优的,不论对于i或者i之后的任何点x都是如此,不存在对i最优但对i之后的任何点x更优,否则i也可以更优(因为只多了一段h[i+1][x](i<x),而这一段显然是相同的),其实打表也能看出来(orz CYC!)

证明:

   假设a<b,k>l,a从k转移最优,b从l转移最优

   则有

     f[k]+h[k+1][b]<f[l]+h[l+1][b],f[l]+h[l+1][a]<f[k]+h[k+1][a]

   则有

     h[k+1][b]+h[l+1][a]<h[l+1][b]+h[k+1][a]          

  则有

     h[k+1][b]-h[k+1][a]<h[l+1][b]-h[l+1][a]

  则有

     h[a][b]<h[a][b]

   ∴不成立

   于是我们就证明了这题的决策单调性

   有了这个性质之后我们就可以分治优化了,查询一个区间里有多少个不同的数用主席树就行了。

   效率O(KNlogNlogN)约等于4亿...TLE QAQ

实际上主席树有更巧妙的用法可以优化到KNlogN,等会补       不会,委屈的折耳猫.jpg

  在CYC大爷的教导下会了!本来不保证复杂度的话直接边分治边递推就行了,但是R~MID可能被统计多次,会TLE。那怎么办呢,把这一段用主席树查一下,就可以把复杂度降低到log了。这样的log是独立于转移之外的,复杂度为O(NKlogN)

  而且常数明显是比线段树小的!线段树需要区间修改,上传下传,而且每次转移完都要修改,而主席树建树之后就不用再修改了,并且建树是O(NlogN)的,跑的飞快。

以下全部为极限数据(未打开O2优化):

主席树:

线段树:

  代码已更新

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
#define ll long long
using namespace std;
const int maxn=,inf=1e9;
struct poi{int sum,lt,rt;}tree[maxn*];
int n,K,N,now,sz;
int a[maxn],b[maxn],f[maxn][],root[maxn],pre[maxn],last[maxn],h[maxn],next[maxn];
bool v[maxn];
void read(int &k)
{
int f=;k=;char c=getchar();
while(c<''||c>'')c=='-'&&(f=-),c=getchar();
while(c<=''&&c>='')k=k*+c-'',c=getchar();
k*=f;
}
inline void update(int &x,int l,int r,int cx)
{
tree[++sz]=tree[x];tree[sz].sum++;x=sz;
if(l==r)return;
int mid=(l+r)>>;
if(cx<=mid)update(tree[x].lt,l,mid,cx);
else update(tree[x].rt,mid+,r,cx);
}
inline int query(int x,int y,int l,int r,int cl,int cr)
{
if(cl<=l&&r<=cr)return tree[y].sum-tree[x].sum;
int mid=(l+r)>>,ret=;
if(cl<=mid)ret+=query(tree[x].lt,tree[y].lt,l,mid,cl,cr);
if(cr>mid)ret+=query(tree[x].rt,tree[y].rt,mid+,r,cl,cr);
return ret;
}
void solve(int l,int r,int L,int R,int now)
{
if(l>r||L>R)return;
int mid=(l+r)>>;
int pos;f[mid][now&]=-inf;
int noww=;
if(R+<mid)noww=query(root[R+],root[mid],,n,,R+);
for(int i=min(R+,mid);i>L;i--)
{
if(next[i]>mid)noww++;
h[i]=noww;
}
for(int i=L;i<=R&&i<mid;i++)
{
if(f[i][(now&)^]+h[i+]>f[mid][now&])
f[mid][now&]=f[i][(now&)^]+h[i+],pos=i;
}
solve(l,mid-,L,pos,now);solve(mid+,r,pos,R,now);
}
int main()
{
freopen("camp.in","r",stdin);
freopen("camp.out","w",stdout);
read(n);read(K);
for(int i=;i<=n;i++)read(a[i]),b[i]=a[i];N=n;
sort(b+,b++N);N=unique(b+,b++N)-b-;
for(int i=;i<=n;i++)a[i]=lower_bound(b+,b++N,a[i])-b;
for(int i=;i<=n;i++)pre[i]=last[a[i]],last[a[i]]=i;
memset(last,,(n+)<<);
for(int i=n;i;i--)next[i]=last[a[i]],last[a[i]]=i;
for(int i=;i<=n;i++)update(root[i]=root[i-],,n,pre[i]);
for(int i=;i<=K;i++)solve(,n,,n,i);
printf("%d\n",f[n][K&]);
return ;
}

    那个方程还可以直接用线段树优化,同样是记录上次出现的位置,加入一个数有影响的只有上次出现位置+1开始的区间,然后就线段树存一下转移方程右边的值logn找max就行了

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=,inf=1e9;
struct poi{int max,delta;}tree[maxn*];
int n,K,N,cnt;
int a[maxn],b[maxn],pre[maxn],root;
int f[maxn][];
void read(int &k)
{
int f=;k=;char c=getchar();
while(c<''||c>'')c=='-'&&(f=-),c=getchar();
while(c<=''&&c>='')k=k*+c-'',c=getchar();
k*=f;
}
inline int max(int a,int b){return a>b?a:b;}
inline void pushup(int x){tree[x].max=max(tree[x<<].max,tree[x<<|].max);}
inline void pushdown(int x)
{
if(!tree[x].delta)return;
tree[x<<].delta+=tree[x].delta;
tree[x<<|].delta+=tree[x].delta;
tree[x<<].max+=tree[x].delta;
tree[x<<|].max+=tree[x].delta;
tree[x].delta=;
}
void build(int x,int l,int r,int ty)
{
if(l==r){tree[x].max=f[l-][ty];tree[x].delta=;return;}
int mid=(l+r)>>;tree[x].delta=;
build(x<<,l,mid,ty);
build(x<<|,mid+,r,ty);
pushup(x);
}
inline void add(int x,int l,int r,int cl,int cr)
{
if(cl<=l&&r<=cr){tree[x].max++;tree[x].delta++;return;}
pushdown(x);
int mid=(l+r)>>;
if(cl<=mid)add(x<<,l,mid,cl,cr);
if(cr>mid)add(x<<|,mid+,r,cl,cr);
pushup(x);
}
inline int query(int x,int l,int r,int cl,int cr)
{
if(cl<=l&&r<=cr)return tree[x].max;
pushdown(x);
int mid=(l+r)>>,ans=;
if(cl<=mid)ans=query(x<<,l,mid,cl,cr);
if(cr>mid)ans=max(ans,query(x<<|,mid+,r,cl,cr));
return ans;
}
int main()
{
freopen("camp.in","r",stdin);
freopen("camp.ans","w",stdout);
read(n);read(K);
for(int i=;i<=n;i++)read(a[i]),b[i]=a[i];N=n;
N=unique(b+,b++N)-b-;sort(b+,b++N);
for(int i=;i<=n;i++)a[i]=lower_bound(b+,b++n,a[i])-b;
for(int j=;j<=K;j++)
{
for(int i=;i<=n;i++)
{
add(,,n+,pre[a[i]]+,i);
f[i][j]=query(,,n+,,i);
pre[a[i]]=i;
}
if(j==K)continue;
build(,,n+,j);for(int i=;i<=n;i++)pre[a[i]]=;
}
printf("%d\n",f[n][K]);
}

makedata:

#include<iostream>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<ctime>
using namespace std;
int main()
{
freopen("camp10.in","w",stdout);
srand(time());
int n=;
printf("%d %d\n",n,);
for(int i=;i<=n;i++)
printf("%d ",+rand()%(+rand()%));
}

SRM12 T2夏令营(分治优化DP+主席树 (已更新NKlogN)/ 线段树优化DP)的更多相关文章

  1. Codeforces 777E(离散化+dp+树状数组或线段树维护最大值)

    E. Hanoi Factory time limit per test 1 second memory limit per test 256 megabytes input standard inp ...

  2. [APIO2019] [LOJ 3146] 路灯 (cdq分治或树状数组套线段树)

    [APIO2019] [LOJ 3146] 路灯 (cdq分治或树状数组套线段树) 题面 略 分析 首先把一组询问(x,y)看成二维平面上的一个点,我们想办法用数据结构维护这个二维平面(注意根据题意这 ...

  3. bzoj 4034 [HAOI2015] T2(树链剖分,线段树)

    4034: [HAOI2015]T2 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1536  Solved: 508[Submit][Status] ...

  4. 主席树(可持久化线段树) 静态第k大

    可持久化数据结构介绍 可持久化数据结构是保存数据结构修改的每一个历史版本,新版本与旧版本相比,修改了某个区域,但是大多数的区域是没有改变的, 所以可以将新版本相对于旧版本未修改的区域指向旧版本的该区域 ...

  5. BZOJ.4553.[HEOI2016&TJOI2016]序列(DP 树状数组套线段树/二维线段树(MLE) 动态开点)

    题目链接:BZOJ 洛谷 \(O(n^2)\)DP很好写,对于当前的i从之前满足条件的j中选一个最大值,\(dp[i]=d[j]+1\) for(int j=1; j<i; ++j) if(a[ ...

  6. HDU 5618 Jam's problem again(三维偏序,CDQ分治,树状数组,线段树)

    Jam's problem again Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Othe ...

  7. 主席树 【权值线段树】 && 例题K-th Number POJ - 2104

    一.主席树与权值线段树区别 主席树是由许多权值线段树构成,单独的权值线段树只能解决寻找整个区间第k大/小值问题(什么叫整个区间,比如你对区间[1,8]建立一颗对应权值线段树,那么你不能询问区间[2,5 ...

  8. st表、树状数组与线段树 笔记与思路整理

    已更新(2/3):st表.树状数组 st表.树状数组与线段树是三种比较高级的数据结构,大多数操作时间复杂度为O(log n),用来处理一些RMQ问题或类似的数列区间处理问题. 一.ST表(Sparse ...

  9. 树(一)——线段树

    问题 现在有1~30这30个数,数N被抽上的概率正比于1/sqrt(N+1),求满足这个概率分布的随机数发生器. 思路 第一,如何解决这个"概率正比"问题. 第二,如何产生满足条件 ...

随机推荐

  1. Kotlin的密封(Sealed)类:超强的枚举(KAD 28)

    作者:Antonio Leiva 时间:Jun 27, 2017 原文链接:https://antonioleiva.com/sealed-classes-kotlin/ Kotlin的封装类是Jav ...

  2. Linux命令应用大词典-第44章 PPPoE配置

    44.1 pppoe-setup:配置PPPoE客户端 44.2 ppoe-connect:管理PPPoE链路 44.3 pppoe-start:启动PPPoE链路 44.4 pppoe-stop:关 ...

  3. Linux命令应用大词典-第12章 程序编译

    12.1 gcc:GNU项目的C和C++编译器 12.2 gdberver:为GNU调试的远程服务器 12.3 cmake:跨平台的Makefile生成工具 12.4 indent:更改通过插入或删除 ...

  4. SSH:远程登陆

    SSH用于计算机之间的加密登录的前提是公钥为真,所以存在中间人攻击中间人攻击:与https协议不同,SSH协议的公钥是没有CA公证的,当对公钥的请求被中间截获时,中间人可以发出伪造公钥干坏事而不被识破 ...

  5. Java进阶知识点:更优雅地关闭资源 - try-with-resource

    一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...

  6. 6.hdfs的存储过程

    1.hdfs 怎么存储 切割存储 2. 为何每块是128m 与io读写速度有关,一般人的接受速度1s中,而磁盘的读写速度为100m/s,在读取文件时候需要硬盘寻找地址,一般读懂速度和寻找之间的比例是1 ...

  7. JavaScript中childNodes和children的区别

    我在学习JavaScript对DOM操作的过程中,发现了使用childNodes属性,得不到我想要的结果,因此我就从JavaScript高级程序设计中了解了childNodes和children的区别 ...

  8. USACO 1.1.3 Friday the Thirteenth 黑色星期五

    Description 13号又是一个星期5.13号在星期五比在其他日子少吗?为了回答这个问题,写一个程序,要求计算每个月的十三号落在周一到周日的次数.给出N年的一个周期,要求计算1900年1月1日至 ...

  9. Thunder团队第三周 - Scrum会议5

    Scrum会议5 小组名称:Thunder 项目名称:i阅app Scrum Master:苗威 工作照片: 参会成员: 王航:http://www.cnblogs.com/wangh013/ 李传康 ...

  10. 自定义类属性设置及setter、getter方法的内部实现

    属性是可以说是面向对象语言中封装的一个体现,在自定义类中设置属性就相当于定义了一个私有变量.设置器(setter方法)以及访问器(getter方法),其中无论是变量的定义,方法的声明和实现都是系统自动 ...