2025 省选做题记录(五)


\(\text{By DaiRuichen007}\)



Round #51 - 20250214

A. 雨水(rain)

Problem Link

题目大意

给定 \(a_1\sim a_n\),初始有 \(n\) 个区间 \([1,1]\sim [n,n]\),每次合并两个相邻区间,对每个区间 \([l,r]\) 计算 \(\sum_{i=l}^r\min(\max a[l,i],\max a[i,r])-a_i\)

数据范围:\(n\le 10^5\)

思路分析

先维护出所有询问的 \([l,r]\),设最大值的下标是 \(x\),那么 \(i<x\) 时贡献是 \(\max a[l,i]-a_i\),否则是 \(\max a[i,r]-a_i\)

那么我们要维护的就是 \([l,x-1]\) 的每个前缀最大值之和,以及 \([x+1,r]\) 的每个后缀最大值之和。

离线扫描线,单调栈配合树状数组维护。

时间复杂度 \(\mathcal O(n\log n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
int n,a[MAXN],tl[MAXN],mx[MAXN];
ll sum[MAXN],ans[MAXN];
struct BIT1 {
	int tr[MAXN];
	void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]+=v; }
	int qry(int k) {
		int s=0,c=0;
		for(int i=16;~i;--i) if(s+(1<<i)<=n&&c+tr[s+(1<<i)]<k) s+=1<<i,c+=tr[s];
		return s+1;
	}
}	S;
struct BIT2 {
	ll t1[MAXN],t2[MAXN];
	void init() { memset(t1,0,sizeof(t1)),memset(t2,0,sizeof(t2)); }
	void add(int x,ll v) {
		for(int i=x;i<=n;i+=i&-i) t1[i]+=v,t2[i]+=v*x;
	}
	void add(int l,int r,ll v) {
		if(v) add(l,v),add(r+1,-v);
	}
	ll qry(int x) {
		ll s1=0,s2=0;
		for(int i=x;i;i&=i-1) s1+=t1[i],s2+=t2[i];
		return s1*(x+1)-s2;
	}
	ll qry(int l,int r) { return l<=r?qry(r)-qry(l-1):0; }
}	T;
vector <array<int,2>> ql[MAXN],qr[MAXN];
int st[MAXN],tp;
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i],S.add(i,1),tl[i]=mx[i]=i;
	for(int i=1,x;i<n;++i) {
		scanf("%d",&x);
		int p=S.qry(x),q=tl[p]+1;
		mx[p]=(a[mx[p]]>a[mx[q]]?mx[p]:mx[q]);
		tl[p]=tl[q],S.add(q,-1);
		int l=p,r=tl[p],z=mx[p];
		ans[i]-=sum[r]-sum[l-1]-a[z];
		if(l<z) qr[l].push_back({z-1,i});
		if(z<r) ql[r].push_back({z+1,i});
	}
	T.init(),st[tp=0]=0;
	for(int i=1;i<=n;++i) {
		for(;tp&&a[st[tp]]<=a[i];--tp) T.add(st[tp-1]+1,st[tp],-a[st[tp]]);
		T.add(st[tp]+1,i,a[i]),st[++tp]=i;
		for(auto o:ql[i]) ans[o[1]]+=T.qry(o[0],i);
	}
	T.init(),st[tp=0]=n+1;
	for(int i=n;i>=1;--i) {
		for(;tp&&a[st[tp]]<=a[i];--tp) T.add(st[tp],st[tp-1]-1,-a[st[tp]]);
		T.add(i,st[tp]-1,a[i]),st[++tp]=i;
		for(auto o:qr[i]) ans[o[1]]+=T.qry(i,o[0]);
	}
	for(int i=1;i<n;++i) printf("%lld\n",ans[i]);
	return 0;
}



*B. 基因(gene)

Problem Link

题目大意

给定 \(n\) 个字符,字符集 \(\texttt{R,S,T}\),每次可以选出若干个字符(可重复)并询问:

  • 如果询问的字符中有 \(\texttt T\),那么会返回 \(\texttt S\) 的个数。
  • 否则会返回询问字符的个数。

\(150\) 次询问内确定每个字符的种类,每次询问至多选 \(300\) 个字符。

交互器不是自适应的。

数据范围:\(n\le 300\)\(\texttt T\) 的个数 \(t\le 30\)

思路分析

由于交互库不自适应,因此可以随机重排整个序列。

首先 \(2n\) 次询问是简单的,询问每个单点得到所有 \(\texttt T\),然后每个不为 \(\texttt T\) 的字符和 \(\texttt T\) 一起询问就能确定是 \(\texttt S\) 还是 \(\texttt T\)

然后可以优化,由于可以加入重复元素,因此可以用二进制一次性询问多个字符:

  • 选出若干个非 \(\texttt T\) 字符,对于第 \(i\) 个字符选择 \(2^{i-1}\) 次,再加上一个 \(\texttt T\),得到的结果里二进制为 \(1\) 的位对应的字符是 \(\texttt S\),否则对应的字符是 \(\texttt R\)

由于 \(2^8-1<300\),因此一次可以解决 \(8\) 个字符,可以做到 \(n+n/8\) 次操作。

如果用二分确定每个 \(\texttt T\),操作次数 \(8t+n/8\)

直接二分未必优秀,可以先套一层分块。

我们 \(8\) 个元素分一块,每块先询问一次,如果有 \(\texttt T\) 再二分。

此时询问次数不超过 \(n/8+3t+n/8\),我们再在这上面进行一些常数优化。

首先首轮分块我们只询问 \(8\) 个元素,那么可以用刚刚的二进制做法,如果块中有 \(\texttt T\),那么就能确定这个块里所有的 \(\texttt S\)

对于每个有 \(\texttt T\) 的块,求出最左边的 \(\texttt T\),然后右边剩余的元素全部放入集合 \(Q\) 中,这些元素字符集为 \(\{\texttt R,\texttt T\}\)

然后每次取出 \(Q\) 的前 \(8\) 个元素,先判定是否有 \(\texttt T\) 再二分,把剩下的元素放回 \(Q\)

然后对于首次二分中没有 \(\texttt T\) 的块,我们在后续的判定与二分过程中,取出 \(8\) 个元素然后用用二进制做法,如果这次判定中有 \(\texttt T\) 字符,那么这 \(8\) 个元素也能被确定。

那么这些没有 \(\texttt T\) 的块期望上来说肯定在求 \(\texttt T\) 的块的过程中就会被确定。

其次在随机的过程中,如果第一个 \(\texttt T\) 的位置比较大,那么被插入 \(Q\) 的元素期望并不多。

不妨把询问次数近似看成 \(n/8+3t+|Q|/8\),而 \(|Q|\) 不妨看作 \(t\)\([0,7]\) 的随机变量之和。

如果每个变量都是均匀随机的用一个简单的 dp 能算出询问次数 \(\le 146\) 的概率已经超过 \(99.9\%\)

如果粗略地考虑每个变量在 \([0,7]\) 上的分布,则询问次数 \(\le 147\) 的概率超过 \(99.5\%\)\(\le 148\) 的概率超过 \(99.9\%\)

