20251217NOI模拟赛

20251217NOI模拟赛

T2.背包问题 1

题面:

\(n\) 个物品,重量依次为 \(a_1,\ldots,a_n\)
定义一个二元组 \((x,y)\) 合法,当且仅当对于任意 \(0\leq x^{\prime}\leq x,0\leq y^{\prime}\leq y\),都可以将物品分为三组,使得第一组重量之和为 \(x^{\prime}\) ,第二组重量之和为 \(y^{\prime}\)
进行 \(m\) 次修改,每次给定 \(p,w\) ,将 \(a_p\) 修改为 \(w\)
在所有修改之前及每次修改后求合法二元组 \((x,y)\) 的数量,答案对998244353取模。
\(1\leq n\leq2\times10^5,0\leq m\leq2\times10^5,1\leq a_i,w\leq10^{12},1\leq p\leq n\)

题解:

先考虑 \(m=0\) 的情况。
定义 \((x,y)\) 可被表示当且仅当存在将物品划分成三组的方式使得第一组重量和为 \(x\),第二组重量和为 \(y\) 按照重量从小到大依次加入每个物品,维护每个 \((x,y)\) 是否可被表示。

假设当前最后一个加入的物品重量为 \(w\),若 \((x,y)\) 不可被表示且 \(x<w,y<w\),则 \((x,y)\) 最终不可被表示,进而所有满足 \(x^{\prime}\geq x,y^{\prime}\geq y\)\((x^{\prime},y^{\prime})\) 最终不可能合法。而对于一个当前可被表示的 \((x^{\prime},y^{\prime})\) 它在之后的转移中只可能影响到 \((\geq x^{\prime},\geq y^{\prime})\) 的部分。故在上述情况中可以直接将 \((\geq x,\geq y)\) 中的部分删除,不需要考虑。

可以归纳证明:未被删除的部分中所有可被表示的 \((x,y)\) 一定形成一个左下阶梯,故它们均合法。

观察阶梯更新方式。令当前所有数之和为 \(s\)。则一段前缀的时刻中,阶梯恰好覆盖所有 \(x+y\leq s\)\((x,y)\)
在此之后,只考虑 \(x\geq y\) 的部分,维护 \(t\) 表示 \(y\leq t\) 的行还可继续延伸,初始 \(s=t\)。可以归纳地证明:\(y\leq t\) 时纵坐标为 \(y\) 的行中 \(x\) 可被表示的格子为 \((y,s-y)\)

加入一个重量为 \(w\) 的物品时,通过画图可发现纵坐标 \(>s-w+1\) 的行不再可延伸,故 \(t\leftarrow\min(t,s-w+1)\),并将纵坐标为 \(0\sim t\) 的行延伸 \(w\) 格。(注意这里的 \(s\) 是加入 \(w\) 之前的)
模拟上述过程即可做到 \(O(n\log n)\)

考虑用线段树修改,维护当前时刻值域上有哪些点。
考虑第一次存在 \(x+y\leq s\) 的所有点不能都被表示的条件。设你当前和是 \(s\),加入一个物品 \(w\),如果存在一个点不能被表示出来那么这个点 \((x,y)\) 肯定是 \(x+y=s+1\) 然后 \(x-w<0,y-w<0\),这样他就无法表示。所以我们取 \(x=y=(s+1)/2\) 最优,解方程可得如果 \(s<2*w-2\) 则它不能被表示。这部分可以通过线段树二分找到。
然后每次找到第一个 \(s-w+1<t\) 的点,这样的点只有 \(\log\) 个所以复杂度是对的,这也可以线段树维护。

