12月CWOI杂题

发布时间 2023-12-14 09:58:12作者: xx019

?怎么 12 月都过一半了?

C0425 【1202 A组】模拟测试

A 【1202 A组】景点游览

一个垃圾的 \(\mathcal{O}(n\sqrt{n})\) 做法。先缩点,然后拓扑,求出每个点能到达的所有点中最大的和最小的,记为 \(R_i\)\(L_i\)。那么一段区间 \([l,r]\) 合法的条件就是 \(\min\limits_{i=l}^r L_i=l\land \max\limits_{i=l}^r R_i=r\)。可以 st 表预处理后二分出每个点作为 \(l\)\(r\) 时对应的另一个端点的合法范围,那么只需要两个点都在对方的范围内就可以组成一个合法的区间。可以以右端点的合法区间为询问区间,跑莫队,需要区间加加单点查询,因为加的次数很多而询问只有 \(n\) 次,选择用 \(\mathcal{O}(1)\) 单点加 \(\mathcal{O}(\sqrt{n})\) 区间求和的分块来维护,复杂度 \(\mathcal{O}(n\sqrt{n})\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[600005];
int tot,head[300005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int cur,top,rc,dfn[300005],low[300005],st[300005],in[300005],L[300005],R[300005],bel[300005];
void tarjan(int u){
	dfn[u]=low[u]=++cur;st[++top]=u,in[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
		else if(in[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int v;rc++;
		L[rc]=inf,R[rc]=-inf;
		do{
			v=st[top--],in[v]=0;
			bel[v]=rc;
			L[rc]=min(L[rc],v);
			R[rc]=max(R[rc],v);
		}while(v!=u);
	}
}
int deg[300005],Log[300005],f[22][300005],g[22][300005];
int askf(int l,int r){
	if(l>r)return inf;
	int o=Log[r-l+1];
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int askg(int l,int r){
	if(l>r)return -inf;
	int o=Log[r-l+1];
	return max(g[o][l],g[o][r-(1ll<<o)+1]);
}
int ll[300005],lr[300005],rl[300005],rr[300005];
struct Que{
	int l,r,id;
}qu[300005];
int n,m,siz,num,Bel[300005],bl[605],br[605];
int cmp(Que x,Que y){
	return Bel[x.l]<Bel[y.l]||(Bel[x.l]==Bel[y.l]&&((Bel[x.l]&1ll)?x.r<y.r:x.r>y.r));
}
struct Block{
	int s1[300005],s2[605];
	void Add(int x,int v){
		if(1<=x&&x<=n)s1[x]=(s1[x]+v+mod)%mod,s2[Bel[x]]=(s2[Bel[x]]+v+mod)%mod;
	}
	void Add(int l,int r,int v){
		if(l<=r)Add(l,v),Add(r+1,-v);
	}
	int ask(int l,int r){
		if(Bel[l]==Bel[r]){
			int res=0;
			for(int i=l;i<=r;i++)res=(res+s1[i])%mod;
			return res;
		}
		int res=0;
		for(int i=l;i<=br[Bel[l]];i++)res=(res+s1[i])%mod;
		for(int i=bl[Bel[r]];i<=r;i++)res=(res+s1[i])%mod;
		for(int i=Bel[l]+1;i<=Bel[r]-1;i++)res=(res+s2[i])%mod;
		return res;		
	}
}B;
void Add(int x){
	B.Add(ll[x],lr[x],1);
}
void Del(int x){
	B.Add(ll[x],lr[x],-1);
}
int ask(int l,int r){
	return B.ask(l,r);
}
signed main(){
	n=read(),m=read(),siz=(int)sqrt(n),num=(n+siz-1)/siz;
	for(int i=1;i<=n;i++)Bel[i]=(i-1)/siz+1; 
	for(int i=1;i<=num;i++)bl[i]=(i-1)*siz+1,br[i]=min(n,i*siz);
	for(int i=1,u,v;i<=m;i++)u=read(),v=read(),add(u,v);
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
	vector<pair<int,int> >tmp;
	for(int u=1;u<=n;u++){
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(bel[u]!=bel[v])tmp.push_back({bel[u],bel[v]});
		}
	}
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(auto x:tmp)add(x.second,x.first),deg[x.first]++;
	queue<int>q;
	for(int i=1;i<=rc;i++){
		if(!deg[i])q.push(i);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			L[v]=min(L[v],L[u]),R[v]=max(R[v],R[u]);
			if((--deg[v])==0)q.push(v);
		}
	}
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	for(int i=1;i<=n;i++)f[0][i]=L[bel[i]],g[0][i]=R[bel[i]];
	for(int j=1;j<=Log[n];j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
			g[j][i]=max(g[j-1][i],g[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1;i<=n;i++){
		int l=i,r=n,lp=i-1,rp=n+1;
		while(l<=r){
			int mid=(l+r)>>1;
			if(askf(i,mid)>i)lp=mid,l=mid+1;
			else r=mid-1;
		}
		l=i,r=n;
		while(l<=r){
			int mid=(l+r)>>1;
			if(askf(i,mid)<i)rp=mid,r=mid-1;
			else l=mid+1;
		}
		ll[i]=lp+1,lr[i]=rp-1;
	}
	for(int i=1;i<=n;i++){
		int l=1,r=i,lp=i+1,rp=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(askg(mid,i)<i)lp=mid,r=mid-1;
			else l=mid+1;
		}
		l=1,r=i;
		while(l<=r){
			int mid=(l+r)>>1;
			if(askg(mid,i)>i)rp=mid,l=mid+1;
			else r=mid-1;
		}
		rl[i]=rp+1,rr[i]=lp-1;
	}
	int cnt=0,ans=0;
	for(int i=1;i<=n;i++){
		if(rl[i]<=rr[i])qu[++cnt]={rl[i],rr[i],i};
	}
	sort(qu+1,qu+cnt+1,cmp);
	for(int i=1,ql=1,qr=0;i<=cnt;i++){
		while(ql>qu[i].l)Add(--ql);
		while(qr<qu[i].r)Add(++qr);
		while(ql<qu[i].l)Del(ql++);
		while(qr>qu[i].r)Del(qr--);
		ans=(ans+ask(1,qu[i].id))%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

坏了,赛时的做法好像很唐。

注意到如果一个点对应的合法区间存在,那么这个区间一定有一个端点是它自己!所以直接数点就行,这就是 \(\mathcal{O}(n\log n)\) 的了。

似乎还有分治的做法,不太会。

B 【1202 A组】人生画卷

C 【1202 A组】博弈游戏

P4694 [PA2013] Raper。鉴定为【模板】模拟费用流。

一眼的费用流。考虑每个 \(i\) 向所有 \(j\ge i\)\(j+n\) 连一条免费边,然后原点向 \(i\)\(a_i\) 的边,\(i+n\) 向汇点连 \(b_i\) 的边,限制一下最大流为 \(k\) 就行。注意到会向后缀连边,可以直接优化一下建图跑 flow,能获得 44pts。

考虑更强力的做法。这个费用流模型告诉我们,问题的答案关于 \(k\) 有凸性,可以使用 wqs 二分来优化。二分额外代价 \(c\),容易发现现在变成了一个 CF865D 那样的经典反悔贪心的样子,直接做就行。复杂度 \(\mathcal{O}(n\log^2 n)\)

能不能再强力一点?考虑模拟费用流,相当于 \(k\) 次操作,每次选两个位置 \(i,j\),让 \(c_i\) 加一并让 \(c_j\) 减一,代价是 \(a_i+b_j\),需要满足每次操作后 \(c_i\) 的前缀和都非负。为什么可以这样转化?听题解说这是因为每次增广的时候我们只会退流中间的边,所以这只会改变原来选的边的匹配性,原来那些边该选还得选。据说这个结论正确性显然,但我不太会证,感觉就很显然吧.jpg

考虑直接线段树维护 \(a_i+b_j\) 最小的合法选取方案 \((i,j)\)。当 \(i\le j\) 时没有限制,但是 \(i>j\) 时需要让 \([i,j)\) 内的前缀和大于 0。这个信息并不好维护,考虑维护一个很高妙的东西,下面给一坨定义:

记当前区间为 \([l,r]\)

  • \(\text{mina}/\text{minb}\) 表示区间里面 \(a/b\) 数组最小值所在的位置。

  • \(va\) 表示这个区间里面选择 \(i\le j\)\(a_i+b_j\) 最小的方案。

  • \(vc\) 表示这个区间里面选择 \(i>j\)\(a_i+b_j\) 最小的方案。

  • \(vb\) 表示这个区间里面选择 \(i>j\)\(a_i+b_j\) 最小的,且同时满足 \([i,j)\) 之间的前缀和的最小值大于 \([l,r]\) 里面前缀和的最小值的方案。

  • \(tag/mn\) 表示区间加法懒标记和 \([l,r]\) 内所有位置前缀和的最小值。

  • \(\text{alim}\) 表示满足 \([l,\text{alim})\) 区间中前缀和最小值大于 \([l,r]\) 前缀和最小值的位置中,\(a\) 数组对应位置上值最小的一个。

  • \(\text{blim}\) 表示满足 \([\text{blim},r]\) 区间中前缀和最小值大于 \([l,r]\) 前缀和最小值的位置中,\(b\) 数组对应位置上值最小的一个。

注意:\(\text{alim}\) 定义中区间是不到其本身位置的,但是 \(\text{blim}\) 的定义取到了,所以最初建线段树在 \(l=r\) 时可以给 \(\text{alim}\) 赋值,但不能给 \(\text{blim}\) 赋。

下面是 pushup 的过程:

  • 先用左右儿子内的信息直接更新 \(va,vb,vc,\text{mina},\text{minb},mn\)

  • \((\text{mina}(ls),\text{minb}(rs))\) 更新 \(va\),用 \((\text{mina}(rs),\text{minb}(ls))\) 更新 \(vc\)

然后是大力分讨:

  • \(mn(ls)>mn(rs)\) 时,用 \(vc(ls)\)\((\text{alim}(rs),\text{minb}(ls))\) 更新 \(vb\),用 \(\text{alim}(rs)\)\(\text{mina}(ls)\) 更新 \(\text{alim}\),用 \(\text{blim}(rs)\) 更新 \(\text{blim}\)

  • \(mn(ls)<mn(rs)\) 时,用 \(vc(rs)\)\((\text{mina}(rs),\text{blim}(ls))\) 更新 \(vb\),用 \(\text{alim}(ls)\) 更新 \(\text{alim}\),用 \(\text{blim}(rs)\)\(\text{minb}(ls)\) 更新 \(\text{blim}\)

  • \(mn(ls)=mn(rs)\) 时,用 \((\text{alim}(rs),\text{blim}(ls))\) 更新 \(vb\),用 \(\text{alim}(ls)\) 更新 \(\text{alim}\),用 \(\text{blim}(rs)\) 更新 \(\text{blim}\)

正确性显然,建议自己推一遍。

注意到当 \([l,r]=[1,n]\) 时因为上一次操作后每个位置前缀和都不小于 0,所以全局前缀和最小值一定为 0,这就刚好符合题目条件了。

直接重复 \(k\) 次,每次选最小的位置,复杂度 \(\mathcal{O}(k\log n)\)

实现的时候最好把 \(a_0,b_0\) 设成 \(\infty\),那些变量没有值的时候设成 0,这样会方便很多。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,a[500005],b[500005];
struct Info{
	int x,y;
	Info operator +(const Info &o)const{
		if(a[x]+b[y]<=a[o.x]+b[o.y])return {x,y};
		return o;
	}
};
struct segtree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mina,minb;
		Info va,vc,vb;
		int tag,mn,alim,blim;
	}c[2000005];
	void pushup(int p){
		if(a[c[ls].mina]<a[c[rs].mina])c[p].mina=c[ls].mina;
		else c[p].mina=c[rs].mina;
		if(b[c[ls].minb]<b[c[rs].minb])c[p].minb=c[ls].minb;
		else c[p].minb=c[rs].minb;
		c[p].mn=min(c[ls].mn,c[rs].mn);
		c[p].va=c[ls].va+c[rs].va;
		c[p].vb=c[ls].vb+c[rs].vb;
		c[p].vc=c[ls].vc+c[rs].vc;
		c[p].va=c[p].va+(Info){c[ls].mina,c[rs].minb};
		c[p].vc=c[p].vc+(Info){c[rs].mina,c[ls].minb};
		if(c[ls].mn>c[rs].mn){
			c[p].vb=c[p].vb+c[ls].vc;
			c[p].vb=c[p].vb+(Info){c[rs].alim,c[ls].minb};
			if(a[c[ls].mina]<=a[c[rs].alim])c[p].alim=c[ls].mina;
			else c[p].alim=c[rs].alim;
			c[p].blim=c[rs].blim;
		}
		else if(c[ls].mn<c[rs].mn){
			c[p].vb=c[p].vb+c[rs].vc;
			c[p].vb=c[p].vb+(Info){c[rs].mina,c[ls].blim};
			c[p].alim=c[ls].alim;
			if(b[c[rs].minb]<=b[c[ls].blim])c[p].blim=c[rs].minb;
			else c[p].blim=c[ls].blim;
		}
		else{
			c[p].vb=c[p].vb+(Info){c[rs].alim,c[ls].blim};
			c[p].alim=c[ls].alim;
			c[p].blim=c[rs].blim;
		}
	}
	void pushdown(int p){
		c[ls].mn+=c[p].tag,c[ls].tag+=c[p].tag;
		c[rs].mn+=c[p].tag,c[rs].tag+=c[p].tag;
		c[p].tag=0;
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){
			c[p].mina=c[p].minb=l;
			c[p].va=(Info){l,l},c[p].vb=c[p].vc=(Info){0,0};
			c[p].mn=0;
			c[p].alim=l;
			c[p].blim=0;
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void upd(int l,int r,int p,int x){
		if(l==r)return;
		int mid=(l+r)>>1;pushdown(p);
		if(x<=mid)upd(lson,x);
		else upd(rson,x);
		pushup(p);
	}
	void add(int l,int r,int p,int L,int R,int k){
		if(L>R)return;
		if(L<=l&&r<=R){
			c[p].mn+=k,c[p].tag+=k;
			return;
		}
		int mid=(l+r)>>1;pushdown(p);
		if(L<=mid)add(lson,L,R,k);
		if(R>mid)add(rson,L,R,k);
		pushup(p);
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs
}Tr;
signed main(){
	n=read(),m=read();a[0]=b[0]=inf;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i]=read();
	Tr.build(1,n,1);int ans=0;
	while(m--){
		Info tmp=Tr.c[1].va+Tr.c[1].vb;
		ans+=a[tmp.x]+b[tmp.y];
		if(tmp.x<=tmp.y)Tr.add(1,n,1,tmp.x,tmp.y-1,1);
		else Tr.add(1,n,1,tmp.y,tmp.x-1,-1);
		a[tmp.x]=inf,b[tmp.y]=inf;
		Tr.upd(1,n,1,tmp.x),Tr.upd(1,n,1,tmp.y);
	}
	printf("%lld\n",ans);
	return 0;
}

C0427 【1204 A组】模拟测试

A 【1204 A组】奇迹之夜

简单 dp。以 \(k\) 号地点为根,记 \(f_{i,0/1/2/3}\) 表示在以 \(i\) 为根的子树内,\(i\) 这个点选了日常/选了聚会且旁边已经有了后勤/选了聚会且旁边还没有后勤/选了后勤的最大价值和。可以随便转移一下。每次询问的限制相当于深度不超过 \(L\) 的点可以随便选,如果按深度分层,相当于上面的随便选,中间那层的 \(f\) 求和。dp 完之后预处理一下即可。记得判掉 \(L\ge n\) 的情况。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int a[100005],b[100005],f[100005][5],g[100005],h[100005],s[100005],dep[100005];
void dfs(int u,int fa,int d){
	dep[u]=d;
	f[u][0]=a[u],f[u][1]=b[u],f[u][2]=b[u],f[u][3]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u,d+1);
	}
	vector<int>tmp;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		f[u][0]+=max({f[v][0],f[v][1],f[v][3]});
		f[u][1]+=max({f[v][0],f[v][1]}),tmp.push_back(f[v][3]-max({f[v][0],f[v][1]}));
		f[u][2]+=max({f[v][0],f[v][1]});
		f[u][3]+=max({f[v][0],f[v][1],f[v][2],f[v][3]});
	}
	sort(tmp.begin(),tmp.end(),[](int x,int y){return x>y;});
	if(tmp.empty())f[u][1]=-inf;
	else{
		f[u][1]+=tmp[0];
		for(int i=1;i<(int)tmp.size();i++)f[u][1]+=max(0ll,tmp[i]);
	}
}
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i]=read();
	for(int i=1;i<=n;i++)b[i]=max(b[i],a[i]);
	int root=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs(root,0,0);
	for(int i=1;i<=n;i++)h[dep[i]]+=b[i];
	s[0]=h[0];for(int i=1;i<=n;i++)s[i]=s[i-1]+h[i];
	for(int i=1;i<=n;i++)g[dep[i]]+=max({f[i][0],f[i][1],f[i][2],f[i][3]});
	while(m--){
		int l=read();
		if(l>=n)printf("%lld\n",s[n]);
		else if(l)printf("%lld\n",g[l]+s[l-1]);
		else printf("%lld\n",g[l]);
	}
	return 0;
}