时间复杂度 \(\mathcal O(n^2)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
int query_sample(vector<int>s);
void answer_type(int x,char c);
mt19937 rnd(time(0));
typedef vector<int> vi;
int sz(vi x) { return x.size(); }
void operator +=(vi &x,vi y) { for(int i:y) x.push_back(i); }
vi I(vi x,int l,int r) { return vi(x.begin()+l,x.begin()+r); }
void pop(vi &x,int m) { x.erase(x.begin(),x.begin()+m); }
char st[305];
void determine_type(int n) {
	vi A; for(int i=1;i<=n;++i) A.push_back(i),st[i]=0;
	shuffle(A.begin(),A.end(),rnd);
	vi R,S,T,RS,RT; vector <vi> G;
	while(A.size()) {
		int m=min(sz(A),8);
		vi q,u;
		for(int i=0;i<m;++i) for(int j=0;j<(1<<i);++j) q.push_back(A[i]);
		int z=query_sample(q);
		if(z==sz(q)) RS+=I(A,0,m);
		else for(int i=0;i<m;++i) {
			if(z>>i&1) st[A[i]]='S';
			else u.push_back(A[i]);
		}
		if(u.size()) G.push_back(u);
		pop(A,m);
	}
	auto chk=[&](vi q) {
		int m=min(sz(RS),8);
		for(int i=0;i<m;++i) for(int j=0;j<(1<<i);++j) q.push_back(RS[i]);
		int z=query_sample(q);
		if(z==sz(q)) return false;
		for(int i=0;i<m;++i) st[RS[i]]="RS"[z>>i&1];
		pop(RS,m);
		return true;
	};
	auto sol=[&](vi q) {
		shuffle(q.begin(),q.end(),rnd);
		int l=0,r=sz(q)-1;
		while(l<r) {
			int mid=(l+r)>>1;
			if(chk(I(q,l,mid+1))) r=mid;
			else l=mid+1;
		}
		for(int i=0;i<=l;++i) st[q[i]]="RT"[i==l];
		return I(q,l+1,sz(q));
	};
	for(auto it:G) RT+=sol(it);
	while(RT.size()) {
		int m=min(sz(RT),8);
		auto it=I(RT,0,m); pop(RT,m);
		if(!chk(it)) for(int i:it) st[i]='R';
		else RT+=sol(it);
	}
	int _=find(st+1,st+n+1,'T')-st;
	while(RS.size()) chk({_});
	for(int i=1;i<=n;++i) answer_type(i,st[i]);
}



*C. 邮局(post)

Problem Link

题目大意

给定 \(n\) 个点的基环内向树森林,\(m\) 条个棋子要从 \(u\to v\),每个时刻可以把每个棋子移动到父亲,但同一个格子的棋子只能移动一个,求所有棋子到达的最小时间。

数据范围:\(n,m\le 2\times 10^5\)

思路分析

首先我们每轮肯定有限移动距离远终点的点,这样可以得到一个朴素暴力。

但是这个做法无法优化,需要更好地刻画答案,考虑每条边对答案的贡献,即对于每条边 \(u\to fa_u\),我们计算所有棋子都离开 \(u\) 的最小时刻 \(w_u\)

设所有经过这条边的棋子到 \(u\) 的距离为 \(d_1\sim d_k\)(按离开顺序排列),考虑每个棋子离开这条边的时刻 \(t_i\),那么有 \(t_i\ge \max(d_i,t_{i-1})+1\)

此时 \(w_u=\max d_{k-i+1}+i\),显然 \(d\) 升序时取到最小。

我们能证明答案恰好能取到下界 \(\max w_u\),观察 \(w_u\) 的计算,相当于对于一些在 \(u\) 的前驱处重合的棋子,我们钦定他们可以一起运动到 \(u\) 再考虑重合的问题。

直观上来看这个做法是很对的,因为重合后这两个棋子已经没有本质区别,无论谁先到 \(u\) 都是等价的。

即所有经过这条边的棋子的贡献可以正确计算,但有一些不经过 \(u\) 的棋子,我们还要考虑他对答案的影响。

对于一条链 \(x\to y\to u\),如果有一个棋子从 \(x\to y\),那么他在 \(x\) 处可能卡住其他到 \(u\) 的径棋子

根据我们的朴素贪心,这显然是不可能的,因为经过 \(u\) 的棋子剩余距离更大,因此 \(x\to y\) 的棋子一定会在这些棋子离开 \(x\) 之后再运动,所有这些棋子无影响。

所以我们能证明最优解中,最后一个棋子离开这条边的时刻恰好就是 \(w_u\)

那么考虑树的情况,把过 \(u\) 的路径用线段树合并,维护每条路径终点的深度,push up 时左边的最大答案加上右边的终点个数,这样就能维护 \(w_u\) 了。

然后处理基环树,我们只要让覆盖每条边的棋子集合正确即可。

随便找一条边破环为链,然后把整棵基环树复制一遍接到末尾,如果过 \(x\to y\) 路径过环边 \(p\to fa_{p}\),就拆成 \(x\to p\)\(x'\to y\),容易证明此时覆盖每条路径的棋子集合不变。

时间复杂度 \(\mathcal O((n+m)\log n)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e5+5;
struct SegmentTree {
	int tot,ls[MAXN*20],rs[MAXN*20],mx[MAXN*20],ct[MAXN*20];
	void psu(int p) {
		ct[p]=ct[ls[p]]+ct[rs[p]],mx[p]=max(mx[ls[p]]+ct[rs[p]],mx[rs[p]]);
	}
	void ins(int u,int op,int l,int r,int &p) {
		if(!p) p=++tot;
		if(l==r) return ct[p]+=op,mx[p]=(ct[p]?l+ct[p]:0),void();
		int mid=(l+r)>>1;
		u<=mid?ins(u,op,l,mid,ls[p]):ins(u,op,mid+1,r,rs[p]);
		psu(p);
	}
	void merge(int l,int r,int q,int &p) {
		if(!q||!p) return p|=q,void();
		if(l==r) return ct[p]+=ct[q],mx[p]=(ct[p]?l+ct[p]:0),void();
		int mid=(l+r)>>1;
		merge(l,mid,ls[q],ls[p]),merge(mid+1,r,rs[q],rs[p]);
		psu(p);
	}
}	T;
vector <int> G[MAXN],D[MAXN];
int n,m,fa[MAXN],dsu[MAXN],tp[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
int dcnt,dfn[MAXN],rdn[MAXN],dep[MAXN];
void dfs1(int u) {
	dfn[u]=++dcnt;
	for(int v:G[u]) dep[v]=dep[u]+1,tp[v]=u?tp[u]:v,dfs1(v);
	rdn[u]=dcnt;
}
int op[MAXN],ans,rt[MAXN];
void ins(int x,int y) { ++op[y],D[x].push_back(dep[y]); }
void dfs2(int u) {
	for(int v:G[u]) dfs2(v),T.merge(0,2*n,rt[v],rt[u]);
	T.ins(dep[u],op[u],0,2*n,rt[u]);
	for(int i:D[u]) T.ins(i,-1,0,2*n,rt[u]);
	ans=max(ans,T.mx[rt[u]]-dep[u]);
}
signed main() {
	scanf("%d",&n),iota(dsu+1,dsu+n+1,1);
	for(int i=1;i<=n;++i) scanf("%d",&fa[i]),fa[i+n]=fa[i]+n,dsu[find(i)]=find(fa[i]);
	for(int i=1;i<=n;++i) if(dsu[i]==i) {
		int x=i;
		for(;dsu[x];x=fa[x]) dsu[x]=0;
		fa[x+n]=fa[x],fa[x]=0;
	}
	for(int i=1;i<=2*n;++i) G[fa[i]].push_back(i);
	dfs1(0),scanf("%d",&m);
	for(int x,y;m--;) {
		scanf("%d%d",&x,&y);
		if(dfn[y]<=dfn[x]&&dfn[x]<=rdn[y]) ins(y,x);
		else if(dfn[y]<=dfn[x+n]&&dfn[x+n]<=rdn[y]) ins(y,x+n),ins(tp[y],x);
		else return puts("-1"),0;
	}
	dfs2(0),printf("%d\n",ans);
	return 0;
}



Round #52 - 20250215

A. 城市(city)

Problem Link

题目大意

给定 \(n\) 个点的树,每个点有颜色 \(a_1\sim a_n\),对于每种颜色的点,选择一个中心,使得每个点到与其同色的中心的距离严格小于到其他中心的距离,构造方案。

数据范围:\(n\le 2\times 10^5\)

思路分析

首先每种颜色的点肯定是要联通的,要不然不和中心再一个连通块里的点肯定不合法。

然后我们把每种颜色缩成一个块,观察两个块的交界点 \((x,y)\),设他们对应的中心为 \(c_x,c_y\)

由于 \(\mathrm{dis}(x,c_y)=\mathrm{dis}(y,c_y)+1<\mathrm{dis}(x,c_x)\)\(\mathrm{dis}(y,c_x)=\mathrm{dis}(x,c_x)+1<\mathrm{dis}(y,c_y)\)

所以 \(\mathrm{dis}(x,c_x)=\mathrm{dis}(y,c_y)\),容易发现满足了这个条件后,\(x\) 所属块的点到 \(c_x\) 的距离一定严格大于到 \(c_y\) 的距离。

因此对于每一对相邻的块,我们的中心都满足这个条件,那么这就是一组合法解。

求出合法解可以考虑 dp,即 \(f_u\) 表示 \(u\) 为该块中心时,其子树内的其他块是否可行。

对于子树内的其他块,设交点为 \((y,x)\),那么必须满足 \(\mathrm{dis}(u,t)\in \{\mathrm{dis}(y,t)\mid f_t=1,a_t=a_y\}\)

暴力检验很显然不合法,我们可以对每个块点分治,把 \(\mathrm{dis}(u,t)\) 拆成 \(d_u+d_t\),然后就能维护每个 \(u\) 对几个 \(y\) 合法了。

构造方案时从上到下枚举每个中心是否合法。

时间复杂度 \(\mathcal O(n\log n)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
vector <int> G[MAXN],E[MAXN],T[MAXN],Q[MAXN];
int n,m,a[MAXN],rt[MAXN],dep[MAXN],fa[MAXN];
int dfn[MAXN],dcnt,st[MAXN][20];
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int dist(int x,int y) {
	if(x==y) return 0;
	int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
	return dep[x]+dep[y]-2*dep[cmp(st[l][k],st[r-bit(k)+1][k])];
}
void dfs1(int u,int fz) {
	dfn[u]=++dcnt,st[dcnt][0]=fz;
	fa[u]=fz,dep[u]=dep[fz]+1;
	for(int v:G[u]) if(v^fz) dfs1(v,u);
	if(!fz||a[u]!=a[fz]) {
		if(rt[a[u]]) cout<<"NE\n",exit(0);
		rt[a[u]]=u,E[a[fz]].push_back(a[u]);
	} else T[fz].push_back(u),T[u].push_back(fz);
}
vector <int> F[MAXN],C[MAXN];
bool f[MAXN];
int siz[MAXN],cur[MAXN],dis[MAXN],dp[MAXN],rs[MAXN];
bool vis[MAXN];
void calc(vector<int>&vi,int op) {
	int mx=0;
	for(int u:vi) mx=max(mx,dis[u]);
	for(int u:vi) for(int c:C[u]) {
		for(int i=0;i<(int)F[c].size();++i) if(F[c][i]) {
			if(0<=i-dis[u]&&i-dis[u]<=mx) dp[i-dis[u]]+=op;
		}
	}
	for(int u:vi) rs[u]+=dp[dis[u]];
	for(int i=0;i<=mx;++i) dp[i]=0;
}
void solve(int u) {
	vector <int> all{u}; dis[u]=0;
	for(int v:T[u]) if(!vis[v]) {
		vector <int> sub;
		function<void(int,int)> dfs6=[&](int x,int fz) {
			dis[x]=dis[fz]+1,siz[x]=1;
			all.push_back(x),sub.push_back(x);
			for(int y:T[x]) if(!vis[y]&&y!=fz) dfs6(y,x),siz[x]+=siz[y];
		};
		dfs6(v,u),calc(sub,-1);
	}
	calc(all,1);
}
void dfs4(int u) {
	vis[u]=true,solve(u);
	for(int v:T[u]) if(!vis[v]) {
		int ct=0,tot=siz[v];
		function<void(int,int)> dfs5=[&](int x,int fz) {
			cur[x]=tot-siz[x];
			for(int y:G[x]) if(!vis[y]&&y!=fz) dfs5(y,x),cur[x]=max(cur[x],siz[y]);
			if(!ct||cur[x]<cur[ct]) ct=x;
		};
		dfs5(v,u),dfs4(ct);
	}
}
void dfs2(int u) {
	for(int v:E[u]) dfs2(v),C[fa[rt[v]]].push_back(v);
	dfs4(rt[u]);
	for(int x:Q[u]) if(rs[x]==(int)E[u].size()) {
		int d=dep[x]-dep[rt[u]]; f[x]=true;
		if((int)F[u].size()<=d) F[u].resize(d+1,0);
		F[u][d]=1;
	}
	if(F[u].empty()) cout<<"NE\n",exit(0);
}
int ans[MAXN];
void dfs3(int u,int lim) {
	for(int x:Q[u]) if(f[x]&&(lim==-1||dist(x,rt[u])==lim)) {
		ans[u]=x; break;
	}
	for(int v:E[u]) dfs3(v,dist(ans[u],fa[rt[v]]));
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],Q[a[i]].push_back(i);
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs1(1,0);
	for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
		st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
	}
	dfs2(a[1]),dfs3(a[1],-1),cout<<"DA\n";
	for(int i=1;i<=m;++i) cout<<ans[i]<<" \n"[i==m];
	return 0;
}



B. 浇水(plant)

Problem Link

题目大意

给定 \(n\) 个盒子,第 \(i\) 个机器可以花 \(w_i\) 的代价往第 \(i,i+1\) 个盒子中各放一个球,要求第 \(i\) 个盒子里有 \(\ge a_i\) 个球,对于每个 \(1<k\le n\),求出用 \(1\sim k-1\) 号机器满足 \(1\sim k\) 号盒子要求的最小代价。

数据范围:\(n\le 5\times 10^5\)

思路分析

容易设计出一个朴素的 dp,即 \(f_{i,j}\) 表示第 \(i\) 个机器使用了 \(j\) 次的最小总代价。

\(V=\max a_i\),即 \(j\) 的上界,则从 \(f_{i-1}\to f_i\) 的转移步骤如下:

  • \(f_{a_i}\) 变成 \(\min_{k\ge a_i} f_{k}\)\(f_{a_i+1}\sim f_{V}\) 设为 \(\infty\),然后翻转 \(f_0\sim f_{a_i}\)

    此时 \(f_j\) 表示还剩 \(j\) 个球没放的最小代价,答案就是此时的 \(f_0\)

  • \(f_j\gets \min_{k\le j} f_k\),然后 \(f_j\gets f_j+j\times w_i\)

那么感性理解 \(f\) 是具有凸性的,观察序列的差分数组,我们首先要弹出若干末尾元素,然后翻转序列并把符号取反,再把元素和 \(0\)\(\min\) 后全局 \(+w_i\)

容易发现差分数组始终单调递增。

那么尝试直接维护差分数组和首项,每次维护首项,相当于维护 \(\min_{k\ge a_i} f_k\),从末项开始减去所有 \(>0\) 的差分值即可。

那么剩下的就是动态维护这个数组,可以发现弹出位置 \(>a_i\) 的差分值和值 \(>0\) 的差分值一定删掉了一个后缀,而这个后缀最终的差分值都会变成 \(w_i\)

所以我们的操作相当于后缀推平,可以颜色段均摊,用一个栈暴力维护所有极长相等的差分值,每次操作末尾。

但我们还需要进行序列翻转、符号取反、全局加三种操作。

后两种容易用懒标记实现,第一种用双端队列代替栈即可。

时间复杂度 \(\mathcal O(n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
const ll inf=1e18;
int n,a[MAXN],w[MAXN];
struct info {
	int len; ll val;
};
deque <info> dp;
bool rev=0;
ll mul=1,add=0;
info qry() {
	auto it=rev?dp.front():dp.back();
	return {it.len,it.val*mul+add};
}
void pop() { rev?dp.pop_front():dp.pop_back(); }
void push(info it) {
	if(it.len>0) {
		it.val=(it.val-add)/mul;
		rev?dp.push_front(it):dp.push_back(it);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<n;++i) cin>>w[i];
	int mx=*max_element(a+1,a+n+1);
	ll Z=1ll*max(a[1],a[2])*w[1],sm=0;
	if(a[2]<=a[1]) {
		sm=1ll*mx*w[2];
		push({mx,w[2]});
	} else {
		push({a[2]-a[1],w[2]-w[1]});
		push({mx-(a[2]-a[1]),w[2]});
		sm=1ll*(a[2]-a[1])*(w[2]-w[1])+1ll*w[2]*(mx-(a[2]-a[1]));
	}
	cout<<Z<<"\n";
	for(int i=3;i<=n;++i) {
		ll nw=Z+sm;
		for(int t=mx-a[i];t;) {
			auto it=qry();
			if(it.len<=t) {
				sm-=it.len*it.val,nw-=it.len*max(0ll,it.val);
				t-=it.len,pop();
			} else {
				sm-=t*it.val,nw-=t*max(0ll,it.val);
				if(rev) dp.front().len-=t;
				else dp.back().len-=t;
				t=0;
			}
		}
		rev^=1,mul*=-1,add*=-1,sm*=-1,Z=nw,cout<<Z<<"\n";
		if(i==n) break;
		int dl=mx-a[i];
		while(dp.size()&&qry().val>0) {
			auto it=qry();
			sm-=it.val*it.len,dl+=it.len,pop();
		}
		push({dl,0}),add+=w[i],sm+=1ll*mx*w[i];
	}
	return 0;
}



*C. 比赛(match)

Problem Link

题目大意

给定长度为 \(n\) 的环,把 \(1\sim n\) 填入这个环,每个元素有一个颜色,如果相邻两个元素异色,那么编号较大的元素所属的颜色得分 \(+1\)

特别的,如果某个蓝色元素顺时针下一个元素是红色的,且红色元素权值较大,则这种情况不会让红色得分 \(+1\)

对于 \(k\in[-n,n]\),计数有多少个有圆排列,使得红色得分比蓝色得分多 \(k\)

数据范围:\(n\le 3000\)

思路分析

首先观察,用 \(w_{R<B},w_{R>B},w_{B<R},w_{B>R}\) 分别表示顺时针方向上异色元素对的四种情况。

那么权值就是是 \(w_{R>B}-w_{B>R}-w_{R<B}\),由于 \(w_{R>B}+w_{R<B}=w_{B>R}+w_{B<R}\),因此权值可以写成 \(w_{B<R}-2w_{R<B}\)

此时我们只要考虑顺时针方向上的点权值较大的情况,那么可以从最小值处破环为链。

这种只关心相邻元素大小关系的问题可以考虑插入 dp 或容斥。

插入 dp 的状态数显然无法接受,因此考虑容斥,即二项式反演。

此时我们钦定 \(x\) 对相邻元素满足 \(B<R\)\(y\) 对相邻元素满足 \(R<B\)

设方案数为 \(f_{x,y}\),然后做二元二项式反演,即对每一维分别反演,得到的 \(f'_{x,y}\) 就是恰好 \(w_{B<R}=x,w_{R<B}=y\) 的方案数。

我们只要计数 \(f_{x,y}\) 即可。

这种问题可以从两种视角看:一种是由这些钦定的边构成的极长连续段的视角,另一种是直接考虑每条边确定了哪些元素的前驱后继。

这题上考虑连续段显然过于困,因此选择视角二。

那么对于这 \(x+y\) 条边,我们直接确定每条边是由哪两个元素构成的,那么相当于在排列上确定了若干个元素的前驱后继。

只要每个点的前驱后继不重复,那么就会形成若干条链,贡献就是链数的阶乘。

我们注意到 \(B<R\) 的边只确定红色元素的前驱,\(R<B\) 的边只确定红色元素的后继,蓝色元素同理。

那么这两种边分别去一个选择元素不重复的解,他们之间一定不冲突。

那么我们只需要计算选出 \(x\)\(B<R\) 的边的方案数,以及 \(y\)\(R<B\) 的边的方案数,此时这 \(x+y\) 条边一定互不矛盾,把整个排列变成 \(n-x-y\) 条链。

由于我们钦定最小值在最前面,那么对答案的贡献就是 \((n-x-y-1)!\)

这样就求出了所有 \(f_{x,y}\),计算出 \(f'_{x,y}\) 只要对每个 \(x,y\) 分别二项式反演,可以用 NTT 优化。

时间复杂度 \(\mathcal O(n^2\log n)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<13,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
	int ret=1;
	for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
	return ret;
}
namespace P {
void poly_init() {
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
	for(int k=1;k<=N;k<<=1) {
		int x=ksm(G,(MOD-1)/k); w[k]=1;
		for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
	}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y;  }
void ntt(int *f,bool idft,int n) {
	for(int i=0;i<n;++i) {
		rev[i]=(rev[i>>1]>>1);
		if(i&1) rev[i]|=n>>1;
	}
	for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
	for(int k=2,x,y;k<=n;k<<=1) {
		for(int i=0;i<n;i+=k) {
			for(int j=i;j<i+k/2;++j) {
				x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
				f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
			}
		}
	}
	if(idft) {
		reverse(f+1,f+n);
		for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
	}
}
}
int C(int x,int y) {
	return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,fb[N],fr[N],f[N][N],g[N],ans[N],c[N];
char str[N];
//ans = B<R-2R<B, fb=BR, fr=RB
void INV(int *a) {
	static int b[N];
	memset(b,0,sizeof(b));
	for(int i=0;i<=n;++i) b[i]=1ll*a[i]*fac[i]%MOD;
	P::ntt(b,0,N);
	for(int i=0;i<N;++i) b[i]=1ll*b[i]*c[i]%MOD;
	P::ntt(b,1,N);
	for(int i=0;i<=n;++i) a[i]=1ll*b[i+n]*ifac[i]%MOD;
}
signed main() {
	P::poly_init();
	scanf("%d%s",&n,str+1);
	fb[0]=fr[0]=1;
	for(int i=1,cb=0,cr=0;i<=n;++i) {
		if(str[i]=='R') {
			for(int j=cb;j;--j) fb[j]=(fb[j]+1ll*fb[j-1]*(cb+1-j))%MOD;
			++cr;
		} else {
			for(int j=cr;j;--j) fr[j]=(fr[j]+1ll*fr[j-1]*(cr+1-j))%MOD;
			++cb;
		}
	}
	for(int i=0;i<=n;++i) c[n-i]=(i&1?MOD-ifac[i]:ifac[i]);
	P::ntt(c,0,N);
	for(int x=0;x<=n;++x) for(int y=0;x+y<n;++y) f[x][y]=(f[x][y]+1ll*fb[x]*fr[y]%MOD*fac[n-1-x-y])%MOD;
	for(int x=0;x<=n;++x) INV(f[x]);
	for(int y=0;y<=n;++y) {
		memset(g,0,sizeof(g));
		for(int x=0;x<=n;++x) g[x]=f[x][y];
		INV(g);
		for(int x=0;x<=n;++x) ans[x+n-2*y]=(ans[x+n-2*y]+g[x])%MOD;
	}
	for(int i=0;i<=2*n;++i) printf("%d ",ans[i]); puts("");
	return 0;
}



Round #53 - 20250217

A. 绝顶之战(war)

Problem Link

题目大意

给定长度为 \(m\) 的数轴,依次放入长度为 \(a_1\sim a_n\) 的物品,如果能放入,可以选择任意一个位置,但必须插入,求最终放入序列的物品总长有多少种可能。

数据范围:\(n\le 14\)

思路分析

先写一个朴素的暴力,枚举放入的物品集合 \(S\),然后暴力枚举所有物品在数轴上的顺序。

考虑过程中产生的每个极长的空位,然后按插入时间建立小根笛卡尔树,那么每个空位都对应笛卡尔树上的一个子树。

我们可以自下而上简单算出每个子树对应的空位的最大长度,如果放不下子树内所有的物品,或者整棵树的最大长度 \(<m\) 就无解。

具体来说,对于笛卡尔树上的点 \(p\),其子树最大长度 \(f_p\le f_{ls}+f_{rs}+a_p\),并且对于所有 \([1,p)\) 中的未插入元素 \(k\)\(f_p\le a_k\)

那么我们不用暴力枚举排列,直接对笛卡尔树形态 dp,即 \(f_s\) 表示 \(s\) 中的点作为笛卡尔树的一个子树时,长度最大是多少,很显然此时根就是 \(\min(s)\),转移再枚举两个子集 \(t,s\setminus t\) 作为子树即可。

此时我们相当于枚举 \(t\subseteq s\subseteq S\subseteq U\),复杂度 \(\mathcal O(4^n)\)

时间复杂度 \(\mathcal O(n3^n+4^n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
int n,b[20];
ll m,a[20],f[1<<14],w[1<<14];
bool chk(int S) {
	int t=0;
	for(int i=n-1;~i;--i) if(S>>i&1) b[t++]=i;
	f[0]=m+1;
	for(int i=n-1;~i;--i) if(!(S>>i&1)) f[0]=min(f[0],a[i]);
	for(int s=1;s<(1<<t);++s) {
		int x=31-__builtin_clz(s),id=0;
		for(int i=0;i<t;++i) if(s>>i&1) id|=1<<b[i];
		f[s]=0;
		for(int o=s^(1<<x),q=o;;q=(q-1)&o) {
			f[s]=max(f[s],f[q]+f[q^o]);
			if(!q) break;
		}
		f[s]=min(f[s]+a[b[x]],m+1);
		for(int j=b[x]-1;~j;--j) if(!(S>>j&1)) f[s]=min(f[s],a[j]);
		if(f[s]<=w[id]) f[s]=-inf;
	}
	return f[(1<<t)-1]>m;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>m>>n; for(int i=0;i<n;++i) cin>>a[i];
	for(int s=0;s<(1<<n);++s) for(int i=0;i<n;++i) if(s>>i&1) w[s]+=a[i];
	vector <ll> z;
	for(int s=1;s<(1<<n);++s) if((s&1)&&chk(s)) z.push_back(w[s]);
	sort(z.begin(),z.end());
	z.erase(unique(z.begin(),z.end()),z.end());
	cout<<z.size()<<"\n";
	for(ll i:z) cout<<i<<" "; cout<<"\n";
	return 0;
}



B. 冲击波(wave)

Problem Link

题目大意

给定 \(a_1\sim a_n\),每次操作可以选择 \(x\in[1,n]\),然后令每个 \(a_i\) 减去 \(|i-x|\),求让所有 \(a_i\le 0\) 的最小代价。

数据范围:\(n\le 10^5\)

思路分析

首先注意到操作 \(1<x\le y<n\) 一定不如操作 \(x-1,y+1\)

那么我们不断调整,最终 \((1,n)\) 中的操作只有 \(\le 1\) 个。

先考虑全部在 \(1,n\) 上操作的情况,二分答案 \(z\),并假设有 \(k\) 个操作在 \(1\) 上,则 \(k(i-1)+(n-i)(z-k)\ge a_i\),化简得到 \((2i-n-1)k\ge a_i-(n-i)z\)

简记为 \(d_ik\ge s_i\),则我们求出所有 \(k\) 解集的交,如果非空说明合法。

可以 \(\mathcal O(n\log V)\) 求解,枚举 \((1,n)\) 中的操作 \(x\),然后分别二分,可以做到 \(\mathcal O(n^2\log V)\)

注意到如果我们在 \((1,n)\) 中操作了 \(x\),那么把这个操作替换成 \(1,n\) 两次操作一定也合法。

因此假设 \(z_0\) 是不操作 \((1,n)\) 的最优解,\(z_1\) 是操作 \((1,n)\) 的最优解,则 \(z_1+1\le z_0\)

那么我们只要判断是否有 \(z_1=z_0-1\),也就是操作 \(x\) 后剩下的数是否能在 \(z_0-2\) 次操作内完成,复杂度 \(\mathcal O(n^2)\)

对每个 \(x\),我们统一维护 \(k\) 的上下界 \(l_x,r_x\)

对于 \(d_i>0\) 的点,我们会让 \(r_x\)\(\left\lfloor\dfrac{s_i-|i-x|}{d_i}\right\rfloor\)\(\min\)

可以发现这个函数可以分成 \(\mathcal O\left(\dfrac{n}{d_i}\right)\) 段,而 \(d\)\(n-1,n-3,\dots\),那么总段数均摊 \(\mathcal O(n\log n)\),对于 \(d_i<0\) 的点也是类似的。

那么我们只要支持快速区间 chkmin,并且最终求出所有 \(r_x\)

可以用类似 ST 表的结构,\(f(i,k)\) 表示对 \(r[i,i+2^k)\) 的修改,求答案的时候按 \(k\) 从大到小下放标记,可以做到 \(\mathcal O(1)-\mathcal O(n\log n)\)

时间复杂度 \(\mathcal O(n\log V+n\log n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline void chkmin(ll &x,const ll &y) { x=y<x?y:x; }
inline void chkmax(ll &x,const ll &y) { x=y>x?y:x; }
const int MAXN=1e5+5;
int n;
ll a[MAXN];
bool chk(ll z) {
	ll l=0,r=z;
	for(int i=1;i<=n;++i) {
		ll s=a[i]-(n-i)*z,d=2*i-n-1; //dk>=s
		if(d==0) {
			if(s>0) return false;
		}
		else if(d<0) {
			if(s<=0) r=min(r,(-s)/(-d));
			else return false;
		} else {
			if(s>=0) l=max(l,(s+d-1)/d);
		}
		if(l>r) return false;
	}
	return true;
}
ll sol() {
	ll l=0,r=(*max_element(a+1,a+n+1)+n-2)/(n-1)*2,p=r;
	while(l<=r) {
		ll mid=(l+r)>>1;
		if(chk(mid)) p=mid,r=mid-1;
		else l=mid+1;
	}
	return p;
}
ll fl[20][MAXN],fr[20][MAXN];
int bit(int x) { return 1<<x; }
void chkL(int l,int r,ll z) {
	int k=__lg(r-l+1);
	chkmax(fl[k][l],z),chkmax(fl[k][r-bit(k)+1],z);
}
void chkR(int l,int r,ll z) {
	int k=__lg(r-l+1);
	chkmin(fr[k][l],z),chkmin(fr[k][r-bit(k)+1],z);
}
int findL(int x,int d,int r) {
	int z=x/d*d+r;
	return max(1,z>x?z-d:z);
}
int findR(int x,int d,int r) {
	int z=x/d*d+r;
	return min(z<x?z+d:z,n);
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	ll ans=sol();
	if(ans<2) return cout<<ans<<"\n",void();
	ll z=ans-2,*L=fl[0],*R=fr[0];
	for(int x=1;x<=n;++x) for(int k=0;k<20;++k) fl[k][x]=0,fr[k][x]=z;
	for(int i=1;i<=n;++i) {
		ll s=a[i]-(n-i)*z,d=2*i-n-1; //dk>=s-|i-x|
		if(d==0) {
			if(s>0) for(int x=1;x<=n;++x) if(abs(i-x)<s) R[x]=-1;
		} else if(d<0) {
			d*=-1;
			for(int p=i,q,r=((i-s+1)%d+d)%d;p>=1;p=q-1) {
				q=findL(p,d,r);
				chkR(q,p,i-p<s?-1:(i-p-s)/d);
			}
			for(int p=i,q,r=((i+s-1)%d+d)%d;p<=n;p=q+1) {
				q=findR(p,d,r);
				chkR(p,q,p-i<s?-1:(p-i-s)/d);
			}
		} else {
			for(int p=i,q,r=(d-(s-i+d-1)%d)%d;p>=1;p=q-1) {
				q=findL(p,d,r);
				if(i-p<=s) chkL(q,p,(s-i+p+d-1)/d);
			}
			for(int p=i,q,r=((s+i+d-1)%d+d)%d;p<=n;p=q+1) {
				q=findR(p,d,r);
				if(p-i<=s) chkL(p,q,(s-p+i+d-1)/d);
			}
		}
	}
	for(int k=19;k;--k) for(int i=1;i+bit(k)-1<=n;++i) {
		chkmax(fl[k-1][i],fl[k][i]),chkmax(fl[k-1][i+bit(k-1)],fl[k][i]);
		chkmin(fr[k-1][i],fr[k][i]),chkmin(fr[k-1][i+bit(k-1)],fr[k][i]);
	}
	for(int x=1;x<=n;++x) if(L[x]<=R[x]) return cout<<ans-1<<"\n",void();
	cout<<ans<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*C. 安全系统(security)

Problem Link

题目大意

\(n\times n\) 网格中给定 \(n\) 条射线,选出若干条不相交射线使得其权值最大。

数据范围:\(n\le 1500\)

思路分析

分类讨论,如果横向射线没有完整覆盖 \([1,n]\),那么网格可以分成三部分:左部由左、上、下三种方向的射线构成,中部由上、下两种方向的射线构成,右部由右、上、下方向的射线构成。

而左部又可以分成由左、上射线构成的左上区域,以及右、下射线构成的右下区域。

那么这种情况全部能简单 dp 解决,那么还剩下的情况就是横纵向射线分别覆盖 \([1,n]\)

相当于把矩形分成九个部分 \(\begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix}\),然后 \(1,4\) 用左、上射线覆盖,\(7,8\) 用左、下射线覆盖,\(6,9\) 用右、下射线覆盖,\(2,3\) 用右、上射线覆盖。

暴力枚举四条分界线是不可能的,但我们枚举上、下的分界线,然后左右的分界线权值独立,可以分别处理,做到 \(\mathcal O(n^3)\)

注意到一定有一条向下的射线顶到 \(6\) 的上边界,同理有一条向上的射线顶到 \(4\) 的下边界。

那么我们只要枚举上、下的射线即可,如果上下射线数乘积大于左右射线数乘积,就翻转矩阵,这样就能带来 \(\dfrac 1{16}\) 的常数,足够通过。

时间复杂度 \(\mathcal O(n^3)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1505;
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
int n,x[MAXN],y[MAXN],d[MAXN],w[MAXN],ans=0;
int s[4][MAXN][MAXN],f[4][MAXN][MAXN],g[MAXN];
vector <int> a[MAXN];
int max_level(vector<int>X,vector<int>Y,vector<int>D,vector<int>W) {
	n=X.size();
	for(int i=0;i<n;++i) x[i]=X[i],y[i]=Y[i],d[i]=D[i]-1,w[i]=W[i],a[d[i]].push_back(i);
	if(a[0].size()*a[2].size()>a[1].size()*a[3].size()) {
		for(int i=0;i<n;++i) d[i]^=1,swap(x[i],y[i]);
		swap(a[0],a[1]),swap(a[2],a[3]);
	}
	for(int i=0;i<n;++i) chkmax(s[d[i]][x[i]][y[i]],w[i]);
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
		chkmax(s[2][i][j],s[2][i][j-1]);
		chkmax(s[3][i][j],s[3][i-1][j]);
	}
	for(int i=n;i>=1;--i) for(int j=n;j>=1;--j) {
		chkmax(s[0][i][j],s[0][i][j+1]);
		chkmax(s[1][i][j],s[1][i+1][j]);
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) {
			f[0][i][j]=max(f[0][i-1][j]+s[2][i][j],f[0][i][j-1]+s[3][i][j]);
		}
		for(int j=n;j>=1;--j) {
			f[1][i][j]=max(f[1][i-1][j]+s[0][i][j],f[1][i][j+1]+s[3][i][j]);
		}
	}
	for(int i=n;i>=1;--i) {
		for(int j=1;j<=n;++j) {
			f[2][i][j]=max(f[2][i+1][j]+s[2][i][j],f[2][i][j-1]+s[1][i][j]);
		}
		for(int j=n;j>=1;--j) {
			f[3][i][j]=max(f[3][i+1][j]+s[0][i][j],f[3][i][j+1]+s[1][i][j]);
		}
	}
	for(int i=1,mx=0;i<=n;++i) {
		for(int j=0;j<=n;++j) chkmax(ans,mx+f[3][i][j+1]+f[2][i][j]);
		int ht=0;
		for(int j=0;j<=n;++j) chkmax(ht,s[0][i][j+1]+s[2][i][j]);
		mx+=ht;
		for(int j=0;j<=n;++j) chkmax(mx,f[1][i][j+1]+f[0][i][j]);
		if(i==n) chkmax(ans,mx);
	}
	for(int j=1,mx=0;j<=n;++j) {
		for(int i=0;i<=n;++i) chkmax(ans,mx+f[3][i+1][j]+f[1][i][j]);
		int ht=0;
		for(int i=0;i<=n;++i) chkmax(ht,s[1][i+1][j]+s[3][i][j]);
		mx+=ht;
		for(int i=0;i<=n;++i) chkmax(mx,f[2][i+1][j]+f[0][i][j]);
		if(j==n) chkmax(ans,mx);
	}
	auto sol=[&]() {
		for(int i:a[0]) for(int j:a[2]) if(x[j]-x[i]>1&&y[i]<=y[j]) {
			int l=x[i]+1,r=x[j]-1; g[r+1]=0;
			for(int k=r;k>=l;--k) g[k]=max(g[k+1],f[0][k][y[i]-1]+f[2][k+1][y[j]]);
			for(int k:a[1]) if(l<=x[k]&&x[k]<=r&&y[k]>y[j]) {
				chkmax(ans,g[x[k]]+f[1][x[k]-1][y[i]]+f[3][x[k]][y[j]+1]);
			}
		}
	}; sol();
	for(int i=0;i<n;++i) y[i]=n-y[i]+1;
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
		swap(f[0][i][j],f[1][i][n-j+1]),swap(f[2][i][j],f[3][i][n-j+1]);
	}
	swap(a[0],a[2]),sol();
	return ans;
}



Round #54 - 20250220

A. 鱼(fish)

Problem Link

题目大意

定义一个序列的权值为,用单点 \(+d\) 和后缀 \(+1\) 构造这个序列时,第一种操作的最小操作次数。

给定 \(a_1\sim a_n\)\(q\) 次询问 \(a[l,r]\) 的权值。

数据范围:\(n,q\le 3\times 10^5\)

思路分析

可以看成进行尽可能少的单点 \(-d\) 使得这个序列递增,一个简单贪心就是倒着确定序列,贪心地取合法的最大值。

假设 \(a_{i+1}\) 上操作了 \(k_{i+1}\) 次,那么 \(a_i\) 上的操作次数 \(k_i=\max\left(0,\left\lceil\dfrac{a_i-(a_{i+1}-k_{i+1}d)}{d}\right\rceil\right)=\max\left(0,\left\lceil\dfrac{a_i-a_{i+1}}{d}\right\rceil+k_{i+1}\right)\)

\(c_i=\left\lceil\dfrac{a_i-a_{i+1}}{d}\right\rceil\),那么我们就要求 \(k_i=\max(0,c_i+k_{i+1})\) 得到的 \(k_{l}\sim k_r\) 之和。

如果 \(c_i+k_{i+1}\) 总是 \(\ge 0\),那么计算权值是平凡的,因此们以 \(k_i=0\) 的时刻进行分割。

用二分求出 \(k_r=0\) 时下一个 \(k_i=0\) 的点,用倍增加速这个过程。

如果 \(dk_i>a_i\) 答案是 \(-1\),判定可以用 ST 表,合并答案的时候注意一下 \(-1\) 的处理即可。

时间复杂度 \(\mathcal O((n+q)\log n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const int MAXN=3e5+5;
int n,q,fa[MAXN][20];
ll a[MAXN],c[MAXN],S[MAXN],d;
ll st[MAXN][20],w[MAXN][20],vl[MAXN][20];
LL sum[MAXN];
int bit(int x) { return 1<<x; }
ll qmn(auto f,int l,int r) {
	int k=__lg(r-l+1);
	return min(f[l][k],f[r-bit(k)+1][k]);
}
ll val(int l,int r) {
	if(qmn(vl,l,r)<-S[r]*d) return -1;
	return sum[l]-sum[r]-(LL)S[r]*(r-l);
}
ll add(ll x,ll y) { return ~x&&~y?x+y:-1; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>d;
	for(int i=1;i<=n;++i) cin>>a[i];
	ll K=*max_element(a+1,a+n+1)/d+1;
	for(int i=n-1;i>=1;--i) {
		c[i]=(a[i]-a[i+1]+K*d+d-1)/d-K;
		S[i]=S[i+1]+c[i],st[i][0]=S[i],vl[i][0]=a[i]-S[i]*d;
		sum[i]=sum[i+1]+S[i];
	}
	for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
		st[i][k]=min(st[i][k-1],st[i+bit(k-1)][k-1]);
		vl[i][k]=min(vl[i][k-1],vl[i+bit(k-1)][k-1]);
	}
	for(int i=1;i<=n;++i) {
		int l=1,r=i,p=i;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(qmn(st,mid,i)-S[i]>=0) p=mid,r=mid-1;
			else l=mid+1;
		}
		fa[i][0]=p-1,w[i][0]=val(p,i);
		for(int k=1;k<20;++k) {
			fa[i][k]=fa[fa[i][k-1]][k-1];
			w[i][k]=add(w[i][k-1],w[fa[i][k-1]][k-1]);
		}
	}
	cin>>q;
	for(int l,r;q--;) {
		cin>>l>>r;
		int p=r; ll s=0;
		for(int k=19;~k;--k) if(fa[p][k]>=l) s=add(s,w[p][k]),p=fa[p][k];
		s=add(s,val(l,p));
		cout<<s<<"\n";
	}
	return 0;
}



*B. 菜园(garden)

Problem Link

题目大意

给定序列 \(a_1\sim a_{2n}\),满足 \(a_1\sim a_{n+1}\) 递增,\(a_{n+1}\sim a_{2n}\) 递减。

把每个元素匹配到 \(b_1\sim b_n\)\(c_1\sim c_n\) 上,要求存在一个长度为 \(n\) 的子区间上每个元素都匹配到 \(b\) 上或都匹配到 \(c\) 上。

最小化每对匹配的元素差的最大值。

数据范围:\(n\le 3\times 10^5\)

思路分析

先考虑暴力,枚举哪个区间填 \(b/c\),然后把两个要匹配的序列都升序排列,对应位置的元素匹配即可。

先二分答案,把 \(a\) 放到环上,对于每个长度为 \(n\) 的弧,判定这些元素能否和 \(b\) 匹配,计算出所有和 \(b,c\) 可匹配的弧即可判定答案。

那么每个 \(a_i\) 可以匹配的元素是一段区间 \(b[l_i,r_i]\),一个过 \(i\) 的弧 \([j,j+n)\) 合法当且仅当 \(\le a_i\) 的元素个数 \(\in[l_i,r_i]\)(需要给相等元素钦定顺序)。

注意到 \(\le a_i\) 的元素一定是原序列的一段前缀和一段后缀,那么 \([j,j+n)\) 和这个范围的交集大小不难直接写出。

这是一份暴力检验的代码:Submission Link

注意到对于每个 \(i\),合法的 \(j\) 都是 \(\mathcal O(1)\) 个区间,分类讨论求得这些区间即可。

时间复杂度 \(\mathcal O(n\log V)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+5,inf=1e9;
int n,a[MAXN],ord[MAXN],d[MAXN],L[MAXN],R[MAXN];
bool in(int x,int l,int r) { return l<=x&&x<=r; } 
void solve(int lim,int *b,int *f) {
	for(int o=1,p=1,q=1;o<=2*n;++o) {
		int i=ord[o];
		while(p<=n&&b[p]<a[i]-lim) ++p;
		while(q<=n&&b[q]<=a[i]+lim) ++q;
		L[i]=p,R[i]=q-1;
	}
	auto add=[&](int l,int r) { if(l<=r) ++f[l],--f[r+1]; };
	for(int i=1,x;i<=n;++i) if(L[i]<=R[i]) {
		x=min(i,d[i]-n);
		if(in(n+i+1-d[i],L[i],R[i])) add(x+1,i);
		add(max(1,i-R[i]+1),min(x,i-L[i]+1));
		x=max(d[i],n+i+1);
		if(in(2*n+1-d[i]+i,L[i],R[i])) add(n+i+1,x-1);
		add(max(x,2*n+1+i-R[i]),min(2*n,2*n+1+i-L[i]));
	}
	for(int i=n+1,x;i<=2*n;++i) if(L[i]<=R[i]) {
		x=min(d[i]+n+1,i);
		add(max(n+1,L[i]-n+i),min(x,R[i]-n+i));
		if(in(2*n-i+1+d[i],L[i],R[i])) add(x+1,i);
		x=max(d[i]+1,i-n+1);
		if(in(n-i+d[i]+1,L[i],R[i])) add(i-n+1,x-1);
		add(max(x,L[i]-n+i),min(n,R[i]-n+i));
	}
}
int b[MAXN],c[MAXN],f[MAXN],g[MAXN];
bool chk(int lim) {
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	solve(lim,b,f),solve(lim,c,g);
	for(int i=1;i<=2*n;++i) f[i]+=f[i-1],g[i]+=g[i-1];
	for(int i=1;i<=n;++i) {
		if(f[i]==n&&g[i+n]==n) return true;
		if(g[i]==n&&f[i+n]==n) return true;
	}
	return false;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=2*n;++i) cin>>a[i],ord[i]=i;
	for(int i=1;i<=n;++i) cin>>b[i];
	for(int i=1;i<=n;++i) cin>>c[i];
	sort(ord+1,ord+2*n+1,[&](int x,int y){ return a[x]<a[y]; });
	sort(b+1,b+n+1),sort(c+1,c+n+1);
	for(int i=1,j=2*n;i<=n;++i) {
		while(a[j]<a[i]) d[j--]=i-1;
		d[i]=j+1;
		if(i==n) while(j>n) d[j--]=i;
	}
	int l=0,r=inf,p=inf;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(chk(mid)) p=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<p<<"\n";
	return 0;
}



*C. 中暑(fever)

Problem Link

题目大意

给定 \(n\) 个盒子,第 \(i\) 个盒子容量为 \(a_i\),然后依次给出 \(m\) 个球,第 \(i\) 个球可以放到 \(p_i\)\(p_i+1\) 号盒子,如果两个盒子都满了就丢弃,求最多能丢弃多少个球。

数据范围:\(n,m\le 8000\)

思路分析

首先我们枚举每个盒子在哪个时刻满了,记为 \(t_i\),那么一个球被丢弃当且仅当 \(i>\max(t_{p_i},t_{p_i+1})\)

考虑什么样的一组 \(t_i\) 是合法的。

可以用二分图最大匹配问题刻画,左部是所有盒子的容量,右部连接能放到这个盒子里的球。

用 Hall 定理判定,显然我们只要考虑若干连续的盒子 \([l,r]\),能放到这些盒子里的球个数必须 \(\ge\sum_{i=l}^r a_i\)

可以把这个问题看成一个类似最小子段和的问题,动态维护后缀最小值就能判定。

那么就有一个朴素 dp:\(dp_{i,j,k}\) 表示前 \(i\) 个盒子,\(t_i=j\),且当前后缀最小值为 \(k\) 的方案数。

注意到 \(j\) 一定是某个 \(p_x\in\{j-1,j\}\)\(x\),设这样的 \(x\) 总数为 \(s_i\),则 \(k\le s_i-a_i\)

所以状态总数 \(\le \sum s_i^2=\mathcal O(m^2)\)

转移时就枚举 \(j'\) 算出 \((j,k)\to (j',k')\) 的系数,此时复杂度 \(\mathcal O(m^3)\)

注意特殊处理 \(j=\infty\) 的情况,此时这个点不在二分图中,不能考虑过这个点的区间。

判掉这种特殊情况,发现转移只在 \(j'<j\)\(j\le j'\) 两种情况有较大区别,对于这两部分都能轻松地优化到 \(\mathcal O(m^2)\)

时间复杂度 \(\mathcal O(nm+m^2)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=8005,inf=1e9;
int n,a[MAXN],m,p[MAXN],s[MAXN],ct[MAXN],w[MAXN];
vector <int> b[MAXN];
vector <vector<int>> dp,f,g,nw;
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i],b[i].push_back(0);
	cin>>m;
	for(int i=1;i<=m;++i) cin>>p[i],b[p[i]].push_back(i),b[p[i]+1].push_back(i);
	for(int i=1;i<=n;++i) s[i]=b[i].size()-1,a[i]=min(a[i],s[i]);
	dp=vector<vector<int>>(s[1]-a[1]+1,vector<int>(s[1]-a[1]+1,-inf));
	for(int j=a[1];j<=s[1];++j) dp[j-a[1]][j-a[1]]=0;
	for(int i=2;i<=n;++i) {
		for(int j=1;j<=m;++j) ct[j]=ct[j-1]+(p[j]==i-1);
		for(int j=a[i-1];j<=s[i-1];++j) {
			w[j]=upper_bound(b[i].begin(),b[i].end(),b[i-1][j])-b[i].begin();
			w[j]=min(max(w[j],a[i]),s[i]);
		}
		nw=g=vector<vector<int>>(s[i]-a[i]+1,vector<int>(s[i]-a[i]+1,-inf));
		f=vector<vector<int>>(s[i]-a[i]+1,vector<int>(s[i-1]-a[i-1]+1,-inf));
		for(int j=a[i-1];j<=s[i-1];++j) for(int k=0;k<=s[i-1]-a[i-1];++k) {
			const int &z=dp[j-a[i-1]][k];
			if(z<0) continue;
			if(j==s[i-1]) { //j = inf
				for(int t=a[i];t<=s[i];++t) {
					chkmax(nw[t-a[i]][t-a[i]],z+ct[m]-ct[max(b[i-1][j],b[i][t])]);
				}
			} else {
				chkmax(nw[s[i]-a[i]][0],z+ct[m]-ct[max(b[i-1][j],b[i][s[i]])]); //j' = inf, k = any val
				if(a[i]<w[j]) chkmax(f[w[j]-a[i]-1][k],z+ct[m]-ct[b[i-1][j]]);
				if(w[j]<s[i]&&-min(0,k-ct[b[i-1][j]])<=s[i]-a[i]) {
					chkmax(g[w[j]-a[i]][-min(0,k-ct[b[i-1][j]])],z);
				}
			}
		}
		for(int t=a[i];t<s[i];++t) for(int x=0;x<=s[i]-a[i];++x) {
			if(t>a[i]) chkmax(g[t-a[i]][x],g[t-a[i]-1][x]);
			int mn=-x+t-a[i];
			if(mn>=0) chkmax(nw[t-a[i]][mn],g[t-a[i]][x]+ct[m]-ct[b[i][t]]);
		}
		for(int t=s[i]-1;t>=a[i];--t) for(int k=0;k<=s[i-1]-a[i-1];++k) {
			chkmax(f[t-a[i]][k],f[t-a[i]+1][k]);
			int mn=min(0,k-ct[b[i][t]])+t-a[i];
			if(mn>=0) chkmax(nw[t-a[i]][mn],f[t-a[i]][k]);
		}
		dp.swap(nw);
	}
	int ans=0;
	for(int j=a[n];j<=s[n];++j) for(int k=0;k<=s[n]-a[n];++k) chkmax(ans,dp[j-a[n]][k]);
	cout<<ans<<"\n";
	return 0;
}



Round #55 - 20250221

A. 塔楼(tower)

Problem Link

题目大意

给定无穷长数轴,每次从 \(x\to x+1\) 费用为 \(a\)\(x\to x+d\) 费用为 \(b\)\(n\) 个区间 \([l,r]\) 不能经过,\(q\) 次询问 \(0\to x\) 的最短路。

数据范围:\(n,q\le 2\times 10^5\)

思路分析

把数轴分为 \(n+1\) 给可以经过的区间,并且把不能到达的点删掉。

首先比较 \(a\times d\)\(b\) 的大小关系。

如果 \(a\times d\le b\),那么尽可能少用 \(+d\) 操作,很显然我们只会用 \(+d\) 操作跨越区间,那么贪心地找最靠前的能跳过来的区间转移即可。

否则我们需要用尽可能多的 \(+d\) 操作,用 \(f_x\) 表示到达 \(x\) 最多用多少次 \(+d\),如果 \(x-d\) 可达那么 \(f_x\gets f_{x-d}+1\)

因此对于连续的可达的一段,\(f\) 一定是若干个长度为 \(d\) 的区间构成,且每个区间值 \(+1\),那么计算 \(f\) 只需要二分所在段。

只有在每个区间开头的地方可能产生不同,即第一段的长度不一定 \(=d\),只需要维护这一段的长度即可。

对于区间 \([l,r]\),我们二分出第一个 \(f_x\) 使得 \(\max_{i\le x-d}f_{i}\ge f_{l-d}+1\),前缀 \(\max\) 只要找到第一个合法的 \(f_i\) 即可。

时间复杂度 \(\mathcal O(n\log^2n+q\log n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
const ll inf=1.1e18;
struct info { ll l,r; } a[MAXN],b[MAXN];
int n,m,q;
ll A,B,D,f[MAXN],d[MAXN];
int pos(ll x) {
	int l=1,r=m,p=m+1;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(x<=b[mid].r) p=mid,r=mid-1;
		else l=mid+1;
	}
	return p;
}
ll dp(ll x) {
	int p=pos(x);
	if(x<b[p].l) x=b[--p].r;
	return f[p]+(x-b[p].l)/D+((x-b[p].l)%D>=d[p]);
}
signed main() {
	cin>>n>>q>>D>>A>>B;
	for(int i=1;i<=n;++i) cin>>a[i].r>>a[i+1].l,--a[i].r,++a[i+1].l;
	a[1].l=0,a[n+1].r=inf,b[m=1]=a[1];
	for(int i=2,j=1;i<=n+1;++i) {
		while(j<=m&&b[j].r+D<a[i].l) ++j;
		if(j<=m&&b[j].l+D<=a[i].r) b[++m]={max(a[i].l,b[j].l+D),a[i].r},f[m]=f[j]+1;
	}
	b[m+1]={inf,inf};
	if(A*D<=B) {
		for(ll x;q--;) {
			cin>>x; int p=pos(x);
			cout<<(x<b[p].l?-1:A*x+f[p]*(B-A*D))<<"\n";
		}
		return 0;
	}
	f[1]=0,d[1]=D;
	for(int i=2;i<=m;++i) {
		f[i]=dp(b[i].l-D)+1,d[i]=D;
		for(ll l=b[i].l,r=min(b[i].r,b[i].l+D-1);l<=r;) {
			ll mid=(l+r)>>1;
			if(dp(mid-D)>=f[i]) d[i]=mid-b[i].l,r=mid-1;
			else l=mid+1;
		}
	}
	for(ll x;q--;) {
		cin>>x; int p=pos(x);
		cout<<(x<b[p].l?-1:A*x+dp(x)*(B-A*D))<<"\n";
	}
	return 0;
}



*B. 冒险(adventure)

Problem Link

题目大意

给定 \(n\) 个点的图,时刻 \(t\)\(u\) 节点,那么 \(t+1\) 时刻会在 \(b_{a,t\bmod a_u}\) 节点,其中 \(a_u\)\(2\) 的幂。

\(q\) 次询问 \(t\) 时刻从 \(u\) 出发走 \(d\) 步会到达哪个节点。

数据范围:\(n,\sum a_u\le 10^5,d\le 10^{18}\)

思路分析

首先肯定需要倍增,\(f_{u,t,i}\) 表示 \(t\) 时刻从 \(u\) 出发走 \(2^i\) 步的结果,但问题是如果路程中遇到 \(a_v>a_u\) 的点,\(t\) 的信息就不够了。

但我们发现此时直接切换到 \(v\) 的位置开始倍增,那么这个过程只会进行 \(\mathcal O(\log A)\) 次,因为 \(a_v\ge 2a_u\)

因此 \(f_{u,t,i}\) 表示从 \(u\) 出发走 \(2^i\) 步或到达 \(a_v>a_u\) 点的结果,\(d_{u,t,i}\) 表示实际运动的步数。

那么查询复杂度 \(\mathcal O(\log A\log T)\),但预处理时需要 \(\mathcal O(n\log T)\) 次询问,难以接受。

注意到我们的瓶颈时 \(f_{u,t,i-1}\) 可能跳到一个 \(a_v<a_u\) 的点,那么就会失去 \(t\) 的信息。

那么我们不妨改变定义,直接令 \(f_{u,t,i}\) 表示经过 \(2^i\)\(a_v=a_u\) 的点,或遇到 \(a_v>a_u\) 的点时停止。

那么询问时我们会用 \(\mathcal O(\log A)\) 轮倍增到达 \(a\) 最大的点,随后每轮倍增,剩余路径上 \(a\) 的最大值减小,因此倍增总轮数 \(\mathcal O(\log A)\)

时间复杂度 \(\mathcal O(n\log T+q\log A\log T)\)

**代码呈现 **

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
const ll inf=2e18;
int n,q,a[MAXN];
vector <int> b[MAXN],f[MAXN][64];
vector <ll> d[MAXN][64];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) {
		b[i].resize(a[i]);
		for(int &p:b[i]) cin>>p;
		for(int k=0;k<=60;++k) d[i][k].resize(a[i]),f[i][k].resize(a[i]);
	}
	for(int s=1;s<MAXN;s<<=1) {
		for(int u=1;u<=n;++u) if(a[u]==s) {
			for(int i=0;i<s;++i) {
				int v=b[u][i]; ll t=1;
				while(a[v]<s&&t<inf) {
					int w=f[v][60][(i+t)%a[v]];
					t+=d[v][60][(i+t)%a[v]],v=w;
				}
				f[u][0][i]=v,d[u][0][i]=min(t,inf);
			}
		}
		for(int k=1;k<=60;++k) for(int u=1;u<=n;++u) if(a[u]==s) {
			for(int i=0;i<s;++i) {
				int v=f[u][k-1][i]; ll t=d[u][k-1][i];
				if(a[v]==s) {
					f[u][k][i]=f[v][k-1][(i+t)%s];
					d[u][k][i]=min(d[v][k-1][(i+t)%s]+t,inf);
				} else f[u][k][i]=v,d[u][k][i]=t;
			}
		}
	}
	for(int u;q--;) {
		ll dis,cur;
		cin>>u>>cur>>dis;
		while(dis) {
			for(int k=60;~k;--k) if(dis>=d[u][k][cur%a[u]]) {
				int v=f[u][k][cur%a[u]];
				ll t=d[u][k][cur%a[u]];
				if(a[v]!=a[u]) k=61;
				dis-=t,cur+=t,u=v;
			}
			if(dis) u=b[u][cur%a[u]],--dis,++cur;
		}
		cout<<u<<"\n";
	}
	return 0;
}



*C. 硬币(coin)

Problem Link

题目大意

给定 \(n\) 堆硬币,每堆硬币由 \(m\) 个红色或蓝色的硬币组成。

现在 A 和 B 轮流取硬币,A 可以选一个红色硬币,并把该硬币及其上方的硬币取走,B 可以选一个蓝色硬币,并把该硬币及其上方的硬币取走。

一个局面是好的,当且仅当无论谁先手,后手都有必胜策略。

已知 \(k\) 堆硬币的情况,对于 \(i=[0,n-k]\),求有多少种加入 \(i\) 堆硬币的方法使得局面是好的。

数据范围:\(n\le 32,m\le 24\)

思路分析

首先感性理解,对于一个好局面,游戏结束时场上应该没有硬币,否则交换先后手时后手还能胜利。

观察可以发现,一个局面是好的,当且仅当按如下方式给每个硬币赋权后,两种硬币权值和相等:

  • 对于每堆,从堆底找到极长同色连续段,权值为 \(1\),剩余的每个硬币权值是前一个的一半。

证明如下:假设最小权值是 \(w\),那么自己的权值至少比对方多损失 \(w\),这是显然的,并且这一定能取到:找到一个这样的硬币,并且向前找到第一个自己可以取走的硬币即可。

并且由于权值总和为 \(0\),因此一定存在 \(\ge 2\) 个权值为 \(w\) 的硬币,那么根据贪心,两边都要减小自己权值的亏损,那么都会选择让自己的权值 \(-w\)

否则在某个时刻,先手操作后就不存在权值为 \(w\) 的硬币,从而先手可以保持自己的权值大于对方直到获胜。

那么我们要对这个过程计数。

给权值 \(\times 2^{m-1}\),然后从低到高考虑权值和的每一位。

对于权值和的第 \(k\) 位,当且仅当堆底同色连续段长度 \(\le k+1\) 的堆会有一个权值为 \(2^{k-(m-1)}\) 的硬币。

那么我们按 \(k\) 从小到大枚举,每次加入堆底同色连续段长度 \(=k+1\) 的堆,然后给已经加入的每个堆确定权值为 \(2^{k-(m-1)}\) 的硬币的颜色。

\(f_{k,i,j,r}\) 表示当前插入了 \(i\) 个堆,权值差是 \(2^{m-2}\times j+2^k\times r\),因为加入的每个堆的时候已经钦定了前 \(k+2\) 的硬币的颜色,所以在 \(k\ge m-2\) 的时候要特殊处理转移。

此时 \(j\in[-mn,mn]\)\(r\in[-n,n]\),状态数 \(\mathcal O(n^3m^2)\)

注意到 \(f_{k}\to f_{k+1}\) 的转移需要分三步:确定有多少形如 \(\texttt{RRR...RC}\)\(\texttt{CCC...CR}\) 的堆,然后给权值为 \(2^{k-(m-1)}\) 的硬币分配颜色。

很显然这三个步骤完全独立,那么分步转移可以做到 \(\mathcal O(n)\)

时间复杂度 \(\mathcal O(n^4m^2)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
inline void add(int &x,const ll &y) { x=(x+y)%MOD; }
int n,m,q,S,C[35][35],f[35][6155][69],g[35][6155][69];
//f[k,i,j,r]: dig = k, cnt = i, diff = 2^{m-2}*j+2^k*r
int h[35][6155],ans[35];
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m>>q,n-=q;
	const int V=2*n*m;
	for(int i=1;i<=q;++i) {
		string s; cin>>s;
		for(int j=0,k=m-1;j<m;++j) {
			if(k<m-1||s[j]!=s[0]) --k;
			S+=(s[j]=='C'?1:-1)*(1<<k);
		}
	}
	for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	S=abs(S),f[0][V][n]=1;
	if(m==1) {
		for(int i=0;i*2+S<=n;++i) ans[i*2+S]=C[i*2+S][i];
		for(int i=0;i<=n;++i) cout<<ans[i]<<" \n"[i==n];
		return 0;
	}
	for(int k=0;k<=m-2;++k) {
		for(int i=0;i<=n;++i) for(int j=-V;j<=V;++j) for(int r=-n;r<=n;++r) {
			const ll &z=f[i][j+V][r+n];
			if(!z) continue;
			int d=2*(k+1)-1; //R*(k+1)+C
			for(int x=0;i+x<=n;++x) add(g[i+x][j+x*d+V][r+n],z*C[i+x][i]);
		}
		memcpy(f,g,sizeof(f)),memset(g,0,sizeof(g));
		for(int i=0;i<=n;++i) for(int j=-V;j<=V;++j) for(int r=-n;r<=n;++r) {
			const ll &z=f[i][j+V][r+n];
			if(!z) continue;
			int d=2*(k+1)-1; //C*(k+1)+R
			for(int x=0;i+x<=n;++x) add(g[i+x][j-x*d+V][r+n],z*C[i+x][i]);
		}
		memcpy(f,g,sizeof(f)),memset(g,0,sizeof(g));
		if(k==m-2) break;
		for(int i=0,t=S>>k&1;i<=n;++i) for(int j=-V;j<=V;++j) for(int r=-n;r<=n;++r) {
			const ll &z=f[i][j+V][r+n];
			if(!z||(r+t+i)%2) continue;
			for(int x=0;x<=i;++x) add(g[i][j+V][(r+t+i-2*x)/2+n],z*C[i][x]);
		}
		memcpy(f,g,sizeof(f)),memset(g,0,sizeof(g));
	}
	for(int i=0,t=S>>(m-2)&1;i<=n;++i) for(int j=-V;j<=V;++j) for(int r=-n;r<=n;++r) {
		const ll &z=f[i][j+V][r+n];
		if(!z||(j+r+t)%2) continue;
		add(h[i][(j+r+t)/2+V],z);
	}
	for(int i=0,t=S>>(m-1);i<=n;++i) for(int j=-V;j<=V;++j) {
		const ll &z=h[i][j+V];
		if(!z||(j+t)%m) continue;
		int c=(j+t)/m;
		for(int x=0;i+2*x+c<=n;++x) add(ans[i+2*x+c],1ll*C[i+2*x+c][x]*C[i+x+c][x+c]%MOD*z);
	}
	for(int i=0;i<=n;++i) cout<<ans[i]<<" \n"[i==n];
	return 0;
}



Round #56 - 20250311

A. 安全(safety)

Problem Link

题目大意

给定 \(a_1\sim a_n\),每次操作可以选择一个 \(a_i\) 增减一,求最小操作次数使得所有 \(|a_{i+1}-a_i|\le h\)

数据范围:\(n\le 2\times 10^5\)

思路分析

朴素 dp \(f_{i,j}\) 表示 \(a_i=j\) 的方案数,转移为 \(f_{i,j}\gets \min f_{i-1}[j-h,j+h]+|j-a_i|\)

那么考虑凸优化,直接维护函数斜率的拐点,拓展操作相当于把斜率为 \(0\) 的区间左右分别平移 \(h\)

用两个堆维护斜率为正负两部分的拐点,需要支持全局加,打标记即可。

时间复杂度 \(\mathcal O(n\log n)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN];
priority_queue <ll> L;
priority_queue <ll,vector<ll>,greater<ll>> R;
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	ll tg=0,ht=0; L.push(a[1]),R.push(a[1]);
	for(int i=2;i<=n;++i) {
		tg+=m;
		ll x=L.top()-tg,y=R.top()+tg;
		if(a[i]<=x) {
			ht+=abs(a[i]-x),L.pop(),L.push(a[i]+tg),L.push(a[i]+tg),R.push(x-tg);
		} else if(y<=a[i]) {
			ht+=abs(a[i]-y),R.pop(),R.push(a[i]-tg),R.push(a[i]-tg),L.push(y+tg);
		} else L.push(a[i]+tg),R.push(a[i]-tg);
	}
	printf("%lld\n",ht);
	return 0;
}



*B. 数对(pair)

Problem Link

题目大意

给定数对 \((a,b)\),每次操作可以变成 \((a+b,b)\)\((a,a+b)\),求变成 \((c,d)\) 的最少步数,\(q\) 次询问。

数据范围:\(q\le 10^5\)

思路分析

先从 \(a,b\ge 0\) 的情况开始,那么整个过程中 \(a,b\) 均非负。

那么从 \((c,d)\) 倒推,前一步一定是 \((c,d-c)\)\((c-d,d)\),容易发现 \(c\ne d\) 时只有一个状态满足两个数均非负。

那么我们模拟对 \((c,d)\) 的辗转相除法即可算出答案。

然后只需要考虑 \(a>0>b\) 的情况,如果 \(c<0<d\) 显然无解,如果 \(c>0>d\),那么我们可以把这个过程看成 \((c,-d)\to (a,-b)\) 的问题,也能解决。

现在只需要考虑 \(c,d>0\) 的情况。

我们的想法就是从 \((a,b)\) 出发,如果得到某个 \(a,b\ge 0\) 的状态,那么直接算出到 \((c,d)\) 的最短路。

因此我们只要在 \(a,b\) 均异号的前提下拓展状态,\(a+b<0\) 时,一定变成 \((a,b+a)\)

\(a+b>0\) 时,\((a,a+b)\) 直接算最短路,然后变成 \((a+b,b)\)

那么我们要计算所有 \((a+(k-1)b,a+kb)\to (c,d)\) 的最短路,其中 \(a+kb\ge 0\)

但是很显然这个过程中很多的 \((a+(k-1)b,a+kb)\) 并不能走到 \((c,d)\),我们要求这个数对 \((x,y)\) 在刚才 \((c,d)\) 的辗转相除过程中出现过。

注意到 \(x\ge y\),而辗转相除的过程中这样的 \(y\) 只有 \(\mathcal O(\log V)\) 个,枚举每个 \(y\) 算出对应的 \(k\),然后计算最短路。

预处理 \((c,d)\) 走到 \((x_0,y)\) 的距离,可以直接 \(\mathcal O(1)\) 计算出最短路,因此复杂度 \(\mathcal O(\log^2V)\)

时间复杂度 \(\mathcal O(q\log^2V)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=4e18;
ll f(ll a,ll b,ll c,ll d) {
	if(c<0||d<0) return -1;
	if(a==c&&b==d) return 0;
	ll s=0;
	while(c&&d) {
		if(c<d) swap(a,b),swap(c,d);
		if(b==d&&a<=c&&(c-a)%d==0) return s+(c-a)/d;
		if(c%d==0&&a==d&&!b) return s+c/d;
		s+=c/d,c%=d;
	}
	return a==c&&b==d?s:-1;
}
ll F(ll a,ll b,ll c,ll d) {
	ll z=f(a,b,c,d);
	return ~z?z:inf;
}
void solve() {
	ll a,b,c,d;
	cin>>a>>b>>c>>d;
	if(a<=0&&b<=0) a=-a,b=-b,c=-c,d=-d;
	if(a>=0&&b>=0) return cout<<f(a,b,c,d)<<"\n",void();
	if(a<0) a=-a,b=-b,c=-c,d=-d;
	if(c<0&&d>0) return cout<<"-1\n",void();
	if(c>=0&&d<=0) return cout<<f(c,-d,a,-b)<<"\n",void();
	if(c<0) a=-a,b=-b,c=-c,d=-d,swap(a,b),swap(c,d);
	vector <array<ll,3>> S;
	for(ll x=c,y=d,z=0;x&&y;) {
		if(y>=x) z+=y/x,y%=x;
		else S.push_back({y,x,z}),z+=x/y,x%=y;
	}
	ll ans=inf,s=0;
	while(a>0&&b<0) {
		if(a+b==0) {
			ans=min(ans,s+1+F(a,0,c,d));
			break;
		}
		if(a+b<0) {
			s+=(-b)/a,b=-((-b)%a);
			continue;
		}
		for(auto it:S) {
			ll y=it[0],mx=it[1];
			if(y<a&&(a-y)%(-b)==0) {
				ll k=(y-a)/b,x=a+(k-1)*b;
				if(x<=mx&&(mx-x)%y==0) ans=min(ans,s+k+it[2]+(mx-x)/y);
			}
		}
		if(a%(-b)==0) ans=min(ans,s+a/(-b)+F(-b,0,c,d));
		s+=a/(-b),a%=-b;
	}
	if(a>=0&&b>=0) ans=min(ans,s+F(a,b,c,d));
	cout<<(ans==inf?-1:ans)<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



C. 染色(color)

Problem Link

题目大意

给定 \(n\times n\) 网格,有一些格子是黑色的,每次操作可以选择一个和黑色格子相邻的白格染黑,求 \(k\) 此操作能生成多少种本质不同的图案。

数据范围:\(n\le 3000,k\le 4\)

思路分析

把所有格子按距离初始黑格的最短路分类,然后分讨染黑的格子的距离,容易解决 \(k\le 3\) 的情况。

对于 \(k=4\) 的情况,大部分都是平凡的,比较特殊的只有 \(1122,1222,1223\) 三种情况,需要一定的特殊处理。

\(1222\)\(1223\) 两种情况类似,我们分讨构成的树形态即可处理,注意特判 \(\begin{bmatrix}1&2\\2&2\end{bmatrix}\)\(\begin{bmatrix}1&2\\2&3\end{bmatrix}\),只有这两种图形会会重复计数。

然后是 \(1122\),我们先考虑两个 \(2\) 分别和某个 \(1\) 相邻的情况:

此时可以容斥,钦定零个或一个 \(2\) 不与 \(1\) 相邻可以简单计数,但钦定两个 \(2\) 都不和 \(1\) 相邻的问题有点困难。

设每个 \(2\) 邻域中 \(1\) 的集合为 \(s_1\sim s_k\)\(1\) 的总数为 \(c\),那么所求即为 \(\sum_{i<j}\binom{c-|s_i\cup s_j|}{2}\)

枚举 \(i\),注意到大部分 \(|s_i\cup s_j|=|s_i|+|s_j|\),又因为 \(|s_i|\le 4\),因此对于每个可能的 \(v\in[0,4]\),预处理 \(\sum_i\binom{c-v-|s_i|}2\)

然后我们特殊处理掉 \(j=i\) 以及 \(s_i\cap s_j\) 非空的情况,注意到后者对应的两个点的曼哈顿距离一定 \(=2\),可以直接枚举出来。

时间复杂度 \(\mathcal O(n^2)\)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3005,MAXS=1e7+5,MOD=1e9+7,i2=(MOD+1)/2,dx[]={0,0,1,-1},dy[]={1,-1,0,0};
char mp[MAXN][MAXN],ds[MAXN][MAXN],w[MAXN][MAXN][5];
int n,ty,m,ct[5];
vector <array<short,2>> f[5];
ll C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	__int128 p=1;
	for(int i=0;i<y;++i) p*=x-i;
	for(int i=0;i<y;++i) p/=i+1;
	return p%MOD;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>ty,memset(ds,-1,sizeof(ds));
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
		cin>>mp[i][j],ds[i][j]=(mp[i][j]=='#'?0:5);
	}
	for(int z=1;z<=4;++z) {
		for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(ds[i][j]>z) {
			for(int k:{0,1,2,3}) {
				int x=i+dx[k],y=j+dy[k];
				if(x<1||x>n||y<1||y>n) continue;
				if(ds[x][y]==z-1) { ds[i][j]=z; break; }
			}
			if(ds[i][j]==z) {
				++ct[z],f[z].push_back({short(i),short(j)});
				for(int k:{0,1,2,3}) {
					int x=i+dx[k],y=j+dy[k];
					if(x<1||x>n||y<1||y>n) continue;
					++w[x][y][z];
				}
				
			}
		}
	}
	if(ty==1) {
		cout<<ct[1]<<"\n";
		return 0;
	}
	if(ty==2) {
		ll ans=C(ct[1],2);
		for(auto o:f[1]) ans+=w[o[0]][o[1]][2];
		cout<<ans%MOD<<"\n";
		return 0;
	}
	if(ty==3) {
		ll ans=C(ct[1],3); //111
		for(auto o:f[2]) { //112
			ans+=C(ct[1],2)-C(ct[1]-w[o[0]][o[1]][1],2);
		}
		for(auto o:f[1]) { //122 & 123
			int i=o[0],j=o[1];
			ans+=C(w[i][j][2],2);
			for(int k:{0,1,2,3}) {
				int x=i+dx[k],y=j+dy[k];
				if(x<1||x>n||y<1||y>n) continue;
				if(ds[x][y]==2) ans+=w[x][y][2]+w[x][y][3];
			}
		}
		cout<<(ans%MOD+MOD)%MOD<<"\n";
		return 0;
	}
	ll ans=C(ct[1],4); //1111
	for(auto o:f[2]) {
		int i=o[0],j=o[1];
		ans+=C(ct[1],3)-C(ct[1]-w[i][j][1],3); //1112
		ans+=(C(ct[1],2)-C(ct[1]-w[i][j][1],2))*w[i][j][3]; //1123
		//1234 & 1233
		ans+=w[i][j][1]*C(w[i][j][3],2);
		for(int k:{0,1,2,3}) {
			int x=i+dx[k],y=j+dy[k];
			if(x<1||x>n||y<1||y>n) continue;
			if(ds[x][y]==3) ans+=(w[x][y][3]+w[x][y][4])*w[i][j][1];
		}
	}
	for(auto o:f[1]) { //1222 & 1223
		int i=o[0],j=o[1],z=w[i][j][2];
		ans+=C(z,3);
		for(int k:{0,1,2,3}) {
			int x=i+dx[k],y=j+dy[k];
			if(x<1||x>n||y<1||y>n||ds[x][y]!=2) continue;
			ans+=(w[x][y][2]+w[x][y][3])*(z-1);
			ans+=C(w[x][y][2],2)+w[x][y][2]*w[x][y][3];
			for(int q:{0,1,2,3}) {
				int s=x+dx[q],t=y+dy[q];
				if(s<1||s>n||t<1||t>n) continue;
				if(ds[s][t]==2) ans+=w[s][t][2]-1+w[s][t][3];
				if(ds[s][t]==3) ans+=w[s][t][2]-1;
			}
		}
		for(int x:{i-1,i+1}) for(int y:{j-1,j+1}) if(ds[x][j]==2&&ds[i][y]==2) {
			if(ds[x][y]==2||ds[x][y]==3) ans-=3;
		}
	}
	//1122
	//link 1-2 1-2
	ans+=C(ct[1],2)*C(ct[2],2);
	ll sum[5]={0,0,0,0};
	for(int z:{0,1,2,3,4}) {
		for(auto o:f[2]) {
			int i=o[0],j=o[1];
			sum[z]+=C(ct[1]-z-w[i][j][1],2);
		}
	}
	for(auto o:f[2]) {
		int i=o[0],j=o[1],z=w[i][j][1];
		ans-=C(ct[1]-w[i][j][1],2)*(ct[2]-1);
		ll res=sum[z]-C(ct[1]-z-w[i][j][1],2);
		for(int k:{0,1,2,3}) {
			int x=i+2*dx[k],y=j+2*dy[k];
			if(x<1||x>n||y<1||y>n||ds[x][y]!=2) continue;
			if(ds[i+dx[k]][j+dy[k]]==1) {
				res-=C(ct[1]-z-w[x][y][1],2);
				res+=C(ct[1]-z-w[x][y][1]+1,2);
			}
		}
		for(int x:{i-1,i+1}) for(int y:{j-1,j+1}) if(ds[x][y]==2) {
			res-=C(ct[1]-z-w[x][y][1],2);
			res+=C(ct[1]-z-w[x][y][1]+(ds[x][j]==1)+(ds[i][y]==1),2);
		}
		ans=(ans+res%MOD*i2)%MOD;
	}
	for(auto o:f[2]) {
		//only link 2-2
		int i=o[0],j=o[1];
		for(int k:{0,1,2,3}) {
			int x=i+dx[k],y=j+dy[k];
			if(x<1||x>n||y<1||y>n||ds[x][y]!=2) continue;
			int a=w[i][j][1],b=w[x][y][1],c=ct[1]-a-b;
			ans+=C(c+a,2)-C(c,2);
		}
	}
	cout<<(ans%MOD+MOD)%MOD<<"\n";
	return 0;
}
posted @ 2025-04-10 22:00  DaiRuiChen007  阅读(121)  评论(0)    收藏  举报