只有 \(\log\) 个是因为假设本次符合条件的点是 \(w\),下次是 \(v\),那么有 \((s+w)-v+1<s-w+1\) 解得 \(2*w<v\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=2e5+5,mod=998244353;
int X[N],n,m,cnt;
ll a[N],W[N],ans;
struct Tree{
	ll v,mx,mn;
}t[N<<3];
vector<pair<ll,bool> >vec;
pair<ll,int>lsh[N<<1];

void pushup(int s){
	t[s].v=t[s<<1].v+t[s<<1|1].v;
	t[s].mx=max(t[s<<1].mx,t[s<<1|1].mx-t[s<<1].v);
	t[s].mn=min(t[s<<1].mn,t[s<<1].v+t[s<<1|1].mn);
}

void build(int s,int l,int r){
	if(l==r){
		t[s]={0,-2,1};
		return ;
	}
	int mid=l+r>>1;
	build(s<<1,l,mid);
	build(s<<1|1,mid+1,r);
	pushup(s);
}

void update(int s,int l,int r,int x,ll v){
	if(l==r){
		t[s].v=v;
		t[s].mx=2*v-2;
		t[s].mn=-v+1;
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid) update(s<<1,l,mid,x,v);
	else update(s<<1|1,mid+1,r,x,v);
	pushup(s);
}

int erfen(int s,int l,int r,ll &S){
	if(S>=t[s].mx){
		S+=t[s].v;
		return cnt+1;
	}
	if(l==r) return l;
	int mid=l+r>>1,res=cnt+1;
	res=erfen(s<<1,l,mid,S);
	if(res!=cnt+1) return res;
	res=erfen(s<<1|1,mid+1,r,S);
	return res;
}

ll Mod(ll x){return x%mod;}

void prt(int s,int l,int r,int x,ll &S,ll &T){
	if(T<0) return ;
	if(x<=l&&S+t[s].mn>=T){
		S+=t[s].v;
		(ans+=Mod(T+1)*Mod(t[s].v)%mod*2)%=mod;
		return ;
	}
	if(l==r){
		T=S-t[s].v+1;
		if(T<0) return ;
		S+=t[s].v;
		(ans+=Mod(T+1)*Mod(t[s].v)%mod*2)%=mod;
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid) prt(s<<1,l,mid,x,S,T);
	prt(s<<1|1,mid+1,r,x,S,T);
}

void solve(){
	ll S=0;
	int p=erfen(1,1,cnt,S);
	ll T=S;
	ans=Mod(S+1)*Mod(S+2)%mod*(mod+1>>1)%mod;
	if(p<=cnt) prt(1,1,cnt,p,S,T);
	printf("%lld\n",ans);
}

int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++){
		X[i]=read();
		W[i]=read();
	} 
	for(int i=1;i<=n;i++) lsh[++cnt]={a[i],i};
	for(int i=1;i<=m;i++) lsh[++cnt]={W[i],n+i};
	sort(lsh+1,lsh+1+cnt);
	build(1,1,cnt);
	for(int i=1;i<=cnt;i++){
		int x=lsh[i].second;
		if(x>n) W[x-n]=i;
		else{
			update(1,1,cnt,i,a[x]);
			a[x]=i;	
		}
	}
	solve();
	for(int i=1;i<=m;i++){
		int x=X[i];
		update(1,1,cnt,a[x],0);
		a[x]=W[i];
		update(1,1,cnt,a[x],lsh[a[x]].first);
		solve();
	}
	return 0;
}

T3.游乐园

题面:

游乐园里要添加一个闯关游戏。
游戏区域是一个 \(n\times m\) 网格图。从 \((i,j)\)\((i,j+1)\) 的单向边预期需要时间 \(r_{i,j}\),从 \((i,j)\)\((i+1,j)\) 的双向边预期需要时间 \(c_{i,j}\)
游客需要从起点冲到最后一列。设计师想知道在哪里设置起点体验感最佳。你需要告诉他如果在第 \(i\) 行第一列 \((i,1)\) 设置起点,到最后一列所有点 \((j,m)\) 的最短时间总和是多少。
形式化题面:对于每个 \(i\),输出 \(\sum_{j=1}^ndis(i,1,j,m)\)。其中 \(dis(x_1,y_1,x_2,y_2)\) 表示从 \((x_1,y_1)\)\((x_2,y_2)\) 的最短路。\(1\leq n\times m\leq 2\times 10^5,1\leq r_{i,j},c_{i,j}\leq 10^6\)

题解:

