「数据结构 2022/02 」学习记录
[NOI2010] 超级钢琴
给定一个长度为 \(n\) 的序列,你需要选出 \(k\) 个不同的区间,每个区间的长度在 \([L, R]\) 之间。你需要使得选出的所有区间和最大。
其中 \(n,k \le 5 \times 10^5,-10^3 \le a_i \le 10^3\) 。
做一个前缀和,于是变成了求 \(k\) 个不同的二元组 \((x,y)\)。
因为两两不同,所以对于一个 \(x\) 我们只要找满足条件并且 \(y>x\) 的 \(y\) 。显然这是在 \(x\) 右边的连续一段的区间。
对于所有 \(x\) ,把满足条件 \(y\) 中最大贡献的 \(y\) 这个二元组塞进一个堆里,每次取出最大贡献,然后去掉 \(y\) ,原区间分成两区间,再把这两区间的最大贡献的 \(y\) 和 \(x\) 组成二元组塞进堆里。 用 ST 表维护一下区间最大值即可。
复杂度 \(\mathcal{O}((n+k) \log n)\)。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n,m,L,R,a[N],s[N],lg[N],st[N][22];
ll ans;
struct node{
int x,l,r,id;
friend bool operator < (const node &u,const node &v){
return (s[u.x]-s[u.id-1])<(s[v.x]-s[v.id-1]);
}
};
priority_queue<node> q;
int qmx(int l,int r)
{ int k=lg[r-l+1];
if(s[st[l][k]]>s[st[r-(1<<k)+1][k]])return st[l][k];
return st[r-(1<<k)+1][k];
}
int main()
{ scanf("%d%d%d%d",&n,&m,&L,&R);
lg[0]=-1;
for(int i=1;i<=n;i++)
{ scanf("%d",&a[i]);
s[i]=s[i-1]+a[i],lg[i]=lg[i>>1]+1;
st[i][0]=i;
}
for(int j=1;j<22;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
{ if(s[st[i][j-1]]>s[st[i+(1<<(j-1))][j-1]])st[i][j]=st[i][j-1];
else st[i][j]=st[i+(1<<(j-1))][j-1];
}
for(int i=1;i<=n;i++)
{ int l=i+L-1,r=min(n,i+R-1);
if(l>n)continue;
q.push((node){qmx(l,r),l,r,i});
// printf("%d %d %d %d\n",qmx(l,r),l,r,i);
}
for(int k=1;k<=m;k++)
{ node u=q.top();
q.pop();
// printf("k:%d:%d %d %d %d\n",k,u.x,u.l,u.r,u.id);
ans+=1ll*s[u.x]-s[u.id-1];
if(u.x>u.l)q.push((node){qmx(u.l,u.x-1),u.l,u.x-1,u.id});
if(u.r>u.x)q.push((node){qmx(u.x+1,u.r),u.x+1,u.r,u.id});
}
printf("%lld\n",ans);
return 0;
}
[九省联考 2018] IIIDX
给定 \(k\) 和一个长度为 \(n\) 的序列,你需要将其重新排序,满足 \(a_i \ge a_{\left\lfloor \frac{i}{k} \right\rfloor}\)。输出字典序最大的解。
其中 \(n \le 5 \times 10^5\)。
题目可转化为,给一棵树,对点附一个点权 \(a_i\) ,满足每个点的点权都小于子树内的点权。然后让这个树的字典树最大。
首先有一个贪心,对 \(a_i\) 排序后从大到小,按子树编号从小到大扔进去。递归处理。
但这个只对 \(a_i\) 互不相同成立,因为如果有相同的,那么可能出现把子树外的点挪到里面,仍然满足条件的情况。
不妨设子树的大小为 \(sz_i\),第 \(i\) 个值出现的次数是 \(cnt_i\) ,设 \(f_x\) 表示大于等于 \(x\) 的还没取的个数。
如果要给第 \(i\) 个点放 \(x\) ,当且仅当 \(f_x \ge sz_i\) 。同时这也意味着对于小于 \(x\) 的 \(f\) 全部减去 \(sz_i-1\) 。
这样处理后实际上的 \(f_x\) 是 \(\min\limits_{i=1}^x\{ f_i \}\) 。
考虑从编号小到大分配点权,每次分配最大即可,所以每次找到最大的满足那个条件的即可,然后按照上面的修改修改 \(f\) 。可以用线段树和二分维护。
复杂度 \(\mathcal{O}(n \log n)\) 。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e5+5;
const int inf=1e9;
int n,a[N],fa[N],sz[N],cnt[N],ans[N],t[N<<2],lz[N<<2];
double k;
bool cmp(int u,int v){return u>v;}
void pushdown(int k)
{ int x=lz[k];lz[k]=0;
t[k<<1]+=x;lz[k<<1]+=x;
t[k<<1|1]+=x;lz[k<<1|1]+=x;
}
void build(int k,int l,int r)
{ if(l==r){t[k]=l;return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
t[k]=min(t[k<<1],t[k<<1|1]);
}
void modify(int k,int l,int r,int x,int y,int z)
{ if(x<=l&&r<=y)
{ t[k]+=z,lz[k]+=z;
return;
}
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid)modify(k<<1,l,mid,x,y,z);
if(y>mid)modify(k<<1|1,mid+1,r,x,y,z);
t[k]=min(t[k<<1],t[k<<1|1]);
}
int query(int k,int l,int r,int x)
{ if(l==r)return ((t[k]>=x)?(l):(l+1));
pushdown(k);
int mid=(l+r)>>1;
if(x<=t[k<<1|1])return query(k<<1,l,mid,x);
return query(k<<1|1,mid+1,r,x);
}
int main()
{ scanf("%d%lf",&n,&k);
for(int i=1;i<=n;i++)
{ scanf("%d",&a[i]);
fa[i]=(int)floor(i/k),sz[i]=1;
}
sort(a+1,a+1+n,cmp);
for(int i=n-1;i>=1;i--)
if(a[i]==a[i+1])cnt[i]=cnt[i+1]+1;
for(int i=n;i>=1;i--)sz[fa[i]]+=sz[i];
build(1,1,n);
for(int i=1;i<=n;i++)
{ if(fa[i]&&fa[i]!=fa[i-1])modify(1,1,n,ans[fa[i]],n,sz[fa[i]]-1);
int x=query(1,1,n,sz[i]);
x+=cnt[x],cnt[x]++;x-=cnt[x]-1;
ans[i]=x;
modify(1,1,n,x,n,-sz[i]);
}
for(int i=1;i<=n;i++)
printf("%d ",a[ans[i]]);
printf("\n");
return 0;
}
CF1413F Roads and Ramen
给定一棵 \(n\) 个点的树,边权为 \(0/1\) 。
有 \(m\) 次操作,每次操作翻转一条边的边权,你需要在每次操作后回答最长的经过偶数个 \(1\) 的路径。
首先有一个性质,最长路径一定是以直径的一端点为起点。
证明的话,假设目前的答案和直径,有交点/没交点,奇/偶,发现从直径一段起点一定会更优。
所以分两种情况然后取最优就可以了。
这个时候变成了根到一个点的路径,所以我们可以用线段树维护 DFS 序,对区间 \([l,r]\) 维护 \(mx_0,mx_1\) 表示最长偶数个 \(0\) 或 \(1\) 。修改的时候修改这条边影响的子树的区间即可。
复杂度 \(\mathcal{O}(n \log n)\) 。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int N=5e5+5;
struct edge{
int v,w,nx;
}e[N<<1];
int n,m,ne,rt1,rt2,f[N],deep1[N],deep2[N];
struct tree{
int rt,tot,rev[N],L[N],R[N],dw[N],deep[N],wp[N];
void dfs(int u,int ffa)
{ L[u]=++tot;rev[tot]=u;
deep[u]=deep[ffa]+1;
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(v==ffa)continue;
dw[(i+1)>>1]=v,wp[v]=wp[u]^e[i].w;
dfs(v,u);
}
R[u]=tot;
}
int mx[N<<2][2],lz[N<<2];
void pushup(int k)
{ mx[k][0]=max(mx[k<<1][0],mx[k<<1|1][0]);
mx[k][1]=max(mx[k<<1][1],mx[k<<1|1][1]);
}
void pushdown(int k)
{ if(!lz[k])return;
swap(mx[k<<1][0],mx[k<<1][1]),swap(mx[k<<1|1][0],mx[k<<1|1][1]);
lz[k<<1]^=1;lz[k<<1|1]^=1;
lz[k]=0;
}
void build(int k,int l,int r)
{ if(l==r)
{ mx[k][wp[rev[l]]]=deep[rev[l]],lz[k]=0;
return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
pushup(k);
}
void modify(int k,int l,int r,int x,int y)
{ if(x<=l&&r<=y)
{ swap(mx[k][0],mx[k][1]),lz[k]^=1;
return;
}
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid)modify(k<<1,l,mid,x,y);
if(y>mid)modify(k<<1|1,mid+1,r,x,y);
pushup(k);
}
void init()
{ tot=0;dfs(rt,0);
build(1,1,n);
}
}t[2];
void read(int u,int v,int w)
{ e[++ne].v=v;
e[ne].nx=f[u];
e[ne].w=w;
f[u]=ne;
}
void dfs1(int u,int ffa)
{ deep1[u]=deep1[ffa]+1;
if(deep1[rt1]<deep1[u])rt1=u;
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(v==ffa)continue;
dfs1(v,u);
}
}
void dfs2(int u,int ffa)
{ deep2[u]=deep2[ffa]+1;
if(deep2[rt2]<deep2[u])rt2=u;
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(v==ffa)continue;
dfs2(v,u);
}
}
int main()
{ scanf("%d",&n);
for(int i=1,u,v,w;i<n;i++)
{ scanf("%d%d%d",&u,&v,&w);
read(u,v,w),read(v,u,w);
}
dfs1(1,0);dfs2(rt1,0);
t[0].rt=rt1,t[1].rt=rt2;
t[0].init();t[1].init();
scanf("%d",&m);
while(m--)
{ int x;
scanf("%d",&x);
t[0].modify(1,1,n,t[0].L[t[0].dw[x]],t[0].R[t[0].dw[x]]);
t[1].modify(1,1,n,t[1].L[t[1].dw[x]],t[1].R[t[1].dw[x]]);
printf("%d\n",max(t[0].mx[1][0],t[1].mx[1][0])-1);
}
return 0;
}
CF1320D Reachable Strings
给定一个
01
串。每次询问两个等长的子串,询问是否可以从一个经过数次变换变成另一个。变换操作的定义是每次选定一个包含 \(110\) 或者 \(011\) 的子段,让 \(110\to 011\) 或者 \(011\to110\)。
首先可以根据变化发现,\(0\) 可以两个两个跳,意味着可走同奇偶的位置。
而 \(00\) 这种是没法互相到达的,说明不能跳过不同奇偶的 \(0\) 。
于是两个字串,如果他们 \(0\) 个数相同,且每个 \(0\) 奇偶也相同。
所以可以哈希。
[国家集训队]飞飞侠
给定一个 \(n \times m\) 的网格,你可以从 \((i, j)\) 花费 \(a_{i,j}\) 到达所有曼哈顿距离不超过 \(B_{i,j}\) 的点。
求两点之间的最短路。
其中 \(n,m \le 150\) 。
可以线段树建图优化,但是有更优的。
考虑拆点,设 \(f(i,j,k)\) 表示到 \((i,j)\) 还能走 \(k\) 步。然后把 \((i,j,k)\) 和 \((i-1,j,k-1),(i+1,j,k-1),(i,j-1,k-1),(i,j+1,k-1),(i,j,k-1)\) 连边。
对于边,有 \((i,j,0)\) 和 \((i,j,B_{i,j})\) 连一条长 \(A_{i,j}\) 的边。表示起飞的所消耗,而 \(B_{i,j}\) 可以选择在距离内选任意点。
CF1303G Sum of Prefix Sums
对于一个序列 \(a_1,a_2,...,a_n\),定义它的 “前缀和的和” 为 \(a_1 + (a_1 + a_2) +...+ (a_1 + a_2 + ...+ a_n)\)。
给定一棵 n 个点的树,你需要求出 “前缀和的和” 最长的一条路径。
其中 \(n \le 1.5 \times 10^5\) 。
首先点分治,找到对于一个点的所有路径答案。而显然从叶子出发最优,于是只要存每个叶子对应该点路径的答案即可。
考虑路径怎么合并,设路径 \(x_1,x_2,...,x_k\) 。分成 \(x_1 \sim x_m,x_{m+1} \sim x_k\) 。
设 \(l=m,v_1=\sum\limits_{i=1}^m i\cdot a_{x_i},s_2=\sum\limits_{i=m+1}^k a_{x_i},v_2=\sum\limits_{i=m+1}^k (i-m)a_{x_i}\) 。
合并的答案为 \(v_1+l s_2+v_2\) 。注意在树上做的的时候注意每个部分的根只能算一次。
考虑对这个答案形式进行转化,\((s_2,v_2)\) 表示插入一次函数 \(s_2x+v_x\) 。那么 \((v_1,l)\) 表示查询,询问 \(x=v_1\) 上和他相交的最大 \(y\) 值和 \(l\) 的和。
可以李超线段树维护。复杂度 \(\mathcal{O}(n \log^2 n)\) 。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=150000+5;
struct edge{
int v,nx;
}e[N<<1];
struct node{
ll u,v1,v2,s,l,f;
}st[N];
struct tree{
ll ta[N<<2],tb[N<<2];
void build(int k,int l,int r)
{ ta[k]=tb[k]=0;
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
}
void modify(int k,int l,int r,ll x,ll y)
{ if(ta[k]*l+tb[k]<=x*l+y&&ta[k]*r+tb[k]<=x*r+y)
{ ta[k]=x,tb[k]=y;
return;
}
if(ta[k]*l+tb[k]>=x*l+y&&ta[k]*r+tb[k]>=x*r+y)return;
int mid=(l+r)>>1;
if(ta[k]*mid+tb[k]<x*mid+y)swap(ta[k],x),swap(tb[k],y);
if(x<ta[k])modify(k<<1,l,mid,x,y);
else modify(k<<1|1,mid+1,r,x,y);
}
ll query(int k,int l,int r,ll x)
{ ll res=ta[k]*x+tb[k];
if(l==r)return res;
int mid=(l+r)>>1;
if(x<=mid)res=max(res,query(k<<1,l,mid,x));
else res=max(res,query(k<<1|1,mid+1,r,x));
return res;
}
}T;
int read()
{ int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}
return s*f;
}
int n,ne,sumn,top,rt,mxd,f[N],sz[N],son[N];
bool vis[N];ll ans,a[N],deep[N];
void read(int u,int v)
{ e[++ne].v=v;
e[ne].nx=f[u];
f[u]=ne;
}
void getroot(int u,int ffa)
{ sz[u]=1,son[u]=0;
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(v==ffa||vis[v])continue;
getroot(v,u);
sz[u]+=sz[v],son[u]=max(son[u],sz[v]);
}
son[u]=max(son[u],sumn-sz[u]);
if(son[u]<son[rt])rt=u;
}
void dfs(int u,ll v1,ll v2,ll s,ll fff,int ffa)
{ if(u!=rt&&!fff)fff=u;
deep[u]=deep[ffa]+1,mxd=max(mxd,(int)deep[u]);
bool is=0;
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(v==ffa||vis[v])continue;
is=1;
dfs(v,v1+s+a[v],v2+a[v]*deep[u],s+a[v],fff,u);
}
if(!is)st[++top]=(node){u,v1,v2,s-a[rt],deep[u],fff};
}
void solve(int u)
{ vis[u]=1,mxd=0,top=0;
dfs(u,a[u],0,a[u],0,0);
st[++top]=(node){u,a[u],0,0,1,0};
T.build(1,1,mxd);
st[0].f=st[top+1].f=-1;
for(int i=1,j;i<=top;)
{ for(j=i;st[j].f==st[i].f;j++)ans=max(ans,T.query(1,1,mxd,st[j].l)+st[j].v1);
for(j=i;st[j].f==st[i].f;j++)T.modify(1,1,mxd,st[j].s,st[j].v2);
i=j;
}
T.build(1,1,mxd);
for(int i=top,j;i>=1;)
{ for(j=i;st[j].f==st[i].f;j--)ans=max(ans,T.query(1,1,mxd,st[j].l)+st[j].v1);
for(j=i;st[j].f==st[i].f;j--)T.modify(1,1,mxd,st[j].s,st[j].v2);
i=j;
}
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(vis[v])continue;
sumn=sz[v];rt=0;
getroot(v,u);
solve(rt);
}
}
int main()
{ n=read();
for(int i=1,u,v;i<n;i++)
{ u=read(),v=read();
read(u,v),read(v,u);
}
for(int i=1;i<=n;i++)a[i]=read();
son[0]=n;rt=0;sumn=n;
getroot(1,0);
solve(rt);
printf("%lld\n",ans);
return 0;
}
[POI2014]HOT-Hotels 加强版
给定一棵 \(n\) 个点的树,求有多少个三元组 \((u, v, w)\),使得它们两两之间的距离相等。
其中 \(n \le 10^5\) 。
首先答案的形式是,有两个点到达他们的 LCA 的距离是一样的。且不存在三点公链的情况。
于是设 \(f(u,i)\) 表示在 \(u\) 子树内,距离 \(u\) 为 \(i\) 的点有多少个。
设 \(g(u,i)\) 表示二元组和他们 LCA 距离相等,还差 \(i\) 距离和他们构成三元组的个数。
于是有 \(ans \leftarrow g(u,j) \times f(v,j-1) + g(v,j) \times f(u,j-1)\) 。
可以长剖优化。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const ll N=1e6+5;
const ll inf=1e9;
struct edge{
ll v,nx;
}e[N<<1];
ll n,ne,f[N],cnt[N],son[N];
ll ans,buf[N],*p=buf,*dp[N],*g[N];
void read(ll u,ll v)
{ e[++ne].v=v;
e[ne].nx=f[u];
f[u]=ne;
}
void dfs(ll u,ll ffa)
{ cnt[u]=1;
for(ll i=f[u];i;i=e[i].nx)
{ ll v=e[i].v;
if(v==ffa)continue;
dfs(v,u);
cnt[u]=max(cnt[u],cnt[v]+1);
if(cnt[v]>cnt[son[u]])son[u]=v;
}
}
void solve(ll u,ll ffa)
{ if(son[u])
{ dp[son[u]]=dp[u]+1,g[son[u]]=g[u]-1;
solve(son[u],u);
}
dp[u][0]=1,ans+=g[u][0];
for(ll i=f[u];i;i=e[i].nx)
{ ll v=e[i].v;
if(v==ffa||v==son[u])continue;
dp[v]=p,p+=cnt[v]<<1;g[v]=p,p+=cnt[v]<<1;
solve(v,u);
for(ll j=1;j<=cnt[v];j++)ans+=g[u][j]*dp[v][j-1]+g[v][j]*dp[u][j-1];
for(ll j=1;j<=cnt[v];j++)g[u][j]+=g[v][j+1];
for(ll j=1;j<=cnt[v];j++)g[u][j]+=dp[u][j]*dp[v][j-1];
for(ll j=1;j<=cnt[v];j++)dp[u][j]+=dp[v][j-1];
}
}
int main()
{ scanf("%lld",&n);
for(ll i=1,u,v;i<n;i++)
{ scanf("%lld%lld",&u,&v);
read(u,v),read(v,u);
}
dfs(1,0);
dp[1]=p,p+=cnt[1]<<1;g[1]=p,p+=cnt[1]<<1;
solve(1,0);
printf("%lld\n",ans);
return 0;
}
CF809D Hitchhiking in the Baltic States
给出 \(n\) 个区间 \([l_i,r_i]\) 和 \(n\) 个未知数 \(a_1,a_2,\dots,a_n\),现在你要确定这 \(n\) 个数,使得 \(a_i \in [l_i,r_i]\),并且这个序列的最长严格上升子序列尽可能大,求这个最大值。
其中 \(1 \le n \le 3 \times 10^5, 1 \le l_i,r_i \le 10^9\)。
设 \(f(i,j)\) 表示到第 \(i\) 个数时,长度为 \(j\) 的严格 LIS 的最后最小值。于是 \(f(0,0)=0\)。
首先 \(i\) 肯定是可是滚掉的。
考虑用平衡树优化,倒着更新。
于是变成了,删除第一个大于等于 \(r\) 的数,区间 \([l,r)\) 平移至 \([l+1,r+1)\),插入第一个大于等于 \(l\) 的数。
然后发现第一类只更新一个数,就是第一个大于等于 \(l\) 的数。于是可以变成删除第一个大于等于 \(l_i\) 再插入 \(l\) 。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=6e5+5;
const int inf=1e9;
int n,ans;
struct Splay{
int s[2],p,val,tag;
void init(int v,int f){
val=v,p=f;
}
};
struct tree{
Splay t[N];
int rt,tot,top,st[N];
void build()
{ rt=1,tot=2;
t[1].init(-inf,0),t[2].init(inf,rt);
t[rt].s[1]=2;
}
void pushdown(int k)
{ if(!t[k].tag)return;
int lc=t[k].s[0],rc=t[k].s[1];
if(lc)t[lc].val+=t[k].tag,t[lc].tag+=t[k].tag;
if(rc)t[rc].val+=t[k].tag,t[rc].tag+=t[k].tag;
t[k].tag=0;
}
void rotate(int x)
{ int y=t[x].p,z=t[y].p;
int k=(t[y].s[1]==x);
t[z].s[t[z].s[1]==y]=x;t[x].p=z;
t[y].s[k]=t[x].s[k^1];t[t[x].s[k^1]].p=y;
t[x].s[k^1]=y;t[y].p=x;
}
void splay(int x,int k)
{ int tmp=x;top=0;
st[++top]=tmp;
while(tmp)st[++top]=tmp=t[tmp].p;
while(top)pushdown(st[top]),top--;
while(t[x].p!=k)
{ int y=t[x].p,z=t[y].p;
if(z!=k)
{ if((t[y].s[1]==x)^(t[z].s[1]==y))rotate(x);
else rotate(y);
}
rotate(x);
}
if(!k)rt=x;
}
void insert(int v)
{ int u=rt,p=0;ans++;
while(u)p=u,u=t[u].s[v>t[u].val];
u=++tot;
if(p)t[p].s[v>t[p].val]=u;
t[u].init(v,p);
splay(u,0);
}
void del(int p)
{ splay(p,0);
int lc=t[p].s[0],rc=t[p].s[1];
while(t[lc].s[1])lc=t[lc].s[1];
while(t[rc].s[0])rc=t[rc].s[0];
splay(lc,0),splay(rc,lc);
t[rc].s[0]=0;ans--;
}
int getpr(int v)
{ int p=rt,res=0;
while(p)
{ pushdown(p);
if(t[p].val<v)res=p,p=t[p].s[1];
else p=t[p].s[0];
}
splay(rt,0);
return res;
}
int getnx(int v)
{ int p=rt,res=0;
while(p)
{ pushdown(p);
if(t[p].val>v)res=p,p=t[p].s[0];
else p=t[p].s[1];
}
splay(rt,0);
return res;
}
void move(int l,int r)
{ int u=getnx(l-1),v=getpr(r);
if(t[u].val>t[v].val)return;
if(u==v)t[u].val++;
else if(t[u].val<t[v].val)
{ splay(u,0),splay(v,u);
t[u].val++,t[v].val++;t[t[v].s[0]].val++;
if(t[v].s[0])t[t[v].s[0]].tag++;
}
}
}T;
int main()
{ //freopen("1.in","r",stdin);
scanf("%d",&n);
T.build();
T.insert(0);ans=0;
for(int i=1,l,r;i<=n;i++)
{ scanf("%d%d",&l,&r);
int pos=T.getnx(r-1);
if(pos&&pos!=1&&pos!=2)T.del(pos);
T.move(l,r);
T.insert(l);
}
printf("%d\n",ans);
return 0;
}
[SCOI2014]方伯伯的玉米田
方伯伯在自己的农田边散步,他突然发现田里的一排玉米非常的不美。
这排玉米一共有 \(n\) 株,它们的高度参差不齐。
方伯伯认为单调不下降序列很美,所以他决定先把一些玉米拔高,再把破坏美感的玉米拔除掉,使得剩下的玉米的高度构成一个单调不下降序列。
方伯伯可以选择一个区间,把这个区间的玉米全部拔高 \(1\) 单位高度,他可以进行最多 \(k\) 次这样的操作。拔玉米则可以随意选择一个集合的玉米拔掉。
问能最多剩多少株玉米,来构成一排美丽的玉米。
其中 \(n \le 10^4,k \le 500,a_i \le 5000\)。
首先显然选一段后缀执行操作最优。
于是设 \(f(i,j)\) 表示前 \(i\) 个数,于是有 \(f(i,j)=\max\limits_{x<i,y<j,a_i+j \ge a_x+y}\{ f(x,y) \}+1\) 。
用二维树状数组维护前缀即可。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int N=1e4+5,M=500+5;
int n,m,a[N],t[M][N],f[N][M];
int lowbit(int x){return x&-x;}
void modify(int x,int y,int z)
{ for(int i=x;i<=m+1;i+=lowbit(i))
for(int j=y;j<=5505;j+=lowbit(j))
t[i][j]=max(t[i][j],z);
}
int query(int x,int y)
{ int res=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
res=max(res,t[i][j]);
return res;
}
int main()
{ scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
{ f[i][j]=query(j+1,a[i]+j)+1;
modify(j+1,a[i]+j,f[i][j]);
}
printf("%d\n",query(m+1,5505));
return 0;
}
CF983D Arkady and Rectangles
给定 \(n\) 个矩形,一个一个覆盖,问最后能看到几个矩形。
其中 \(n \le 10^5\)。
首先扫描线,维护 \(y\) ,如果 \(x\) 能被看到,当且仅当这条线段树上的路径最大值是 \(x\) 。
但是还要考虑删除的情况,所以每个点开一个 set
存储完全覆盖这个点的颜色,能看到未被看到过的最大和最小颜色。最大的颜色还没统计进去的最大,最小表示不超过最小的就被覆盖。
所以每次更新的时候就直接更新对应区间。pushup
的时候如果最大的被统计过就更新最小。
这题还没写(? 好,补了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
using namespace std;
const int N=2e5+5;
struct node{
int x,y,xx,yy;
}a[N];
struct qry{
int l,r,x;
};
int n,ans=1,mx,my,bx[N],by[N];
vector<qry> vc1[N],vc2[N];
bool vis[N];
struct tree{
int mn[N<<2],mx[N<<2],vl[N<<2],ps[N<<2];
set<int> st[N<<2];
void build(int k,int l,int r)
{ st[k].insert(0);
mn[k]=mx[k]=vl[k]=ps[k]=0;
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
}
void pushup(int k)
{ mn[k]=max(mx[k],min(mn[k<<1],mn[k<<1|1]));
ps[k]=max(vl[k],max(ps[k<<1],ps[k<<1|1]));
if(ps[k]<mn[k])ps[k]=0;
}
void modify(int k,int l,int r,int x)
{ x=max(x,mx[k]);
if(!ps[k]||ps[k]<x)return;
while(vl[k]&&vl[k]>=max(x,mn[k]))
{ vis[vl[k]]=1;
while(vis[vl[k]])vl[k]=(*(--st[k].find(vl[k])));
}
if(l==r){mn[k]=mx[k],ps[k]=(vl[k]<mn[k])?(0):(vl[k]);return;}
int mid=(l+r)>>1;
modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
pushup(k);
}
void modify1(int k,int l,int r,int x,int y,int z,int w)
{ w=max(w,mx[k]);
if(x<=l&&r<=y)
{ if(z>=max(w,mn[k]))vis[z]=1;
else vl[k]=max(vl[k],z);
mx[k]=max(mx[k],z),st[k].insert(z);
if(l==r)mn[k]=mx[k],ps[k]=(vl[k]<mn[k])?(0):(vl[k]);
else pushup(k);
return;
}
int mid=(l+r)>>1;
if(x<=mid)modify1(k<<1,l,mid,x,y,z,w);
if(y>mid)modify1(k<<1|1,mid+1,r,x,y,z,w);
pushup(k);
}
void modify2(int k,int l,int r,int x,int y,int z,int w)
{ w=max(w,mx[k]);
if(x<=l&&r<=y)
{ while(vl[k]==z||vis[vl[k]])vl[k]=(*(--st[k].find(vl[k])));
st[k].erase(z),mx[k]=(*(--st[k].end()));
if(l==r)mn[k]=mx[k],ps[k]=(vl[k]<mn[k])?(0):(vl[k]);
else pushup(k);
return;
}
int mid=(l+r)>>1;
if(x<=mid)modify2(k<<1,l,mid,x,y,z,w);
if(y>mid)modify2(k<<1|1,mid+1,r,x,y,z,w);
pushup(k);
}
}T;
int main()
{ scanf("%d",&n);
for(int i=1;i<=n;i++)
{ scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].xx,&a[i].yy);
bx[++mx]=a[i].x,bx[++mx]=a[i].xx;
by[++my]=a[i].y,by[++my]=a[i].yy;
a[i].x++,a[i].y++;
}
sort(bx+1,bx+1+mx),sort(by+1,by+1+my);
mx=unique(bx+1,bx+1+mx)-bx-1,my=unique(by+1,by+1+my)-by-1;
for(int i=n;i>=1;i--)
{ a[i].x=lower_bound(bx+1,bx+1+mx,a[i].x)-bx;
a[i].xx=lower_bound(bx+1,bx+1+mx,a[i].xx)-bx;
a[i].y=lower_bound(by+1,by+1+my,a[i].y)-by;
a[i].yy=lower_bound(by+1,by+1+my,a[i].yy)-by;
vc1[a[i].x].push_back((qry){a[i].y,a[i].yy,i});
vc2[a[i].xx].push_back((qry){a[i].y,a[i].yy,i});
}
T.build(1,1,my);
for(int i=1;i<=mx;i++)
{ for(int j=0;j<(int)vc1[i].size();j++)
{ int l=vc1[i][j].l,r=vc1[i][j].r,x=vc1[i][j].x;
T.modify1(1,1,my,l,r,x,0);
}
T.modify(1,1,my,0);
for(int j=0;j<(int)vc2[i].size();j++)
{ int l=vc2[i][j].l,r=vc2[i][j].r,x=vc2[i][j].x;
T.modify2(1,1,my,l,r,x,0);
}
}
for(int i=1;i<=n;i++)
if(vis[i])ans++;
printf("%d\n",ans);//
return 0;
}
鸽了亿些 LCT 和 KD-Tree 的题。