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");
		}
	}
}
posted @ 2021-07-05 20:11  celerity1  阅读(75)  评论(0)    收藏  举报