考虑当起点从 \((i,0)\) 移动到 \((i+1,0)\) 时,最短路树的变化。最短路上的每个点的父亲具有决策单调性,具体的一个点 \((i,j)\) 的父亲会先是 \((i-1,j)\) 然后是 \((i,j-1)\) 最后是 \((i+1,j)\)
假设当前已经求出起点为 \((l,0)\)\((r,0)\) 的最短路树。若某个点的父亲在两棵树上都是一样的,说明当起点 \((i,0)\) 位于 \(l\leq i\leq r\) 时,该点的父亲都不变。那么可以将其与父亲缩点。
考虑分治,继续递归分治 \([l,\lfloor\frac{l+r}2\rfloor]\)\([\lfloor\frac{l+r}2\rfloor,r]\),并继续缩点。这样每个边只会出现在 \(\log\) 个区间中,这样总边数是 \(\log\) 级别的,那么求最短路的复杂度也是 \(\log\) 的。
缩点时每次记录,连通快中有几个终点,连通块根距离所有终点的距离和是多少,然后记得更新所有和这个连通块有关的边权。
复杂度 \(O(nm\log nm)\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const ll inf=1e18;
int n,m;
vector<vector<int> >A,B;
vector<ll>ans;
struct Graph{
	int n;
	vector<ll>dis;
	vector<pair<int,ll> >con;
	vector<tuple<int,int,ll> >e;
	
	Graph(){
		n=0;
		e.clear();
		dis.clear();
		con.clear();
	}
	
	void init(int tot){
		n=tot;
		vector<pair<int,ll> >(tot,{0,0}).swap(con);
	}
	
	bool get(int u,int v,ll w){ return dis[u]+w==dis[v]; }
	
	void bfs(int s){
		vector<ll>(n,inf).swap(dis);
		dis[s]=0;
		for(auto o:e){
			int u,v;ll w;
			tie(u,v,w)=o;
			dis[v]=min(dis[v],dis[u]+w);
		}
	}
	
	ll query(){
		ll ans=0;
		for(int i=0;i<n;i++) 
			ans+=dis[i]*con[i].first+con[i].second;
		return ans;
	}
}L,R;

namespace dsu{
	vector<int>fa;
	
	void clear(int n){
		vector<int>(n).swap(fa);
		for(int i=0;i<n;i++) fa[i]=i;
	}
	
	bool get(int x){return fa[x]==x;}
	
	int find(int x){
		if(x==fa[x]) return x;
		else return fa[x]=find(fa[x]);
	}
}

pair<int,ll> operator + (pair<int,ll>&A,pair<int,ll>&B){
	return {A.first+B.first,A.second+B.second};
}

Graph merge(Graph &A,Graph &B){
	Graph C;
	dsu::clear(A.n);
	vector<pair<int,ll> >tmp=A.con;
	for(auto o:A.e){
		int u,v;ll w;
		tie(u,v,w)=o;
		if(dsu::get(v)&&A.get(u,v,w)&&B.get(u,v,w)){
			int x=dsu::find(u);
			dsu::fa[v]=x;
			tmp[x]=tmp[x]+tmp[v];
			tmp[x].second+=(A.dis[v]-A.dis[x])*tmp[v].first;
		}
	}
	int &n=C.n;
	vector<ll>&dis=C.dis;
	vector<pair<int,ll> >&con=C.con;
	vector<tuple<int,int,ll> >&e=C.e;
	vector<int>ids(A.n,-1);
	for(int i=0;i<A.n;i++)
		if(dsu::get(i)){
			ids[i]=n++;
			con.push_back(tmp[i]);
			dis.push_back(A.dis[i]);
		}
	for(auto o:A.e){
		int u,v;ll w;
		tie(u,v,w)=o;
		if(dsu::get(v)&&dsu::find(u)!=dsu::find(v)){
			int x=dsu::find(u);
			e.emplace_back(ids[x],ids[v],w+A.dis[u]-A.dis[x]);
		}
	}
	return C;
}

int ids(int x,int y){return y*n+x;}

void solve(int l,int r,Graph L,Graph R){
	if(l==r-1){
		ans[l]=L.query();
		return ;
	}
	Graph M=L;
	int mid=l+r>>1;
	M.bfs(mid-l);
	solve(l,mid,merge(L,M),merge(M,L));
	solve(mid,r,merge(M,R),merge(R,M));
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();m=read();
	vector<vector<int> >(n,vector<int>(m-1)).swap(A);
	vector<vector<int> >(n-1,vector<int>(m)).swap(B);
	for(int i=0;i<n;i++)
		for(int j=0;j<m-1;j++) A[i][j]=read();
	for(int i=0;i<n-1;i++)
		for(int j=0;j<m;j++) B[i][j]=read();
	L.init(n*m);R.init(n*m);
	for(int i=0;i<n;i++){
		int x=(m-1)*n+i;
		R.con[x]=L.con[x]={1,0};
	}
	for(int j=0;j<m-1;j++){
		for(int i=0;i<n-1;i++){
			L.e.emplace_back(ids(i,j),ids(i+1,j),B[i][j]);
			R.e.emplace_back(ids(i,j),ids(i+1,j),B[i][j]);
		}
		for(int i=n-2;i>=0;i--){
			L.e.emplace_back(ids(i+1,j),ids(i,j),B[i][j]);
			R.e.emplace_back(ids(i+1,j),ids(i,j),B[i][j]);
		}
		for(int i=0;i<n;i++){
			L.e.emplace_back(ids(i,j),ids(i,j+1),A[i][j]);
			R.e.emplace_back(ids(i,j),ids(i,j+1),A[i][j]);
		}
	}
	int j=m-1;
	for(int i=0;i<n-1;i++){
		L.e.emplace_back(ids(i,j),ids(i+1,j),B[i][j]);
		R.e.emplace_back(ids(i,j),ids(i+1,j),B[i][j]);
	}
	for(int i=n-2;i>=0;i--){
		L.e.emplace_back(ids(i+1,j),ids(i,j),B[i][j]);
		R.e.emplace_back(ids(i+1,j),ids(i,j),B[i][j]);
	}
	L.bfs(0);R.bfs(n-1);
	vector<ll>(n).swap(ans);
	ans[n-1]=R.query();
	if(n-1) solve(0,n-1,L,R);
	for(ll x:ans) printf("%lld\n",x);
	return 0;
}

posted @ 2025-12-18 11:46  programmingysx  阅读(2)  评论(0)    收藏  举报
Title