B 【1204 A组】树莓立方体

题解做法没看懂,写一个曦老师优秀做法。

考虑询问离线,挂到右端点,从大到小排序。那么现在从右往左移动右端点时就需要删除一个点,把一段分裂成几段,这个可以用 ST 表加二分处理。发现这个过程反过来就是每次加一个点,然后合并几段,于是容易证明这里均摊复杂度是对的。

为什么要从右往左扫呢?因为这个题是要维护一个每行 \(\min\)\(\max\),如果从左往右扫 \(\min\) 只会越来越小,此时无法忽略之前那个操作区间的影响,就寄了。但是反过来分裂出来的区间只会越来越大,所以之前的区间就直接没影响了,可以直接开一颗线段树维护。支持区间取 max,区间 \(F\) 值历史和,直接套 C 【1129 A组】序列 的板子就行。

有一些小细节:比如每次删点的时候那个点之后就不能再更新历史和了,还有问的是一段时间内的历史和,需要拆询问。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int k,n,q,A,B,C,a[25][50005],b[25][50005],s[50005];
int ql[50005],qr[50005],ans[50005];vector<int>v1[50005],v2[50005];
int F(int x){
	if(x==0)return 0;
	return A^(B*x+C);
}
struct segtree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int cnt,mn,se,tag,ad,dl,a,ta;
	}c[4000005];
	void pushup(int p){
		c[p].mn=min(c[ls].mn,c[rs].mn);c[p].cnt=0,c[p].se=inf;
		if(c[ls].mn==c[p].mn)c[p].cnt+=c[ls].cnt,c[p].se=min(c[p].se,c[ls].se);
		else c[p].se=min(c[p].se,c[ls].mn);
		if(c[rs].mn==c[p].mn)c[p].cnt+=c[rs].cnt,c[p].se=min(c[p].se,c[rs].se);
		else c[p].se=min(c[p].se,c[rs].mn);
		c[p].ad=c[ls].ad+c[rs].ad;
		c[p].dl=c[ls].dl+c[rs].dl;
	}
	void pushdown(int l,int r,int p){
		int mid=(l+r)>>1,ln=mid-l+1,rn=r-mid;
		if(c[ls].mn<c[p].tag)c[ls].ad+=c[ls].cnt*c[p].a,c[ls].dl+=c[ls].cnt*c[p].ta,c[ls].a+=c[p].a,c[ls].ta+=c[p].ta;
		if(c[rs].mn<c[p].tag)c[rs].ad+=c[rs].cnt*c[p].a,c[rs].dl+=c[rs].cnt*c[p].ta,c[rs].a+=c[p].a,c[rs].ta+=c[p].ta;
		if(c[ls].mn<c[p].tag)c[ls].tag=c[p].tag,c[ls].mn=c[p].tag;
		if(c[rs].mn<c[p].tag)c[rs].tag=c[p].tag,c[rs].mn=c[p].tag;
		c[p].a=c[p].ta=0,c[p].tag=-inf;
	}
	void build(int l,int r,int p){
		c[p].a=c[p].ta=0,c[p].tag=-inf;
		if(l==r){
			c[p].mn=c[p].ad=c[p].dl=0,c[p].se=inf,c[p].cnt=1;
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void add(int l,int r,int p,int L,int R,int v,int t){
		if(c[p].mn>=v)return;
		if(L<=l&&r<=R){
			if(c[p].mn<v&&v<c[p].se){
				c[p].a+=(F(v)-F(c[p].mn)),c[p].ta+=t*(F(v)-F(c[p].mn));
				c[p].ad+=c[p].cnt*(F(v)-F(c[p].mn)),c[p].dl+=c[p].cnt*t*(F(v)-F(c[p].mn));	
				c[p].tag=v,c[p].mn=v;
				return;
			}
		}
		if(l==r)return;
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)add(lson,L,R,v,t);
		if(R>mid)add(rson,L,R,v,t);
		pushup(p);
	}
	void upd(int l,int r,int p,int x,int t){
		if(l==r){
			c[p].a+=(F(0)-F(c[p].mn)),c[p].ta+=t*(F(0)-F(c[p].mn));
			c[p].ad+=c[p].cnt*(F(0)-F(c[p].mn)),c[p].dl+=c[p].cnt*t*(F(0)-F(c[p].mn));	
			c[p].tag=0,c[p].mn=0;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(x<=mid)upd(lson,x,t);
		else upd(rson,x,t);
		pushup(p);		
	}
	int ask(int l,int r,int p,int L,int R,int t){
		if(L<=l&&r<=R){
			return c[p].ad*t-c[p].dl;
		}
		int mid=(l+r)>>1,res=0;pushdown(l,r,p);
		if(L<=mid)res+=ask(lson,L,R,t);
		if(R>mid)res+=ask(rson,L,R,t);
		return res;
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs
}Tr;
int f[25][18][50005];
int ask(int I,int l,int r){
	if(l>r)return inf;
	int o=__lg(r-l+1);
	return min(f[I][o][l],f[I][o][r-(1ll<<o)+1]);
}
signed main(){
	k=read(),n=read(),q=read();
	for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)a[i][j]=read();
	A=read(),B=read(),C=read();
	for(int i=1;i<=q;i++){
		ql[i]=read(),qr[i]=read(),v1[qr[i]].push_back(i),v2[ql[i]].push_back(i);
	}
	for(int j=1;j<=k;j++){
		for(int i=1;i<=n;i++)f[j][0][i]=a[j][i];
		for(int o=1;(1ll<<o)<=n;o++){
			for(int i=1;i+(1ll<<o)-1<=n;i++){
				f[j][o][i]=min(f[j][o-1][i],f[j][o-1][i+(1ll<<(o-1))]);			
			}
		}
	}
	Tr.build(1,n,1);
	for(int i=n,t=1;i>=1;i--,t++){
		for(auto x:v1[i])ans[x]-=Tr.ask(1,n,1,ql[x],qr[x],t-1);
		if(i+1<=n)Tr.upd(1,n,1,i+1,t-1);
		for(int j=1;j<=k;j++){
			int p=i;
			while(p>=1&&a[j][p]>a[j][i+1]){
				int l=1,r=p,res=0;
				while(l<=r){
					int mid=(l+r)>>1;
					if(ask(j,mid,p)==a[j][p])res=mid,r=mid-1;
					else l=mid+1;
				}
				Tr.add(1,n,1,res,p,a[j][p],t-1),p=res-1;
			}
		}
		for(auto x:v2[i])ans[x]+=Tr.ask(1,n,1,ql[x],qr[x],t);
	} 
	for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
	return 0;
}

C 【1204 A组】爱上火车

考虑一个 25pts 的朴素 dp。定义 \(f_{i,j}\) 表示第 \(i\) 天坐了 \(j\) 次火车的最大价值和,有转移式 \(f_{i,j}=\max(f_{i-1,j},f_{i-1,j-1})+a_{j\bmod k,i}\)。发现题目中有刚好选 \(k\) 个的限制,考虑寻找凸性。大力瞪眼可以发现如果记 \(g_{p,q,i}=f_{i,pk+q}\),那么 \(g_{p,q,*}\) 是凸的。证明略。考虑开一颗线段树维护凸包。具体的,考虑一个状态 \((l,r,p,q)\),表示从第 \(l\) 天到第 \(r\) 天,第 \(l\) 天时在第 \(p\) 座城市,第 \(r\) 天时在第 \(q\) 座城市,可以维护 \((*,*,p,q)\) 的凸包,每个点 \((x,y)\) 表示坐了 \(x\) 次火车的最大价值和为 \(y\)

合并是简单的。对于两个位置 \((x_1,y_1)\)\((x_2,y_2)\),容易发现合并后新的点就是 \((x_1+x_2,y_1+y_2)\)。注意到这就是一个 max,+ 的卷积,即对这两个凸包求闵可夫斯基和,可以做到 \(\mathcal{O}(|S_1|+|S_2|)\)。所以转移就是你枚举 \((l,mid)\) 的开始城市 \(i\),结束城市 \(o\)\((mid+1,r)\) 的结束城市 \(j\)。如果从 \(mid\) 天到第 \(mid+1\) 天没有坐火车就是 \((l,mid,i,o)+(mid+1,r,o,j)\),否则就是 \((l,mid,i,o)+(mid+1,r,(o+1)\bmod k,j)\),注意后面的转移需要让横坐标全部 +1。

算一下空间。\((l,r,p,q)\) 对应的凸包大小就是 \(\dfrac{r-l}{k}\)。由于每个点需要维护 \(k^2\) 个凸包,所以空间是 \(k^2\sum\dfrac{len}{k}=kn\log n\) 的。时间就是 \(k^3\sum\dfrac{len}{k}=k^2n\log n\)。4 秒钟的话常数大点应该也可以过。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
typedef pair<ll,ll>pii;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,q,a[5][100005];
struct convex{
	vector<pii>v;
	convex add(){
		convex b;b.v=v;
		for(int i=0;i<(int)v.size();i++)b.v[i].fi++;
		return b;
	}
	convex operator +(const convex &b)const{
		if(v.empty()||b.v.empty())return (convex){vector<pii>()};
		convex c;int i=1,j=1;
		c.v.push_back({v[0].fi+b.v[0].fi,v[0].se+b.v[0].se});
		while(i<(int)v.size()&&j<(int)b.v.size()){
			if(v[i].se-v[i-1].se>=b.v[j].se-b.v[j-1].se)c.v.push_back({v[i].fi+b.v[j-1].fi,v[i].se+b.v[j-1].se}),i++;
			else c.v.push_back({v[i-1].fi+b.v[j].fi,v[i-1].se+b.v[j].se}),j++;
		}
		while(i<(int)v.size())c.v.push_back({v[i].fi+b.v[j-1].fi,v[i].se+b.v[j-1].se}),i++;
		while(j<(int)b.v.size())c.v.push_back({v[i-1].fi+b.v[j].fi,v[i-1].se+b.v[j].se}),j++;
		return c;
	}
	void merge(convex b){
		for(int i=0,j=0;i<(int)v.size();i++){
			while(j<(int)b.v.size()&&b.v[j].fi<v[i].fi)j++;
			if(j<(int)b.v.size()&&b.v[j].fi==v[i].fi)v[i].se=max(v[i].se,b.v[j].se);
		}
	}
}t[400005][5][5];
void build(int l,int r,int p){
	if(l==r){
		for(int i=0;i<m;i++)t[p][i][i].v.push_back({0,a[i][l]});
		return;
	}
	int mid=(l+r)>>1;build(l,mid,p<<1);build(mid+1,r,p<<1|1);
	for(int i=0;i<m;i++){
		for(int j=0;j<m;j++){
			for(int x=j-i;x<=r-l;x+=m)if(x>=0)t[p][i][j].v.push_back({x,0});
			if(t[p][i][j].v.empty())continue;
			for(int o=0;o<m;o++){
				t[p][i][j].merge(t[p<<1][i][o]+t[p<<1|1][o][j]);
				t[p][i][j].merge((t[p<<1][i][o]+t[p<<1|1][(o+1)%m][j]).add());		
			}
		}
	}
	for(int l=0;l<m;l++)for(int r=0;r<m;r++){
		vector<pii>().swap(t[p<<1][l][r].v);
		vector<pii>().swap(t[p<<1|1][l][r].v);
	}
} 
ll ans[100005];
signed main(){
	n=read(),m=read(),q=read();
	for(int i=0;i<m;i++)for(int j=1;j<=n;j++)a[i][j]=read();
	build(1,n,1);
	for(int i=0;i<m;i++){
		for(auto x:t[1][0][i].v)ans[x.fi+1]=max(ans[x.fi+1],x.se);
	}
	while(q--){
		int pos=read();
		printf("%lld\n",ans[pos]);
	}
	return 0;
}

C0432 【1208 A组】模拟测试

A 【1208 A组】蛋神的团建

P6571 [BalticOI 2017] Political Development。

又不会做蓝题。

考虑部分分。当 \(d_i\le k\) 时我们可以枚举一个在团里的点,然后团里其他点只会是它相邻的点,可以 \(nk^22^k\) 算。优化就是你发现周围只有至多 10 个点,记周围的点集为 \(S\),可以 \(k^2\log n\) 预处理 \(S\) 中每个点能到达 \(S\) 中哪些点,这样单次检查复杂度变为 \(\mathcal{O}(k)\)

考虑正解。注意到还有性质没用。发现整张图一定存在至少一个点 \(d_i\le k\),那么对这个点跑上述做法后这个点就没用了,可以删去。删去后这个新图一定也有度数不超过 \(k\) 的点,直接递归做就行。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,k,ans,d[50005],vis[50005],id[50005],f[50005];set<int>g[50005];
void dfs(int u){
	vis[u]=1;vector<int>tmp;tmp.push_back(u);
	for(auto v:g[u])if(!vis[v])tmp.push_back(v);
	int siz=(int)tmp.size();
	for(int i=0;i<siz;i++){
		f[i]=0;
		for(int j=0;j<siz;j++){
			int x=tmp[i],v=tmp[j];
			if(g[x].count(v)||x==v)f[i]|=1ll<<j;
		}
	}
	for(int i=0;i<(1ll<<siz);i++){
		int flag=1;
		for(int j=0;j<siz;j++){
			if((i>>j)&1ll&&(f[j]&i)!=i){flag=0;break;}
		}
		if(flag)ans=max(ans,(int)__builtin_popcountll(i));
	}
	for(auto v:g[u])if(!vis[v])g[v].erase(u),d[v]--;
	for(auto v:g[u])if(!vis[v]&&(int)g[v].size()<=k)dfs(v);
}
signed main(){
	n=read(),k=read();
	for(int i=0;i<n;i++){
		d[i]=read();
		for(int j=1;j<=d[i];j++)g[i].insert(read());
	}
	for(int i=0;i<n;i++)if(!vis[i]&&(int)g[i].size()<=k)dfs(i);
	printf("%lld\n",ans);
	return 0;
}

B 【1208 A组】行列式

C 【1208 A组】万灵药