ds练习记录
CF1540D
(自己做出)
把\(b_i=i-b_i-1\),\(b_i\)变成前面小于\(i\)的数的个数。
如果得知\(b_i\),求出原序列的方法:顺序扫描整个数组,在序列的第\(b_i\)个数前插入\(i\)。
第\(i\)个位置的权值是序列的值为\(i\)的数的排名。
发现我们只需要关心\(p_i\)的值,所以过程可以改为:
首先有一个数\(x\),往\(i+1\to n\)扫描后,如果当前数小于\(x\),则把\(x+1\)。
考虑分块,考虑从一个块跳出后,\(x\)会变成的值\(c_x\)。
容易发现在一个块,不同的\(c_x-x\)只有\(O(\sqrt{n})\)类。
如果用\(d_x\)表示\(x\)位置\(x\)是否被加,则显然当\(x\)增加时,\(d_x\)的每个位置的值只会增加,不会减少。
当没有\(d\)变化时,\(c_x-x\)在\(x++\)时显然是不变的。
\(c_x-x\)相同的\(x\)肯定是若干连续区间,在求得这些区间后,二分一下就能知道\(c_x\)了。
考虑修改操作。当\(x++\)时,考虑维护接下来每个位置还要加多少\(d\)会变成\(1\)。如果\(d\)已经变成\(1\)了就赋值成\(\inf\)。
如果存在一个位置是\(0\),则把\(0\)位置\(y\)找出来后,\(y\)后面的值全部-1。
这样子我们就知道当前连续段的值了。
不断的找到\(0\)位置直到不存在,这时把所有数减去整个序列的最小值后新开一个连续段继续寻找。
所以这道题真的有3200难度吗...
lg5068
(自己做出)
简单题
做法1:我想到的第一个做法
容易发现,如果随机出\(i\),那么答案就是最大的\(j\),使得对于所有\(k\in [1,j]\),存在一个数在区间\([ki,(k+1)i)\)。
容易发现,形如\([ki,(k+1)i)\)的区间个数是\(O(n\log_2n)\)的。
设值\(i\)的答案是\(ans_i\),容易发现\(ans\)的每个位置只可能增加,不可能减少,变化次数最多是\(n\log_2n\)。
如果我们维护每个时间的\(ans\),那么就能在\(O(\log_2n)\)的时间内回答每个询问。
求出\(ans\)是否变化可以用线段树维护。
在线段树上每个节点上挂一个链表,每次对于所有\(i\),把\([ians_i,i(ans_i+1))\)挂在线段树上。
每次查询\(i\)对应叶子节点到根的所有点,然后把这些点的\(ans+1\)并且从线段树上对应节点的链表上删除,然后插入\(ans+1\)对应的区间。
做法2:维护\(ans\)事实上可以维护区间\([ki,(k+1)i)\)第一次出现点的时间\(t_{k,i}\),显然可以st表维护。
然后把\(t_{k,i}\)和\(t_{k-1,i}\)取最大值。
对于每对\((k,i)\)在时间\(t_{k,i}\)时把\(i\)位置+1,用bit查询即可。
时间复杂度均为\(O(n\log_2^2n+m\log_2n)\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 1000010
int n,m,mn[N*4],a[N],bt[N],ct,ok[N],l[N],r[N];
vector<int>v[N],md[N];
void ad(int x,int y){
for(;x<=n;x+=x&-x)
bt[x]+=y;
}
int qu(int x){
int ans=0;
for(;x;x-=x&-x)
ans+=bt[x];
return ans;
}
void bd(int o,int l,int r){
if(l==r){
mn[o]=a[l];
return;
}
int md=(l+r)/2;
bd(o*2,l,md);
bd(o*2+1,md+1,r);
mn[o]=min(mn[o*2],mn[o*2+1]);
}
int qu(int o,int l,int r,int x,int y){
if(r<x||y<l)
return m+1;
if(x<=l&&r<=y)
return mn[o];
int md=(l+r)/2;
return min(qu(o*2,l,md,x,y),qu(o*2+1,md+1,r,x,y));
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
a[i]=m+1;
for(int i=1;i<=m;i++){
int op;
scanf("%lld%lld",&op,&l[i]);
if(op==1){
a[l[i]]=min(a[l[i]],i);
}
else{
scanf("%lld",&r[i]);
ok[i]=1;
}
}
bd(1,1,n);
for(int i=1;i<=n;i++){
v[i].push_back(0);
for(int j=1;j<=n;j+=i){
int l=j,r=j+i-1;
int va=qu(1,1,n,l,r);
v[i].push_back(va);
}
for(int j=1;j<v[i].size();j++)
v[i][j]=max(v[i][j],v[i][j-1]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<v[i].size();j++)
md[v[i][j]].push_back(i);
for(int i=1;i<=m;i++){
for(int j=0;j<md[i].size();j++)
ad(md[i][j],1);
if(ok[i])
printf("%lld\n",qu(r[i])-qu(l[i]-1)+r[i]-l[i]+1);
}
}
时代的眼泪
(自己做出)
自己的做法有点奇怪,参考了区间逆序对的做法。
设一个询问的矩形为\((p1,p2),(p3,p4)\)
定义\(g(i,j)\)为顶部为\(i\),右部为\(j\),左/底部贴着平面的最左/最下边的答案
由于问题比区间逆序对强,所以考虑分块。
在分块时,序列被分为了\(L,M,R\)三个部分,\(L,R\)是左,右散块,\(M\)是中间整块。
每个点的贡献是以它为左下角的矩形和询问矩形的交。
\(L,R\)的贡献可以枚举\(L\)的每个节点,这样子贡献就是\(R\)平面上的一个子平面。
把每个块内的\(a\)离散化后显然可以二维前缀和。
为了避免二分,每个块\(i\)需要维护\(f_{i,j}\)表示第\(i\)个块内小于\(j\)的最大数的排名。
\(L,M\)和\(M,R\)的贡献是对称的,所以只需要处理\(L,M\)的贡献。
二维前缀和后,转化成了计算顶部任意,右部贴着某个块,左/底部贴着平面的最左/最下边的答案。
设\(s_{i,j}\)表示右部为第\(i\)个块的右端点,顶部为\(j\)的答案,显然可以把前\(i\)个块内的点排序后求。
\(M\)内部的每个节点的贡献可以被拆成在块内,块外的。
块内的贡献事实上就是求块内的纵坐标在查询下界,上界的点的逆序对个数。
一个块内最多只有\(\sqrt{n}*\sqrt{n}=n\)类贡献,可以通过前面的二维前缀和,枚举左端点,右端点递增处理。
找下界,上界在这个块内对应的贡献类型可以用前面提到的\(f\)。
设当前最右的整块的右部横坐标为\(p\)
设当前考虑的节点为\(i\),\(i\)所在块的右部横坐标为\(q\),扫描\(M\)的整块做处理。则我们要求矩形\([(q,a_i+1),(p,p4)]\)内的点数。
二维差分后变成\(g(p,p4)-g(p,a_i)-g(q,p4)+g(q,a_i)\)。
\(g(p,p4)\)和\(g(q,p4)\)贡献和\(i\)无关,显然可以用前面提到的二维前缀和求出\(i\)的个数。
由于\(p4\)贴在块的右端点上,所以可以用前面提到的\(s\)求。
\(g(i,a_i)\)可以在块内维护一个前缀和\(t\),\(t_i\)表示\(g(i,a_i)\),找标号可以用以前的数组\(f\)。
\(g(q,a_i)\)可以把这个块挂在\(q\)的块上。
这事实上是一些点有权值,要求一个左部贴着某个块的左部,右部贴着某个块的右部平面的点的权值和。
由于点权贴在块的右端点上,所以可以用前面提到的\(s\)求。
可以给每个块再维护一个前缀和,然后扫描每个当前询问包含的块,用\(f\)求出标号后用前缀和求出答案。
时间复杂度\(O((n+m)\sqrt{n})\),感觉常数很大。
CF1088F
(瞄了一眼题解)
考虑把权值最小的点作为根,这样子会让孩子的值大于父亲的。
如果一个点的父亲的值大于孩子,那么这个孩子肯定也有一个孩子的值是小于这个点的。
反复向孩子跳跃可以导出矛盾。
原问题等于最小生成树,\((u,v)\)边的代价是\(a_u+a_v+\log_2(dis(u,v))\)
然而这样子并不能过。
发现我们只会选择孩子到父亲的边。
如果我们选择了\((u,v),lca(u,v)=l,u\neq l,v\neq l\),显然\((u,l)\)和\((v,l)\)的代价都小于\((u,v)\)的代价。
在kruskal选择\((u,v)\)之前,\((u,v)\)肯定已经连通了。
于是我们就是要给每个点\(x\)确定父亲\(y\)使得\(a_x+(a_y+\log_2(dis(x,y)))\)最小。
\(\log_2(dis(x,y))\)只有\(\log_2\)类取值,于是枚举\(\log_2(dis(x,y))\)后倍增维护链上最小的\(y\)就完事了。
所以这道题真的有2800难度吗...
lg5010
(自己做出)
简单题
发现数据范围很奇怪。
由于没有强制在线,考虑扫描线。
类似HH的项链,为每个颜色\(i\)记\(lst_i\)表示它上一次出现的位置。
维护\(k\)bit\(bt\),\(bt_{i,j}\)个位置表示长度\(i\)左端点为\(j\)答案。
对于每个\(i\),在\(i\)位置画一个高度为\(lst_i\)的柱子。
在更新\(lst\)时,设原来\(lst=p\)。
考虑如何更新bit,我们事实上可以把整个图从\(lst\)处画一条横线,忽略在这条横线下和距离超过\(k\)的柱子的值。
往左/右求出后,前缀\(\min\)值\(L,R\),把他们从小到大排序后去重,设数组为\(a\)。
设切开后底部长度为\(len\),循环\(a\)内每个元素\(i\)。
每循环一次,就把\([a_i,a_{i+1}-1]\)在\(bt_{len}\)上\(+1\),并且把左/右原长度\(-1\),然后\(len-1\)。
lg5073
(瞄了一眼题解)
由于空间限制,不能直接在线段树上建立每个节点的凸包。
考虑后序遍历整颗线段树,在遍历一个节点时,回答当前节点上的询问。
给每个询问维护一个答案节点,表示当前遍历到的节点能够回答到的。
在遍历到一个节点时,需要维护子树的答案凸包,前/后缀凸包。
这可以再遍历到一个节点时不销毁当前凸包,把儿子没有销毁的信息合并后得到当前节点的答案凸包,并且销毁儿子的凸包。
然而我们还要知道当前该询问哪些节点。
对于这个问题,我们在当前维护一个链表,表示链表内的询问可能在子树中可以回答。
每次分裂不包含当前节点的询问到左/右节点,返回时归并一下即可。
这样子就卡掉了空间的\(\log_2\)
Yakiniku Restaurants
(自己做出)
简单题。
显然,我们走到的是一个区间\([l,r]\)。代价是\(\sum_{i=1}^m(\max(B_{l...r,i}))\)
从区间左走到右距离最小。
枚举右端点,维护左端点的答案。
左端点的\(\max\)可以用单调栈维护。
容易发现,在右端点移动时,在单调栈弹出节点时,左端点的答案事实上应该进行均摊\(O(m)\)次区间加法。
用差分/线段树维护。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 5010
int n,m,ans,a[N],b[310][N],s[N],st[310][N],tp[310],va[N],dt[N];
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<n;i++){
scanf("%lld",&a[i]);
s[i+1]=s[i]+a[i];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&b[j][i]);
for(int i=1;i<=n;i++){
memset(dt,0,sizeof(dt));
for(int j=1;j<=m;j++){
while(tp[j]&&b[j][st[j][tp[j]]]<b[j][i]){
dt[st[j][tp[j]-1]+1]+=b[j][i]-b[j][st[j][tp[j]]];
dt[st[j][tp[j]]+1]-=b[j][i]-b[j][st[j][tp[j]]];
tp[j]--;
}
st[j][++tp[j]]=i;
dt[i]+=b[j][i];
}
for(int j=1;j<=i;j++)
dt[j]+=dt[j-1];
for(int j=1;j<=i;j++)
va[j]+=dt[j];
for(int j=1;j<=i;j++)
ans=max(ans,-s[i]+s[j]+va[j]);
}
printf("%lld",ans);
}
CF576E
(自己做出)
简单题。
如果有"执行后合法"这个限制,显然可以用时间轴线段树解决:
维护\(k\)个并查集,一条边的染色事实上就是一种颜色的边消失,另一种颜色的边出现。
每种边的每种颜色会对应线段树上的一个区间,插入到线段树上dfs解决。
但是现在有后效性了。
考虑按照时间顺序遍历叶子节点(就是先dfs左儿子,再dfs右儿子,线段树都是这么写的),它代表一个询问。
发现,时间轴线段树上的某一个编号是\(i\)的边对应的区间,可能性比较少:
把所有编号为\(i\)的边的询问拿出来,按照时间顺序排序为\(a_1,a_2...a_n\)
则在时刻\([1,a_1),[a_1,a_2)...[a_{n-1},a_n)\)的边的颜色都是相同的。
考虑在遍历到\(a_i\)时把区间\([a_i,a_{i+1})\)(对应的存在区间)染成对应颜色,然后插到线段树里。
在遍历到\(a_i\)时,判定把\(a_i\)插入染后的新图是否合法。
如果合法,则插入区间\((a_i,a_{i+1})\)。
时间复杂度\(O(n\log_2^2n)\)
lg6122
(自己做出)
简单题。
显然的费用流模型:s->有鼹鼠的点连接费用\(0\)流量\(1\)的边。
树上相邻两个点链接流量\(\inf\)费用\(c_i\)的边。
\(i\)向\(t\)连接流量\(p_i\)费用\(0\)的边。
费用流的重要性质:无论按照如何顺序对以后一定要增广的边进行增广,答案都是对的。
注意到树高很小,不用最短路增广。
维护反向边的流量即可知道每条边增广的最优价值。
考虑枚举路径的\(lca\)为\(l\),我们要知道\(l\)一个子树到某个节点\(y\)的最优价值。
在更新流量时,被影响的点是\(x\),\(y\)的祖先,显然可以在\(O(1)\)的时间内递推出到子树的答案。
#include<bits/stdc++.h>
using namespace std;
#define N 300010
int n,m,c[N],p[N],fl[N],po[N],d[N],ans;
int fu(int x){
if(fl[x]<0)
return -1;
return 1;
}
int fd(int x){
if(fl[x]>0)
return -1;
return 1;
}
void up(int x){
if(c[x]){
d[x]=0;
po[x]=x;
}
else{
d[x]=1e9;
po[x]=0;
}
if(x*2<=n&&d[x]>d[x*2]+fd(x*2)){
d[x]=d[x*2]+fd(x*2);
po[x]=po[x*2];
}
if(x*2+1<=n&&d[x]>d[x*2+1]+fd(x*2+1)){
d[x]=d[x*2+1]+fd(x*2+1);
po[x]=po[x*2+1];
}
}
int main(){
//freopen("c.in","r",stdin);
//freopen("c.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=n;i;i--)
up(i);
for(int i=1;i<=m;i++){
scanf("%d",&p[i]);
int ps=0,di=1e9,va=0;
for(int j=p[i];j;j/=2){
if(di>d[j]+va){
di=d[j]+va;
ps=j;
}
va+=fu(j);
}
int pp=po[ps];
ans+=di;
for(int j=p[i];j!=ps;j/=2)
fl[j]++;
for(int j=pp;j!=ps;j/=2)
fl[j]--;
c[pp]--;
for(int j=p[i];j;j/=2)
up(j);
for(int j=pp;j;j/=2)
up(j);
printf("%d ",ans);
}
}
lg7476
(瞄了一眼题解)
简单题。
考虑标记永久化,在每个节点上用一个堆维护标记。
1操作就是区间加标记,3操作可以区间查询标记,每走到一个节点把答案和当前点的标记取最大值。
子树标记最大值可以轻松维护。
2操作有点麻烦,假设最大值为\(x\)。
在线段树上,我们可以把当前点拆成\(\log_2n\)个区间,拆出的区间的祖先节点假如标记最大值\(=x\),则把祖先\(=x\)的标记弹出。
把祖先的区间和查询区间取交,把祖先的区间减去交区间后,插入这个值为\(x\)的这个区间。
在拆成线段树的区间后,我们可以在这个区间对应的点\(p\)上dfs,当\(p\)的子树没有\(x\)标记时(就是子树标记最大值不等于\(x\))就退出。
由于标记只会弹出一个,所以在可以弹出时就可以退出了。
时间复杂度是\(O(n\log_2^2n+q\log_2n)\)的,不知道如何证明。
因为漏了一个条件所以做了这么久...
#include<bits/stdc++.h>
using namespace std;
#define N 2000010
int n,m,mx[N],ans;
priority_queue<int>q[N];
void add(int o,int l,int r,int x,int y,int z){
if(r<x||y<l)
return;
if(x<=l&&r<=y){
q[o].push(z);
mx[o]=max(mx[o],z);
return;
}
int md=(l+r)/2;
add(o*2,l,md,x,y,z);
add(o*2+1,md+1,r,x,y,z);
if(q[o].size())
mx[o]=max(max(mx[o*2],mx[o*2+1]),q[o].top());
else
mx[o]=max(mx[o*2],mx[o*2+1]);
}
void d1(int o,int l,int r,int z){
if(mx[o]!=z)
return;
if(q[o].size()&&q[o].top()==z){
q[o].pop();
if(q[o].size())
mx[o]=max(max(mx[o*2],mx[o*2+1]),q[o].top());
else
mx[o]=max(mx[o*2],mx[o*2+1]);
return;
}
int md=(l+r)/2;
d1(o*2,l,md,z);
d1(o*2+1,md+1,r,z);
if(q[o].size())
mx[o]=max(max(mx[o*2],mx[o*2+1]),q[o].top());
else
mx[o]=max(mx[o*2],mx[o*2+1]);
}
void dl(int o,int l,int r,int x,int y,int z){
if(r<x||y<l)
return;
if(x<=l&&r<=y){
d1(o,l,r,z);
return;
}
else{
if(q[o].size()&&q[o].top()==z){
q[o].pop();
int il=max(l,x),ir=min(r,y);
if(l<=il-1)
add(1,1,n,l,il-1,z);
if(ir+1<=r)
add(1,1,n,ir+1,r,z);
if(q[o].size())
mx[o]=max(max(mx[o*2],mx[o*2+1]),q[o].top());
else
mx[o]=max(mx[o*2],mx[o*2+1]);
return;
}
}
int md=(l+r)/2;
dl(o*2,l,md,x,y,z);
dl(o*2+1,md+1,r,x,y,z);
if(q[o].size())
mx[o]=max(max(mx[o*2],mx[o*2+1]),q[o].top());
else
mx[o]=max(mx[o*2],mx[o*2+1]);
}
void qu(int o,int l,int r,int x,int y){
if(r<x||y<l)
return;
if(q[o].size())
ans=max(ans,q[o].top());
if(x<=l&&r<=y){
ans=max(ans,mx[o]);
return;
}
int md=(l+r)/2;
qu(o*2,l,md,x,y);
qu(o*2+1,md+1,r,x,y);
}
int main(){
for(int i=0;i<N;i++)
mx[i]=-1e9;
scanf("%d%d",&n,&m);
while(m--){
int op,l,r,x;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
scanf("%d",&x);
add(1,1,n,l,r,x);
}
else if(op==2){
ans=-1e9;
qu(1,1,n,l,r);
if(ans!=-1e9)
dl(1,1,n,l,r,ans);
}
else{
ans=-1e9;
qu(1,1,n,l,r);
if(ans!=-1e9)
printf("%d\n",ans);
else
puts("-1");
}
}
}