2025 省选做题记录(三)


By DaiRuichen007



Round #41 - 2024.12.31

A. [CF1900F] Local Deletions

Problem Link

题目大意

定义数组 a 的权值为不断重复如下操作直到 |a|=1 时剩下的数:

  • 仅保留 aimin(ai1,ai+1) 的元素。
  • 仅保留 aimax(ai1,ai+1) 的元素。

给定 1n 排列 aiq 次询问 a[l,r] 的权值。

数据范围:n,q105

思路分析

题目中的操作不存在方便刻画的性质,因此想求 a 的权值,必须暴力模拟。

但我们发现每次操作后,a 的大小总会减半,因此我们只会进行上述操作 O(logn) 轮。

回到原题,我们求出原序列操作一轮后的结果,如果仅对 a[l,r] 操作一轮,只可能会改变 al,ar 的保留状态。

同理,操作第二轮,只可能改变 al 后继与 ar 前驱的保留状态。

因此我们提取出当前序列的前后 O(logn) 个元素暴力维护,剩下的元素和在原序列操作若干轮后的结果一样。

时间复杂度 O(n+qlog2n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
const int MAXN=1e5+5;
int n,q,a[MAXN];
vi f[32];
void trs(const vi &u,vi &v,bool op,int ed=-1) { //0: local max
	auto cmp=[&](int i,int j) {
		int tp=(j>=(int)u.size()?ed:(j<0?-1:a[u[j]]));
		return tp==-1||(op?a[u[i]]<tp:a[u[i]]>tp);
	};
	for(int i=0;i<(int)u.size();++i) if(cmp(i,i-1)&&cmp(i,i+1)) v.push_back(u[i]);
}
signed main() {
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),f[0].push_back(i);
	for(int i=1;f[i-1].size()>1;++i) trs(f[i-1],f[i],i&1);
	for(int l,r;q--;) {
		scanf("%d%d",&l,&r);
		vi L,R,o;
		int i=1;
		for(;;++i) {
			auto il=lower_bound(f[i-1].begin(),f[i-1].end(),l);
			auto ir=upper_bound(f[i-1].begin(),f[i-1].end(),r)-1;
			if(ir-il<=3) {
				for(auto it=il;it<=ir;++it) L.push_back(*it);
				for(int j=(int)R.size()-1;~j;--j) L.push_back(R[j]);
				break;
			}
			L.push_back(*il),R.push_back(*ir);
			trs(L,o,i&1,a[*++il]),L.swap(o),o.clear();
			trs(R,o,i&1,a[*--ir]),R.swap(o),o.clear();
			l=*il,r=*ir;
		}
		for(;L.size()>1;++i) {
			trs(L,o,i&1),L.swap(o),o.clear();
		}
		printf("%d\n",a[L[0]]);
	}
	return 0;
}



B. [CF1906C] Cursed Game

Problem Link

题目大意

交互器有一个 3×3 的 01 矩阵 a,你每次可以询问一个 n×n 的矩阵 b,返回 n2×n2 矩阵 c 满足 cx,y=i,j[1,3]ai,jANDbx+i1,y+j1

你需要构造一个 b 使得返回的 c 是全 1 矩阵,要求 333 组数据的交互次数 999

数据范围:n33,2n

思路分析

首先如果 n>3,我们构造仅有 b2,2=1 的矩阵,那么 c13,13 就是行列反转的 a

知道 a 之后,我们用 a 中最靠右下角的 1 来调整每个 c 即可,需要 2 次询问。

如果 n=3,那么随机一个矩阵 b,得到的结果可以看做 {0,1} 中的随机变量,那么直接随机,期望 2 次询问得到结果。

时间复杂度 O(n2)

代码呈现

#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(time(0));
int n,a[45][45],b[3][3];
string op;
void solve() {
	cin>>n;
	if(n==3) {
		while(true) {
			cout<<(rnd()&1)<<(rnd()&1)<<(rnd()&1)<<endl;
			cout<<(rnd()&1)<<(rnd()&1)<<(rnd()&1)<<endl;
			cout<<(rnd()&1)<<(rnd()&1)<<(rnd()&1)<<endl;
			cin>>op;
			if(op=="CORRECT") return ;
			int x; cin>>x;
		}
		return ;
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) cout<<(i==3&&j==3);
		cout<<endl;
	}
	cin>>op;
	if(op=="CORRECT") return ;
	for(int i=1;i<=n-2;++i) for(int j=1;j<=n-2;++j) {
		char w; cin>>w; a[i][j]=w-'0';
	}
	for(int i=1;i<=3;++i) for(int j=1;j<=3;++j) b[3-i][3-j]=a[i][j];
	memset(a,0,sizeof(a));
	int x=0,y=0;
	for(int i:{0,1,2}) for(int j:{0,1,2}) if(b[i][j]) x=i,y=j;
	for(int i=1;i<=n-2;++i) for(int j=1;j<=n-2;++j) {
		int val=0;
		for(int u:{0,1,2}) for(int v:{0,1,2}) val^=b[u][v]&a[i+u][j+v];
		if(!val) a[i+x][j+y]^=1;
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) cout<<a[i][j];
		cout<<endl;
	}
	cin>>op;
}
signed main() {
	int _=333;
	while(_--) solve();
	return 0;
}



*C. [CF1895G] Two Characters, Two Colors

Problem Link

题目大意

给定长度为 n 的 01 串 si,每个位置保留有权值 ri,删除有权值 bi,最大化获得权值之和减去剩余元素的逆序对数。

数据范围:n4×105

思路分析

对于原序列的一对逆序对 (i,j),看成同时选 si,sj 产生 1 贡献,这就是一个最小割问题,相当于给 lirj 连一条流量为 1 的边。

如果 si=0 那么 Sli 流量 biriT 流量 ri,否则 Sli 流量 ririT 流量 bi,最后 liri 流量

很显然每个点优先从 lirimin(ri,bi) 的流量。

然后对于每个 si=1,我们还能在 j<isj=0 中匹配 bimin(ri,bi) 个,而每个 sj=0 最多匹配 bjmin(rj,bj)si

很显然这是贪心问题,每个 si=1 匹配剩余次数最大的若干个 sj=0,用平衡树动态维护每个元素的剩余匹配次数。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e5+5;
mt19937 rnd(time(0));
struct Treap {
	int ls[MAXN],rs[MAXN],pri[MAXN],siz[MAXN],tg[MAXN];
	ll val[MAXN];
	void adt(int p,int k) { if(p) val[p]+=k,tg[p]+=k; }
	void psd(int p) { adt(ls[p],tg[p]),adt(rs[p],tg[p]),tg[p]=0; }
	void psu(int p) { siz[p]=siz[ls[p]]+1+siz[rs[p]]; }
	void spiv(int p,ll k,int &x,int &y) {
		if(!p) return x=y=0,void();
		psd(p);
		if(val[p]<=k) x=p,spiv(rs[p],k,rs[x],y),psu(x);
		else y=p,spiv(ls[p],k,x,ls[y]),psu(y);
	}
	void spiz(int p,int k,int &x,int &y) {
		if(!p) return x=y=0,void();
		psd(p);
		if(siz[ls[p]]<k) x=p,spiz(rs[p],k-siz[ls[p]]-1,rs[x],y),psu(x);
		else y=p,spiz(ls[p],k,x,ls[y]),psu(y);
	}
	int merge(int x,int y) {
		if(!x||!y) return x|y;
		psd(x),psd(y);
		if(pri[x]<pri[y]) return rs[x]=merge(rs[x],y),psu(x),x;
		else return ls[y]=merge(x,ls[y]),psu(y),y;
	}
	ll mx(int x) {
		for(;rs[x];x=rs[x]) psd(x);
		return val[x];
	}
	ll mn(int x) {
		for(;ls[x];x=ls[x]) psd(x);
		return val[x];
	}
}	T;
char op[MAXN];
ll a[MAXN],b[MAXN];
void solve() {
	int n,rt=0;
	cin>>n;
	ll ans=0,all=0;
	for(int i=1;i<=n;++i) cin>>op[i];
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) cin>>b[i];
	for(int i=1,x,y;i<=n;++i) {
		ans+=min(a[i],b[i]),all+=a[i]+b[i];
		if(a[i]<=b[i]) continue;
		if(op[i]=='1') {
			ll k=a[i]-b[i];
			T.val[i]=k,T.pri[i]=rnd(),T.siz[i]=1;
			T.spiv(rt,k,x,y);
			rt=T.merge(T.merge(x,i),y);
		}
		if(op[i]=='0'&&rt) {
			ll k=a[i]-b[i];
			if(T.siz[rt]<=k) ans+=T.siz[rt],T.adt(rt,-1);
			else {
				T.spiz(rt,T.siz[rt]-k,x,y);
				T.adt(y,-1),ans+=k;
				ll vl=T.mx(x),vr=T.mn(y);
				if(vl>vr) {
					int xl,xr,yl,yr;
					T.spiv(x,vl-1,xl,xr);
					T.spiv(y,vr,yl,yr);
					rt=T.merge(T.merge(xl,yl),T.merge(xr,yr));
				} else rt=T.merge(x,y);
			}
			if(!T.mn(rt)) T.spiv(rt,0,x,y),rt=y;
		}
	}
	cout<<all-ans<<"\n";
	for(int i=1;i<=n;++i) {
		T.ls[i]=T.rs[i]=T.siz[i]=T.pri[i]=T.tg[i]=T.val[i]=0;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*D. [CF1896G] Pepe Racing

Problem Link

题目大意

交互器中有 n2 个不同元素,每次可以询问 n 个不同元素中的最大值,在 2n2(2n1) 次询问内找到前 n2(n1) 大元素及其顺序。

数据范围:n20

思路分析

很自然的想法就是分块,分成 n 个组,每个组维护最大值,然后在 n 个最大值之间再进行询问。

每次弹出最大值之后,重新求出该块最大值,但是此时块的大小 <n,我们需要放入一些元素进行填补,可以把其他块中的非最大值移动到这个块中,不影响每块最大值。

此时询问次数 n+2(n2n+1)=2n2(n2),还要再优化 n+1 次询问。

那么我们找到最后的 n 个元素,尝试把它们用 n1 次询问求出来。

此时我们还剩 2n1 个元素,并且最大的 n 个一定是每个块的最大值,剩下的 n1 个肯定不会是答案。

那么每次询问 n 个元素最大值,删去后放入一个剩下的元素,进行 n1 轮,还剩一个元素就是第 n2(n1) 大值。

恰好需要 2n2(2n1) 次询问。

时间复杂度 O(n3)

代码呈现

#include<bits/stdc++.h>
using namespace std;
vector <int> a[25];
int n,mx[25];
int find(vector<int>&v,int x) { return find(v.begin(),v.end(),x)-v.begin(); }
int qry(vector<int>&v) {
	cout<<"? "; for(int i:v) cout<<i<<" "; cout<<endl;
	int r; cin>>r; return r;
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) a[i].push_back((i-1)*n+j);
		mx[i]=qry(a[i]),swap(a[i][0],a[i][find(a[i],mx[i])]);
	}
	vector <int> ans;
	for(int o=n*n;o>=2*n;--o) {
		vector <int> b;
		for(int i=1;i<=n;++i) b.push_back(mx[i]);
		ans.push_back(qry(b));
		for(int i=1;i<=n;++i) if(mx[i]==ans.back()) {
			a[i].erase(a[i].begin());
			for(int j=1;j<=n&&(int)a[i].size()<n;++j) if(i!=j) {
				while((int)a[i].size()<n&&(int)a[j].size()>1) {
					a[i].push_back(a[j].back()),a[j].pop_back();
				}
			}
			mx[i]=qry(a[i]),swap(a[i][0],a[i][find(a[i],mx[i])]);
		}
	}
	vector <int> b,c;
	for(int i=1;i<=n;++i) {
		b.push_back(mx[i]);
		for(int j=1;j<(int)a[i].size();++j) c.push_back(a[i][j]);
	}
	for(int o=n;o>1;--o) {
		ans.push_back(qry(b));
		b.erase(find(b.begin(),b.end(),ans.back()));
		b.push_back(c.back()),c.pop_back();
	}
	ans.push_back(b[0]);
	cout<<"! "; for(int i:ans) cout<<i<<" "; cout<<endl;
	for(int i=1;i<=n;++i) a[i].clear(),mx[i]=0;
}
signed main() {
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



E. [CF1893E] Cacti Symphony

Problem Link

题目大意

给定 n 个点 m 条边的点仙人掌,在每条边中间插入若干个点,给每个点和边赋权 {1,2,3}

使得每条边的端点异或和 0 且不等于边权,每个点的邻边异或和 0 且不等于点权,求方案数。

数据范围:n5×105,m106

思路分析

先观察边 e=(u,v),那么 auav,且 be{au,av}

然后考虑点 u,设其出边中 1,2,3 三种权值出现次数的奇偶性为 c1,c2,c3,要求就是 c1,c2,c3 不全相等,并且 cauc1,c2,c3 中出现过两次。

如果 u 度数为奇数,要求 cau 为偶数,对其他两种无限制,否则要求 cau 为偶数,对其他两种也无限制。

因此我们只需要 beau 的点有偶数个即可。

把每条边定向到 beau 的方向上,赋边权只要求每个点度数为偶数,赋点权只要求相邻点权不同。

先考虑边权,这是经典问题,只要 2mn 即可在任意一棵生成树上调整,方案数 2c 其中 c 是环数。

赋权的方案数可以把环看成点,从上到下填颜色,每条边会让方案数 ×23,那么求出桥的个数,以及每个点双联通分量的方案数乘积。

对于一个大小为 n 的环,染色方案数 fn=3×2n1fn1 即用链的方案数减去两个端点同色的方案数,展开得到 fn=2n2×(1)n

Tarjan 找出每个环即可。

时间复杂度 O(nlogP+m)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,MOD=998244353,i3=(MOD+1)/3;
ll ksm(ll a,ll b) { ll s=1; for(b%=MOD-1;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
struct Edge { int v,i,w; };
vector <Edge> G[MAXN];
int dfn[MAXN],low[MAXN],dcnt;
bool ins[MAXN],br[MAXN*2],vis[MAXN];
ll bc,vc,cc,sz; //bridge, ver, cycle, cyc-siz
void tarjan(int u,int fz) {
	dfn[u]=low[u]=++dcnt;
	for(auto e:G[u]) if(e.v^fz) {
		if(!dfn[e.v]) {
			tarjan(e.v,u),low[u]=min(low[u],low[e.v]);
			if(low[e.v]>dfn[u]) br[e.i]=true,bc+=e.w+1,vc+=e.w;
		} else low[u]=min(low[u],dfn[e.v]);
	}
}
void dfs(int u) {
	if(vis[u]) return ; vis[u]=true;
	for(auto e:G[u]) {
		if(!br[e.i]) sz+=e.w+1,br[e.i]=true,dfs(e.v);
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int n,m;
	cin>>n>>m;
	if(n%2!=m%2) return cout<<"0\n",0;
	for(int i=1,u,v,w;i<=m;++i) {
		cin>>u>>v>>w;
		G[u].push_back({v,i,w});
		G[v].push_back({u,i,w});
	}
	ll ans=1;
	tarjan(1,0);
	for(int i=1;i<=n;++i) if(!vis[i]) {
		sz=0,dfs(i);
		if(!sz) { ++vc; continue; }
		ans=ans*(ksm(2,sz)+(sz&1?MOD-2:2))%MOD,++cc;
	}
	ans=ans*ksm(i3,bc)%MOD*ksm(3,vc)%MOD*ksm(2,bc+cc)%MOD;
	cout<<ans<<"\n";
	return 0;
}




Round #42 - 2025.1.2

A. [CF1874D] Jellyfish and Miku

Problem Link

题目大意

给定长度为 n 的链,给每条边赋一个权重 ai,每个点会按权重随机一条边移动,要求 aim,最小化 0n 的期望移动步数。

数据范围:n,m3000

思路分析

根据期望线性性,计算 ii+1 的期望步数 fi

fi=1+aiai+ai+1(fi1+fi)=aiai+1(1+fi1)+1=1+2jiajai+1

因此直接 dp,fi,s 表示 a[1,i] 前缀和为 s 的方案数,枚举 ai=x,暴力 dp 复杂度 O(nm2)

但根据贪心思想,不难证明 a 单调递减,否则交换邻项更优,那么 (ni)xms,此时枚举量降到 O(m2logn) 级别。

时间复杂度 O(m2logn)

代码呈现

#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=3005;
const ld inf=1e18;
ld f[MAXN],g[MAXN];
signed main() {
	int n,m;
	scanf("%d%d",&n,&m);
	for(int j=0;j<=m;++j) f[j]=g[j]=inf;
	f[0]=0;
	for(int i=1;i<=n;++i) {
		for(int s=0;s<=m;++s) {
			for(int j=1;s+j<=m&&s+(n-i)*j<=m;++j) {
				g[s+j]=min(g[s+j],f[s]+(ld)s/j);
			}
		}
		for(int j=0;j<=m;++j) f[j]=g[j],g[j]=inf;
	}
	ld ans=inf;
	for(int j=0;j<=m;++j) ans=min(ans,f[j]);
	printf("%.20Lf\n",ans*2+n);
	return 0;
}



B. [CF1870F] Lazy Numbers

Problem Link

题目大意

给定 n,k,求 1nk 进制下有多少个数值等于其字典序排名。

数据范围:n,k1018

思路分析

求正整数的字典序排名可以直接建 Trie,那么数值等于其 bfs 序,字典序排名等于其 dfs 序。

对每层的节点分别考虑,同层节点 bfs 序每次 +1,而 dfs 序每次增量 1,因此两者之差单调递增,可以二分出 =0 的区间。

那么我们只要支持快速计算每个数的字典序排名,逐层计数即可。

时间复杂度 O(log3n)

代码呈现

#include<bits/stdc++.h> 
#define ll long long
#define LL __int128
using namespace std;
ll n,m,pw[65],len;
ll rk(int k,ll x) {
	ll s=0,y=x;
	for(int i=k;~i;--i) s+=y-pw[i]+1,y/=m;
	if(k==len) return s;
	for(int i=k+1;i<len;++i) s+=(x-pw[k])*pw[i-k];
	s+=min((LL)n+1,(LL)x*pw[len-k])-pw[len];
	return s;
}
void solve() {
	scanf("%lld%lld",&n,&m),len=0;
	for(pw[0]=1;(LL)pw[len]*m<=n;++len) pw[len+1]=pw[len]*m;
	ll ans=0;
	for(int i=0;i<=len;++i) {
		ll L=pw[i],R=(i==len?n:pw[i+1]-1);
		auto find=[&](ll d) {
			ll l=L,r=R,p=l-1;
			while(l<=r) {
				ll mid=(l+r)>>1;
				if(rk(i,mid)-mid<=d) p=mid,l=mid+1;
				else r=mid-1;
			}
			return p;
		};
		ans+=find(0)-find(-1);
	}
	printf("%lld\n",ans);
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



C. [CF1906I] Contingency Plan 2

Problem Link

题目大意

给定 n 个点 n1 条边的弱联通 DAG,加入最少的边使得该图拓扑序唯一。

数据范围:n105

思路分析

可以证明拓扑序唯一当且仅当最长链长度为 n,否则拓扑序中无边的相邻元素并交换即可。

那么我们求出该图的最小链覆盖,然后把所有链缩点,按拓扑序连起来即可。

时间复杂度 O(nn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
namespace F {
const int MAXV=2e5+5,MAXE=6e5+5,inf=1e9;
struct Edge {
	int v,f,lst;
}	G[MAXE];
int S,T,tot=1,hd[MAXV],cur[MAXV],dep[MAXV];
void init() { tot=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int w) { G[++tot]={v,w,hd[u]},hd[u]=tot; }
int link(int u,int v,int w) { adde(u,v,w),adde(v,u,0); return tot; }
bool BFS() {
	memcpy(cur,hd,sizeof(cur)),memset(dep,-1,sizeof(dep));
	queue <int> Q;
	Q.push(S),dep[S]=0;
	while(!Q.empty()) {
		int u=Q.front(); Q.pop();
		for(int i=hd[u];i;i=G[i].lst) if(G[i].f&&dep[G[i].v]==-1) {
			dep[G[i].v]=dep[u]+1,Q.push(G[i].v);
		}
	}
	return ~dep[T];
}
int dfs(int u,int f) {
	if(u==T) return f;
	int r=f;
	for(int i=cur[u];i;i=G[i].lst) {
		int v=G[cur[u]=i].v;
		if(G[i].f&&dep[v]==dep[u]+1) {
			int g=dfs(v,min(r,G[i].f));
			if(!g) dep[v]=-1;
			G[i].f-=g,G[i^1].f+=g,r-=g;
		}
		if(!r) return f;
	}
	return f-r;
}
int Dinic() {
	int f=0;
	while(BFS()) f+=dfs(S,inf);
	return f;
}
}
const int MAXN=1e5+5;
vector <int> G[MAXN];
int st[MAXN],ed[MAXN],id[MAXN],nxt[MAXN],ty[MAXN],deg[MAXN];
bool vis[MAXN];
signed main() {
	int n;
	scanf("%d",&n);
	int s=F::S=2*n+1,t=F::T=2*n+2;
	for(int i=1;i<=n;++i) F::link(s,i,1),F::link(i+n,t,1);
	for(int i=1;i<n;++i) {
		scanf("%d%d",&st[i],&ed[i]);
		id[i]=F::link(st[i],ed[i]+n,1);
	}
	printf("%d\n",n-1-F::Dinic());
	for(int i=1;i<n;++i) if(F::G[id[i]].f) nxt[st[i]]=ed[i],vis[ed[i]]=true;
	for(int i=1;i<=n;++i) if(!vis[i]) for(int j=i;j;j=nxt[j]) ty[j]=i;
	for(int i=1;i<n;++i) if(ty[st[i]]!=ty[ed[i]]) {
		G[ty[st[i]]].push_back(ty[ed[i]]),++deg[ty[ed[i]]];
	}
	queue <int> Q;
	for(int i=1;i<=n;++i) if(!deg[i]&&!vis[i]) Q.push(i);
	for(int x=0;Q.size();) {
		int u=Q.front(); Q.pop();
		if(x) printf("%d %d\n",x,u);
		for(x=u;nxt[x];x=nxt[x]);
		for(int v:G[u]) if(!--deg[v]) Q.push(v);
	}
	return 0;
}



*D. [CF1874F] Jellyfish and OEIS

Problem Link

题目大意

给定 a1an,求有多少 1n 排列 p,使得对于所有 lral[l,r],都有 {al,al+1,,ar}{l,l+1,,r}

数据范围:n300

思路分析

首先这个条件难以刻画,因此需要容斥处理。

我们钦定若干个区间 [l,r] 满足这个要求。

但这些区间的关系过于复杂,但我们注意到如果 l1l2r1r2,那么 [l1,r1],[l2,r2] 被我们钦定满足要求,则 [l2,r1] 自然也满足要求,且 r1al2,所以无论是否钦定 [l2,r1],方案数不变,且容斥系数取反,因此这种情况都会被抵消。

所以我们只要考虑钦定的区间集合两两无交或包含的情况。

此时所有的区间显然构成树状结构,一个节点的方案数就是所有儿子的方案数乘积,再乘上未被儿子覆盖的点的阶乘,表示这些点无限制,可以在该节点对应的区间内重排。

因此按这个结构 dp,fl,r 表示以区间 [l,r] 为根的子树的方案数总和,gl,r,x 表示 [l,r] 区间内,有 x 个未覆盖点的方案数。

g 转移的时候枚举 r 未覆盖,或加入一个节点 [k,r],而 fl,r=x!gl,r,x,答案就是 x!g1,n,x

时间复杂度 O(n4)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=205,MOD=1e9+7;
int n,m[MAXN],fac[MAXN],f[MAXN][MAXN],g[MAXN][MAXN][MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=fac[0]=1;i<=n;++i) scanf("%d",&m[i]),fac[i]=1ll*i*fac[i-1]%MOD;
	for(int i=0;i<=n;++i) g[i+1][i][0]=1;
	for(int l=n;l>=1;--l) for(int r=l;r<=n;++r) {
		for(int x=1;x<=r-l+1;++x) g[l][r][x]=g[l][r-1][x-1];
		for(int x=0;x<r-l+1;++x) {
			int &z=g[l][r][x];
			for(int i=(x?l+x-1:l);i<r;++i) z=(z+1ll*g[l][i][x]*f[i+1][r])%MOD;
		}
		if(r<=m[l]) {
			for(int x=0;x<=r-l+1;++x) f[l][r]=(f[l][r]+1ll*(MOD-fac[x])*g[l][r][x])%MOD;
		}
		g[l][r][0]=(g[l][r][0]+f[l][r])%MOD;
	}
	int ans=0;
	for(int x=0;x<=n;++x) ans=(ans+1ll*fac[x]*g[1][n][x])%MOD;
	printf("%d\n",ans);
	return 0;
}



*E. [CF1874G] Jellyfish and Inscryption

Problem Link

题目大意

给定 n 个点 m 条边的 DAG,从 1 走到 n,每个点上可能有如下的事件之一:

  • A:获得一个二元组 (au,bu)
  • B:选择一个二元组 (a,b) 变成 (a+xu,b)
  • C:选择一个二元组 (a,b) 变成 (a,b+yu)
  • D:获得 w 的权值。

到达 n 之后你可以把一个 (a,b) 变成 (a×109,b),然后每个二元组 (a,b) 会产生 a×b 的权值,最大化你获得的权值。

数据范围:n,a,b,x,y200,m2000

思路分析

先解决 12n 的链上的问题。

最特殊的操作很显然是 ×109,假设我们给 (au,bu) 进行了这个操作,那么相当于按 au×bu 为第一关键字,其他权值为第二关键字比较。

所以 u+1n 的所有 BC 操作肯定肯定会给 (au,bu),因为这样能最大化 au×bu

因此我们只要考虑 1u1 且不存在 ×109 操作的情况。

如果没有 B 操作,那么每个 C 操作肯定都会给前缀最大的 au,这样收益最大。

如果有 B 操作,那么直接来看难以确定 C 操作,但如果我们已经知道最终的每个 au 的值 au,那么 C 操作就会给到 au 的前缀最大值。

所以我们知道,所有 C 操作的决策点 opi 要么和上一个操作的决策点 opj 相同,要么 >j

所以可以得到一个朴素的 dp:fu,x,y,0/1 表示当前 1u,BC 操作的决策点当前的 b/a 的值分别为 x,y,且这两个决策点是否相同。

此时状态数是 O(n3V2) 的,需要进一步优化。

我们注意到如果最终存在某个 aj>V,bk>V,且 j<k,那么由于 bk>V,说明至少有一个 C 操作增加了 bk,从而 ak 也是一个前缀最大值,所以 ak>aj>V

那么此时 ak,bk>V,如果我们选择这个元素 ×109,得到的结果一定更优。

所以我们知道 maxaiV,maxbiV 至少有一个成立,不妨设 maxaiV,则 xV,状态数 O(n2V2)

此时我们尝试不记录 y

我们发现 y 只用来处理操作 B,如果我们把操作 B 的贡献直接放到操作 A 里计算,即枚举 au,钦定权值为 au×bu,而操作 C 权值就是 maxa1i×yi

那么我们在状态里要记录所有 auau,不能接受。

由于刚刚的贪心结论,操作 B 的位置单调递增,因此每个 ai>ai 的位置之前的 aj 已经被填满,所以此时只剩当前 ai 还没填满,所以只存在一个非零的 auau

那么 dp 的时候记录 fi,x,y 表示当前 maxa1i=x,当前 auau=y 的最大权值。

转移是平凡的,状态数 O(nV2),转移的时候如果要枚举当前新的 au,则一定有 y=0,因此复杂度 O(nV2)

这个 dp 放到图上转移不变,只不过有多个后继,复杂度 O(mV2)

最后我们还要从 n 开始 dp,求出走到每个 ×109(au,bu),每种 x 对应的最大 y

如果不存在 A 类点,还要求 D 类点构成的最长路。

时间复杂度 O(mV2)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=205,V=200,inf=1e9;
vector <int> G[MAXN],rG[MAXN];
int n,m,op[MAXN],a[MAXN],b[MAXN];
inline void upd(ll &x,const ll &y) { x=y>x?y:x; }
void solve(auto f) {
	//f[u,x,y]: 1->u, max a'=x, a'-a=y
	f[1][0][0]=0;
	for(int u=1;u<=n;++u) for(int v:G[u]) {
		if(op[v]==0) {
			for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
				upd(f[v][x][y],f[u][x][y]);
			}
		}
		if(op[v]==1) {
			for(int x=0;x<=V;++x) {
				for(int y=0;y<=x;++y) {
					upd(f[v][max(x,a[v])][y],f[u][x][y]+a[v]*b[v]);
				}
				for(int y=a[v];y<=V;++y) {
					upd(f[v][max(x,y)][y-a[v]],f[u][x][0]+y*b[v]);
				}
			}
		}
		if(op[v]==2) {
			for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
				upd(f[v][x][y],f[u][x][y]);
				upd(f[v][x][max(0,y-a[v])],f[u][x][y]);
			}
		}
		if(op[v]==3) {
			for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
				upd(f[v][x][y],f[u][x][y]+x*a[v]);
			}
		}
		if(op[v]==4) {
			for(int x=0;x<=V;++x) for(int y=0;y<=x;++y) {
				upd(f[v][x][y],f[u][x][y]+a[v]);
			}
		}
	}
}
ll dp[2][MAXN][V+5][V+5];
array<ll,2> h[MAXN][MAXN*V+5];
//sum a, {sum b, sum other}
void init() {
	h[n][0]={0,0};
	for(int u=n;u>=1;--u) for(int v:rG[u]) {
		if(op[v]==0) {
			for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],h[u][x]);
		}
		if(op[v]==1) {
			for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],{h[u][x][0],h[u][x][1]+a[v]*b[v]});
		}
		if(op[v]==2) {
			for(int x=0;x<=n*V;++x) {
				h[v][x]=max(h[v][x],h[u][x]);
				if(x>=a[v]) h[v][x]=max(h[v][x],h[u][x-a[v]]);
			}
		}
		if(op[v]==3) {
			for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],{h[u][x][0]+a[v],h[u][x][1]});
		}
		if(op[v]==4) {
			for(int x=0;x<=n*V;++x) h[v][x]=max(h[v][x],{h[u][x][0],h[u][x][1]+a[v]});
		}
	}
}
ll dis[MAXN];
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		scanf("%d",&op[i]);
		if(op[i]==1) scanf("%d%d",&a[i],&b[i]);
		else if(op[i]) scanf("%d",&a[i]);
	}
	for(int i=1,u,v;i<=m;++i) {
		scanf("%d%d",&u,&v);
		G[u].push_back(v),rG[v].push_back(u);
	}
	memset(dp,-0x3f,sizeof(dp));
	memset(h,-0x3f,sizeof(h));
	init(),solve(dp[0]);
	for(int i=1;i<=n;++i) {
		if(op[i]==1) swap(a[i],b[i]);
		else if(op[i]==2||op[i]==3) op[i]^=1;
	}
	solve(dp[1]);
	for(int i=1;i<=n;++i) {
		if(op[i]==1) swap(a[i],b[i]);
		else if(op[i]==2||op[i]==3) op[i]^=1;
	}
	ll ans=0;
	for(int u=1;u<=n;++u) if(op[u]==1) {
		ll Z=0;
		for(int i=0;i<=n*V;++i) if(h[u][i][0]>=0) Z=max(Z,1ll*(a[u]+i)*(b[u]+h[u][i][0])*inf+h[u][i][1]-a[u]*b[u]);
		for(int x=0;x<=V;++x) ans=max(ans,max(dp[0][u][x][0],dp[1][u][x][0])-a[u]*b[u]+Z);
	}
	memset(dis,-0x3f,sizeof(dis)),dis[1]=0;
	for(int u=1;u<=n;++u) for(int v:G[u]) dis[v]=max(dis[v],dis[u]+(op[v]==4?a[v]:0));
	ans=max(ans,dis[n]);
	printf("%lld\n",ans);
	return 0;
}



*F. [CF1868E] Min-Sum-Max

Problem Link

题目大意

给定 a1an,将序列分成 k 段,每段元素和为 S1Sk,要求 1lrk 满足 i=lrSi[minS[l,r],maxS[l,r]],最大化 k

数据范围:n300

思路分析

求出前缀和数组 s(0)s(n),设每个区间的右端点为 p0pk,其中 p0=0

尝试判定:求出 s(px) 的最小值 s(pi) 与最大值 s(pj)

不妨设 j>i,如果我们取区间 (i,j],那么区间和 s(pj)s(pi) 就是全局最大子段和,如果 maxS[l,r]s(pj)s(pi)

说明 s(pi+1)s(pj+1) 中还有等于 s(pi)s(pj) 的元素,或者 ji=1

根据归纳,我们一定能找到满足 ji=1 的一对 s(pi),s(pj)

我们对 [1,j1],[j+1,k] 分别检验,对于一个区间 [l,r] 如果 l<j<r,则显然 SSj,只要 S 有下界即可。

注意到 [j+1,r] 合法,又 s(pr)s(pl1)s(pr)s(pj)minS[j+1,r],因此 [l,r] 一定合法。

那么找到这样的 (i,j) 之后分别检验 [1,j1],[j+1,k] 即可。

那么我们可以划分子结构并 dp:fl,r,x,y 表示区间 [l,r] 只能选择 si[x,y] 的点,最多能划分出多少个区间。

暴力转移复杂度 O(n6),需要优化。

观察转移的形式:我们枚举 i,j[l,r]si,sj[x,y],从 fl,i,min(si,sj),max(si,sj)+fj,r,min(si,sj),max(si,sj) 转移。

注意划分出的新状态 x,y 至少有一个取到 {sl,sr},不妨假设 sl=xsl=y,那么状态仅剩 fl,r,y/gl,r,x

观察 fl,r,y 的转移,我们枚举最大值 sj,此时最小值 sj 一定 =sl,根据贪心,我们肯定最靠近 j 且满足 si=slil,ir

那么 fl,r,y 的转移就是 fl,il,sj+gj,r,slfl,j,sj+fir,r,sj

可以发现这部分已经封闭,可以完成转移,翻转 s 求出 sr{x,y} 的情况,最后枚举全局最大最小值计算答案即可。

时间复杂度 O(n3)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=305,inf=1e9;
int n,m,pre[MAXN],suf[MAXN];
ll a[MAXN],w[MAXN];
void upd(int &x,const int &y) { x=y>x?y:x; }
void DP(auto f,auto g) {
	//f/g[l,r,x]: dp[l,r,a[l],x] / dp[l,r,x,a[l]]
	for(int l=0;l<=n;++l) for(int r=0;r<=n;++r) for(int x=1;x<=m;++x) {
		f[l][r][x]=(l==r&&a[l]<=x?1:-inf);
		g[l][r][x]=(l==r&&x<=a[l]?1:-inf);
	}
	for(int l=n;l>=0;--l) {
		for(int i=0,v=-1;i<=n;++i) pre[i]=v,v=(a[i]==a[l]?i:v);
		for(int i=n,v=n+1;i>=0;--i) suf[i]=v,v=(a[i]==a[l]?i:v);
		for(int r=l+1;r<=n;++r) {
			for(int i=l;i<=r;++i) {
				if(a[i]>=a[l]) {
					if(l<=pre[i]) upd(f[l][r][a[i]],f[l][pre[i]][a[i]]+g[i][r][a[l]]);
					if(suf[i]<=r) upd(f[l][r][a[i]],f[l][i][a[i]]+f[suf[i]][r][a[i]]);
				}
				if(a[i]<=a[l]) {
					if(l<=pre[i]) upd(g[l][r][a[i]],g[l][pre[i]][a[i]]+f[i][r][a[l]]);
					if(suf[i]<=r) upd(g[l][r][a[i]],g[l][i][a[i]]+g[suf[i]][r][a[i]]);
				}
			}
			for(int x=2;x<=m;++x) upd(f[l][r][x],f[l][r][x-1]);
			for(int x=m;x>=2;--x) upd(g[l][r][x-1],g[l][r][x]);
		}
	}
}
int dp[2][2][MAXN][MAXN][MAXN];
void solve() {
	scanf("%d",&n),w[1]=a[0]=0,m=1;
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]),a[i]+=a[i-1],w[++m]=a[i];
	sort(w+1,w+m+1),m=unique(w+1,w+m+1)-w-1;
	for(int i=0;i<=n;++i) a[i]=lower_bound(w+1,w+m+1,a[i])-w;
	DP(dp[0][0],dp[0][1]);
	reverse(a,a+n+1);
	DP(dp[1][0],dp[1][1]);
	reverse(a,a+n+1);
	int ans=1;
	for(int x=0;x<=n;++x) for(int y=x+1;y<=n;++y) {
		upd(ans,dp[1][a[x]>a[y]][n-x][n][a[y]]+dp[0][a[x]<=a[y]][y][n][a[x]]-1);
	}
	printf("%d\n",ans);
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



Round #43 - 2025.1.8

A. [CF2048G] Kevin and Matrices

Problem Link

题目大意

给定 n×m 矩阵,填入 [1,v] 使得每行最大值的最小值小于等于每列最小值的最大值。

数据范围:nv106,m109

思路分析

可以反面考虑,统计有多少矩阵每行最大值 ri 都大于每列最小值 cj

然后接着容斥,钦定若干格子 (i,j) 满足 ricj,由于 ai,jricjai,j,因此 ai,j=ri=cj

因此当我们钦定的格子包含 xy 列,则这个 x×y 矩阵内的元素相等,枚举其取值,方案数为 v(nx)(my)kx(my)(vk+1)(nx)y

直接枚举 (x,y),求对应的容斥系数,即 x×y 矩阵每行每列至少钦定一个格子的容斥系数,容斥每行是否非空得到系数为 (1)x+y+1

此时答案为:

x,y,k(1)x+y(nx)(my)v(nx)(my)kx(my)(vk+1)(nx)y

y 一维提出来,用二项式定理化简得到:

x,k(1)x(nx)(vnxkx(vk+1)nx)m

直接计算即可。

时间复杂度 O(nvlogP)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll inv[MAXN];
void solve() {
	ll n,m,v;
	scanf("%lld%lld%lld",&n,&m,&v);
	ll c=1,s=0;
	for(int i=1;i<=n;++i) {
		c=c*(n-i+1)%MOD*inv[i]%MOD;
		for(int k=1;k<=v;++k) {
			ll b=(ksm(k,i)*ksm(v,n-i)+MOD-ksm(v-k+1,n-i))%MOD;
			b=(ksm(b,m)-ksm(k,i*m)*ksm(v,(n-i)*m))%MOD;
			s=(s+(i&1?MOD-c:c)*(b+MOD))%MOD;
		}
	}
	printf("%lld\n",s);
}
signed main() {
	inv[1]=1;
	for(int i=2;i<MAXN;++i) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	int _;
	scanf("%d",&_);
	while(_--) solve();
	return 0;
}



*B. [CF2048H] Kevin and Strange Operation

Problem Link

题目大意

给定长度为 n 的 01 串 s,每次操作可以选定一个 i,然后把 s1si 替换成 max(s1,s2),,max(si1,si),求能得到多少本质不同字符串。

数据范围:n106

思路分析

可以发现最终的每个字符都对应原串一段区间的最大值,不妨设新串每个字符对应 maxs[li,ri]

观察一次操作后每个 (li,ri) 的变化情况,对于 j[1,i)(lj,rj) 会变成 (lj,rj+1)

仅观察 l 数组,相当于删除 li,仅观察 r 数组,相当于删除 r1,因此原操作就是删除 li,r1 并重组所有 (l,r)

那么在 k 次操作之后,r 数组一定是 [k+1,k+2,,n],而 l 数组可以是 [1,2,,n] 的任意长度为 nk 的子序列。

翻转原串,此时 l=[1,,nk]r 是任意严格递增序列,因此我们判断一个字符串是否合法,只需要贪心地每次取最小合法的 r

那么可以 dp:fi,j 表示选出前 i 个字符,且 ri=j 的方案数。

如果 si=0j<p,那么 fi,j 转移到 fi+1,p,其中 p 是第一个大于 isp=1 的位置,否则直接转移到 fi+1,j+1

需要一个支持整体平移区间求和的数据结构,直接树状数组维护。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
char s[MAXN];
int n,nxt[MAXN];
struct FenwickTree {
	int dx,tr[MAXN*2],s;
	void init() { fill(tr,tr+2*n+1,0); }
	void add(int x,int v) { for(x+=dx;x<=2*n;x+=x&-x) tr[x]=(tr[x]+v)%MOD; }
	int qry(int x) { for(s=0,x+=dx;x;x&=x-1) s=(s+tr[x])%MOD; return s; }
}	F;
void solve() {
	cin>>(s+1),F.dx=n=strlen(s+1);
	reverse(s+1,s+n+1);
	F.add(0,1);
	int ans=0;
	nxt[n+1]=n+1;
	for(int i=n;i>=1;--i) nxt[i]=(s[i]=='1'?i:nxt[i+1]);
	for(int i=1;i<=n;++i) {
		--F.dx;
		if(s[i]=='0'&&nxt[i]<=n) {
			F.add(nxt[i],(F.qry(nxt[i]-1)+MOD-F.qry(i-1))%MOD);
		}
		ans=(ans+F.qry(n))%MOD;
	}
	cout<<ans<<"\n",F.init();
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



C. [CF2048I1] Kevin and Puzzle (Easy Version)

Problem Link

题目大意

给定长度为 n 的串 si,构造 a1an 使得 si=La[1,i1] 的颜色数 =aisi=Ra[i+1,n] 的颜色数 =ai

数据范围:n2×105

思路分析

从一些简易的情形入手,如果 s1=L,则 a1=0,如果 sn=R,那么 an=0

因此可以分讨 (s1,sn) 的情况:

  • (s1,sn)=(L,R):那么 a1=an=0,且 a2n1 中没有 0,因此可以看成给 a2n1 直接 +1
  • (s1,sn)=(L,L)/(R,R):不妨设 s1=L,则 a1=0,设 an=x,那么构造的时候 a[2,n1] 必须包含 1x1 的所有数。
  • (s1,sn)=(R,L):很显然 a 中没有 0,一种可能的构造是所有 ai=1

分析上述条件,导出矛盾当且仅当 (s1,sn)=(L,L),然后 s2n1 中出现了一对 (R,L),此时不能保证 1x1 的所有数都出现过。

否则直接按照上述过程递归构造即可。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN],ok;
char s[MAXN];
int dfs(int l,int r,int d,int x) {
	if(l>r) return 0;
	if(l==r) return a[l]=x;
	if(s[l]==s[r]) {
		a[l]=x,a[r]=max(x,dfs(l+1,r-1,1,x+1))+1;
		if(s[r]=='R') swap(a[l],a[r]);
		return max(a[l],a[r]);
	}
	if(s[l]=='L') return a[l]=a[r]=x,max(x,dfs(l+1,r-1,d,x+1));
	ok&=d!=1,fill(a+l,a+r+1,x+1);
	return x+1;
}
void solve() {
	cin>>n,ok=1;
	for(int i=1;i<=n;++i) cin>>s[i],a[i]=0;
	dfs(1,n,-1,0);
	if(!ok) cout<<"-1\n";
	else for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



D. [CF2057F] Formation

Problem Link

题目大意

给定 a1an,其是好的当且仅当所有 ai2ai1q 次询问 k,求出给所有 ai 总共增加 k 的值,最大化 maxai

数据范围:n,q5×104,ai,k109

思路分析

很显然可以二分,求出 aix 的最小代价 fi(x),判断 minifi(x) 是否 k 即可。

观察 fi(x) 的形式,发现我们只会修改一个区间 a(ij,i],如果 j 确定,那么 a(ij,i] 就是一次函数,并且 j 很显然是 O(logV) 级别。

所以 fi(x) 实际上是一个 O(logV) 段的一次函数,并且对于相同的 j,每个 fi(x) 这一段的斜率都相等,就是 t=0j1x2t

所以我们对于每个 j,维护所有 fi(x) 这一段中常数项的最小值即可。

因此我们先预处理出每个 a(ij,i] 对应的常数项,以及其定义域,然后把询问离线下来,动态维护每个 j 对应的最小的 fi(x)

fi(x) 的定义域是基于 x 的,不难发现可以把其定义域修改到关于 k 的范围上,然后就可以扫描线维护了。

时间复杂度 O(nlogVlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e4+5,mx=2e9;
const ll inf=1e18;
ll a[MAXN],ans[MAXN];
struct Heap {
	priority_queue <ll> qi,qo;
	void ins(ll x) { qi.push(x); }
	void ers(ll x) { qo.push(x); }
	ll top() {
		while(qi.size()&&qo.size()&&qi.top()==qo.top()) qi.pop(),qo.pop();
		return qi.size()?qi.top():-inf;
	}
}	f[32];
ll qry(ll x) {
	ll z=inf,s=0;
	for(int i=1;i<=30;++i) s+=x,x=(x+1)/2,z=min(z,s-f[i].top());
	return z;
}
void solve() {
	int n,q;
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	vector <array<ll,3>> op;
	for(int i=1;i<=n;++i) {
		ll s=0,l=0;
		for(int j=1;j<=30&&j<=i;++j) {
			s+=a[i-j+1];
			ll r=(i==j?inf:2ll*a[i-j]*((1<<j)-1)-s);
			op.push_back({l,-j,s}),op.push_back({r,j,s}),l=r;
		}
	}
	for(int i=1,k;i<=q;++i) cin>>k,op.push_back({k,0,i});
	sort(op.begin(),op.end());
	for(auto i:op) {
		if(i[1]<0) f[-i[1]].ins(i[2]);
		else if(i[1]>0) f[i[1]].ers(i[2]);
		else {
			ll l=0,r=mx,p=0;
			while(l<=r) {
				ll mid=(l+r)>>1;
				if(qry(mid)<=i[0]) p=mid,l=mid+1;
				else r=mid-1;
			}
			ans[i[2]]=p;
		}
	}
	for(int i=1;i<=q;++i) cout<<ans[i]<<" \n"[i==q];
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



E. [CF2057G] Secret Message

Problem Link

题目大意

n×m 网格上给出若干个格子,选择其中的若干个,使得每个未被选择格子与某个选择的格子四联通。

给出一种选择格子数 s+p5 的方案,其中 s 是格子个数,p 是格子构成图形的周长。

数据范围:n×m106

思路分析

这种问题可以考虑鸽巢原理,即构造 5 个方案,选出格子数总和为 s+p,取出最小的一种肯定满足题意。

当网格为无穷大网格时,我们要恰好选出 15 的格子,即任意两个选出的格子不四联通,可以按 (i+2j)mod5 分类,选出某类的所有格子一定能覆盖整个网格。

回到原问题,对于每个 r[0,5),取出所有 (i+2j)mod5=r 的格子,如果该格子可以选就直接选择。

此时还会剩下一些格子未被覆盖,那么这个格子一定在边界上,这条边界外的格子一定满足 (i+2j)mod5=r

此时每条边界只会在恰某个 r 中产生一次贡献,直接选择这些未覆盖的格子,此时五种方案的总代价恰好为 s+p,直接取最小值。

时间复杂度 O(nm)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+5;
string s[MAXN];
vector <int> id[MAXN];
int G[MAXN][7];
bool vis[MAXN];
int n,m,q;
void link(int u,int v) { G[u][++G[u][0]]=v,G[v][++G[v][0]]=u; }
void upd(vector<int>&f) {
	memset(vis,0,q+1);
	for(int i:f) for(int j=1;j<=G[i][0];++j) vis[G[i][j]]=true;
	for(int i=1;i<=q;++i) if(!vis[i]) f.push_back(i);
}
vector <int> f[5];
void solve() {
	cin>>n>>m,q=0;
	for(int i=0;i<n;++i) {
		cin>>s[i],id[i].resize(m,0);
		for(int j=0;j<m;++j) if(s[i][j]=='#') {
			id[i][j]=++q,G[q][G[q][0]=1]=q;
			if(i>0&&s[i-1][j]=='#') link(q,id[i-1][j]);
			if(j>0&&s[i][j-1]=='#') link(q,id[i][j-1]);
			f[(i+2*j)%5].push_back(q);
		}
	}
	int x=0;
	for(int r:{0,1,2,3,4}) {
		upd(f[r]);
		if(f[r].size()<f[x].size()) x=r;
	}
	memset(vis,0,q+1);
	for(int i:f[x]) vis[i]=true;
	for(int i=0;i<n;++i) {
		for(int j=0;j<m;++j) if(vis[id[i][j]]) s[i][j]='S';
		cout<<s[i]<<"\n";
		string().swap(s[i]),vector<int>().swap(id[i]);
	}
	for(int r:{0,1,2,3,4}) f[r].clear();
}
signed main() {
	for(int r:{0,1,2,3,4}) f[r].reserve(MAXN);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*F. [CF2057H] Coffee Break

Problem Link

题目大意

给定 a1an,一次对 i 的操作会令 aiaimod2,ai1ai1+ai2,ai+1ai+1+ai2,对于每个 k[1,n] 求出若干次操作后 ak 能达到的最大值。

思路分析

根据贪心,我们肯定不可能操作 ak,并且其他的 ai 如果 2 肯定会操作。

那么我们的操作实际上和 aiai2,ai1ai1+1,ai+1ai+1+1 是等价的,因为我们肯定会把所有的 ai 操作到 1

那么动态维护 a[1,i] 最多能向 ai+1 传递多少权值,然后翻转序列再做一遍。

考虑 i1i 的转移:先操作 a1i1 直到不能操作,然后加入 ai

找到 a1i1 中最后一个 0,设为 aj,此时序列为 [0,1,1,,ai],如果我们操作一次 ai,那么接下来会依次操作 i1,i2,j+1,序列变为 [1,0,1,,ai1],并且令 ai+1 加一。

不断进行这个操作,发现我们可以进行 ij 轮,会使得 aji1=1ai 减去 ij+1ai+1 加上 ij

那么用一个栈动态维护当前剩下的所有 0 的位置,手动模拟上述过程。

特殊处理 j=0 的情况,此时相当于栈中有 j=0 的元素,快速计算操作总轮数即可。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h> 
#define ll long long
using namespace std;
const int MAXN=1e6+5;
int n,st[MAXN];
ll a[MAXN],b[MAXN],f[MAXN];
void sol(bool op) {
	for(int i=1,tp=0;i<=n;++i) {
		ll z=0;
		while(tp) {
			int d=i-st[tp];
			if(b[i]>d) b[i]-=d+1,z+=d,--tp;
			else break;
		}
		if(!tp) {
			ll c=b[i]/(i+1);
			b[i]-=(i+1)*c,z+=i*c;
		}
		if(b[i]>1) {
			z+=b[i]-1;
			if(tp) st[tp]+=b[i]-1;
			else st[++tp]=b[i]-1;
		} else if(!b[i]) st[++tp]=i;
		b[i+1]+=z,f[op?n-i:i+1]+=z;
	}
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i],f[i]=a[i];
	copy(a+1,a+n+1,b+1);
	sol(0);
	copy(a+1,a+n+1,b+1);
	reverse(b+1,b+n+1);
	sol(1);
	for(int i=1;i<=n;++i) cout<<f[i]<<" \n"[i==n];
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}




Round #44 - 2025.1.9

*A. [CF2035G2] Go Learn!

Problem Link

题目大意

给定 m 个限制 (x,y),表示在 a 序列上二分第一个 y 的位置会得到 xa 序列不一定要有序)。

删除尽可能少的限制使得存在一个 a 序列满足上述限制,并求这样的 a 个数。

数据范围:n,m3×105,所有 x 两两不同,y 两两不同。

思路分析

很显然建立线段树,那么每个限制对应线段树上一条到叶子的路径,对每个节点 mid 的权值有限制,即大于等于左子树的最大权值,小于右子树的最小权值。

对于两个限制 xi<xj,如果 yi>yj,那么他们线段树上的 LCA 的权值无法构造。

因此保留的限制必须满足 xi<xj,yi<yj,很显然只要不存在 x>1,y=1 的限制,就一定能构造出对应的方案。

把这些不可能满足的限制删除,第一问的答案就是 LIS。

然后考虑第二问,把所有限制按 yi 排序,逐个插入线段树,求出对应的方案数 fi

假设限制 i 的前驱是限制 j,那么 xixj 的路径上的点的 amid 的取值范围都已经确定。

注意到除了 LCA,其他的点的取值范围只和 yi,yj 其中一个有关系。

因此我们动态维护每个 LCA 子树内的可能的 j 的方案数之和,以及对应 yj 之和。

注意特殊处理 LCAmid 恰好是 j 的情况,此时 amid 取值已经确定。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5,MOD=998244353,inf=1e9;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
struct info {
	int x; ll w;
	inline friend info operator +(const info &u,const info &v) {
		return u.x^v.x?(u.x>v.x?u:v):info{u.x,(u.w+v.w)%MOD};
	}
	inline info operator -(const info &o) const { return {x,(w+MOD-o.w)%MOD}; }
	inline info operator *(const ll &o) const { return {x,w*o%MOD}; }
}	f[MAXN],s1[MAXN],s2[MAXN];
vector <int> L[MAXN],R[MAXN];
int n,m,a[MAXN],p[MAXN];
void solve() {
	scanf("%d%d",&n,&m);
	ll inv=ksm(n);
	for(int i=1,x,y;i<=m;++i) {
		scanf("%d%d",&x,&y);
		if(x==1||y>1) a[x]=y,p[y]=x;
	}
	for(int i=1;i<=n;++i) {
		f[i]=s1[i]=s2[i]={-inf,1};
		for(int l=1,r=n;l<r;) {
			int j=(l+r)>>1;
			if(i<=j) {
				r=j;
				if(i!=j) R[i].push_back(j);
			} else l=j+1,L[i].push_back(j);
		}
		reverse(L[i].begin(),L[i].end());
		reverse(R[i].begin(),R[i].end());
	}
	info ans={0,1};
	for(int i=1;i<=n;++i) if(p[i]) {
		int x=p[i]; ll vl=1,wys=(i-1)*inv%MOD;
		for(int j:L[x]) {
			f[x]=f[x]+(s1[j]*i-s2[j])*vl*inv;
			if(a[j]) f[x]=f[x]+f[j]*vl;
			vl=vl*wys%MOD;
		}
		f[x]=f[x]+info{0,vl},++f[x].x;
		vl=1,wys=(n-i+1)*inv%MOD;
		for(int j:R[x]) {
			s1[j]=s1[j]+f[x]*vl;
			s2[j]=s2[j]+f[x]*vl*i;
			vl=vl*wys%MOD;
		}
		ans=ans+f[x]*vl;
	}
	printf("%d %lld\n",m-ans.x,ans.w*ksm(n,n-ans.x)%MOD);
	for(int i=1;i<=n;++i) L[i].clear(),R[i].clear(),a[i]=p[i]=0;
}
signed main() {
	int _; scanf("%d",&_);
	while(_--) solve();
	return 0;
}



*B. [CF2035H] Peak Productivity Forces

Problem Link

题目大意

给定两个 n 阶排列 a,b,每次操作可以选择一个 i,然后把 a[1,i1],a[i+1,n] 分别向右循环移位一位,构造一组 2n 次操作以内使得 a=b 的操作序列。

数据范围:n5×105

思路分析

很显然可以转成给 a 排序的问题。

这种用循环移位还原排列的问题,可以维护一个 [x,x+1,,n] 的子串在序列开头,然后每次把 x1 放到序列开头。

如果 x1 的位置 p<n,那么直接操作 p+1 即可。

否则我们先把 x2 放到开头,如果此时依然有 p=n,那么我们操作位置 1 就能让开头变成 [x2,x1],否则我们操作 p+1,此时开头变为 [x1,x2]

那么我们按照这个方式构造到 x=1,此时整个排列几乎有序,只有若干个 x 使得 ax+1=x,ax=x+1

考虑如何复原这部分,保证 an=n,然后不断操作位置 n,那么 a[1,n1] 会不断循环移位 n1 次。

如果 an2=an1+1,那么此时改为操作 n1n 即可交换这两个元素。

用上述方式还原排列,由于我们要保证 an=n,因此第一部分至多操作 n+1 次,第二部分操作 n1 次,总次数 2n

然后我们需要一个数据结构支持快速维护上述操作,注意到每次操作实际上会把绝大部分元素向右循环移位,只会单点修改 O(1) 个位置,因此维护一个支持循环移位的数组即可。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,A[MAXN],B[MAXN],z[MAXN];
struct arr {
	int tg,p[MAXN],a[MAXN*3];
	void init() { tg=0,fill(p+1,p+n+1,0),fill(a+1,a+3*n+1,0); }
	inline int operator [](int i) { return a[i+2*n-tg]; }
	inline int operator ()(int x) { return p[x]+tg; }
	inline void set(int i,int x) { p[x]=i-tg,a[i+2*n-tg]=x; }
}	a;
void opr(int i) {
	int l=(i>1?a[i-1]:0),x=a[i],r=(i<n?a[n]:0);
	++a.tg,a.set(i,x),cout<<i<<" ";
	if(l) a.set(1,l);
	if(r) a.set(i+1,r);
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) cin>>A[i];
	for(int i=1;i<=n;++i) cin>>B[i],z[B[i]]=i;
	if(n==1) return cout<<"0\n\n",void();
	if(n==2) return cout<<(A[1]==B[1]?"0\n\n":"-1\n"),void();
	for(int i=1;i<=n;++i) a.set(i,z[A[i]]);
	if(a(n)<n) cout<<2*n-1<<"\n",opr(a(n)+1);
	else cout<<2*n<<"\n",opr(1),opr(3);
	for(int x=n-1;x>=1;--x) {
		int p=a(x);
		if(p<n) opr(p+1);
		else if(x==1) opr(1);
		else opr(a(x-1)+1),opr(a(x)==n?1:a(x)+1),--x;
	}
	for(int i=1;i<n;opr(n),++i) if(i<n-1&&a[n-2]==a[n-1]+1) opr(n-1),++i;
	cout<<"\n",a.init();
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}




*C. [CF2039F2] Shohag Loves Counting

Problem Link

题目大意

给定 m,求有多少值域 [1,m] 的序列 a1an 满足:gcd(S1)gcd(Sn) 互不相同,其中 Si 表示所有长度为 i 的子区间的最大值构成的集合。

T 组数据。

数据范围:m,T106

思路分析

首先区间最大值问题建出笛卡尔树,那么 Si 就是子树 i 的节点构成的集合。

由于 gcd(Si1)gcd(Si) 所以 Si1Si,所以对于每个 i,笛卡尔树上都有一个大小恰为 i 的子树。

那么笛卡尔树一定是一条链,我们只关心链的权值序列,对应的序列很显然有 2n1 种。

那么我们现在要计数值域 [1,m] 的递减序列满足每个前缀 gcd 不同。

fi 表示前缀 gcd=i 的方案数,从大到小枚举 x=n1 尝试填入序列末尾,转移为 2fjfgcd(i,x)

这个形式难以优化,用莫比乌斯反演展开:

fij>i2[gcd(j,x)=i]fjj>i2fjidj,idxμ(d)idxμ(d)2idjfj

si 表示 ijfj,则 fikxμ(x/k)2sk

此时枚举 (i,k,x) 由于 ik,kx,因此复杂度 O(mlog2m)

那么我们得到一个 O(Tmlog2m) 的做法,但 x 要倒序枚举,因此不同的 m 不能统一求答案。

尝试正序枚举 x,观察这个 dp 的本质:可以把 1,fi,si,ans 看成 2n+2 个点,那么 dp 就是在他们之间转移。

每个 x 实际上就是在这些点之间连一些带权的边,而答案就是 1ans 的所有路径权值乘积之和,要求依次经过 x=m1 的边。

如果我们正序枚举 x,那么要依次处理 x=1m 的边,这是简单的,把图的所有边都取反,计数 ans1 的路径权值乘积之和即可。

实际上这就是转置原理,或者说原 dp 中令 xx+ky 则转置后令 yy+kx

那么做转置后的 dp 就能预处理出所有 m 的答案。

时间复杂度 O(mlog2m+T)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
vector <int> pr;
bool isc[MAXN];
int mu[MAXN],f[MAXN],g[MAXN],s[MAXN],rs[MAXN];
int a[MAXN*20],l[MAXN],r[MAXN];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline void sub(int &x,const int &y) { x=(x>=y)?x-y:x+MOD-y; }
signed main() {
	const int n=1e6;
	mu[1]=1;
	for(int i=2;i<=n;++i) {
		if(!isc[i]) mu[i]=-1,pr.push_back(i);
		for(int p:pr) {
			if(i*p>n) break;
			isc[i*p]=true,mu[i*p]=-mu[i];
			if(i%p==0) { mu[i*p]=0; break; }
		}
	}
	for(int i=1;i<=n;++i) for(int j=i;j<=n;j+=i) ++r[j];
	for(int i=1;i<=n;++i) l[i]=r[i]+=r[i-1];
	for(int i=n;i>=1;--i) for(int j=i;j<=n;j+=i) a[--l[j]]=i;
	s[1]=1;
	int ans=0;
	for(int x=1;x<=n;++x) {
		for(int u=l[x],i;u<r[x];++u) {
			i=a[u],g[i]=f[i];
			for(int j=l[i];j<r[i];++j) add(g[i],s[a[j]]);
		}
		add(ans,g[x]),rs[x]=ans;
		for(int u=l[x],k;u<r[x];++u) {
			k=a[u];
			for(int v=l[k],i;v<r[k];++v) {
				i=a[v];
				if(mu[k/i]==1) add(s[k],g[i]),add(s[k],g[i]);
				else if(mu[k/i]==-1) sub(s[k],g[i]),sub(s[k],g[i]);
			}
		}
		for(int i=l[x];i<r[x];++i) sub(f[a[i]],g[a[i]]),sub(f[a[i]],g[a[i]]);
	}
	int _; scanf("%d",&_);
	for(int m;_--;) scanf("%d",&m),printf("%d\n",rs[m]);
	return 0;
}



*D. [CF2039G] Shohag Loves Pebae

Problem Link

题目大意

给定一棵 n 个点的树,给每个点赋 [1,m] 的权值,使得每条路径的长度都不是点权 LCM 的因数,且所有点权互质,求方案数。

数据范围:n106,m109

思路分析

观察题目的限制,实际上就是要求 u 的点权的最小质因子大于 u 的最长路长度。

不妨设最长路长度为 hu,那么方案数就是 fm,hu,其中 fi,j 表示 [1,i] 中最小质因子 >j 的数个数,做一个类似 Min25 筛的过程,只处理到 pkj 的部分即可。

但是这样无法满足点权互质的限制,莫比乌斯反演得到方案数为 dμ(d)fm/d,hu

很显然整除分块,那么我们要算 μ(d) 的区间和且要求 d 的最小质因子 >maxhu,这也是类似地仿照 Min25 筛的过程解决即可。

然后我们要求出所有 fm/d,hu,此时有 O(nm) 组询问,直接计算肯定无法通过。

注意到根据直径的性质 hu12maxh,所以当 maxh2m 的时候,所有 hum,那么 au 不可能是合数,只可能是 1 或质数,且互质的条件变成不能同时填某个质数,这是容易解决的。

所以我们只要处理 maxh<2m 的情况,此时 hu[m,2m],那么本质不同的询问数不超过质数个数,即 π(m) 级别。

对于 hu 相同的元素要用快速幂计算答案,复杂度 O(mπ(m)logn)=O(mlogmlogn)

时间复杂度 O(mlognlogm)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
vector <int> G[MAXN];
int n,lim[MAXN],dep[MAXN],ot[MAXN];
void dfs1(int u,int fz) {
	for(int v:G[u]) if(v^fz) dfs1(v,u),dep[u]=max(dep[u],dep[v]+1);
}
void dfs2(int u,int fz) {
	int mx=ot[u],smx=0;
	for(int v:G[u]) if(v^fz) {
		if(mx<dep[v]+1) smx=mx,mx=dep[v]+1;
		else smx=max(smx,dep[v]+1);
	}
	lim[u]=mx+smx+1;
	for(int v:G[u]) if(v^fz) ot[v]=(dep[v]+1==mx?smx:mx)+1,dfs2(v,u);
}
int q,up,pr[MAXN],tot,qc[MAXN];
bool isc[MAXN];
ll m,B,vl[MAXN],id1[MAXN],id2[MAXN];
int id(ll x) { return x<=B?id1[x]:id2[m/x]; }
ll g[MAXN],f[MAXN],h[MAXN],dp[MAXN]; //cnt prime, sum mu, minp>?, answer
ll F(ll x) {
	if(x<=pr[up]) return x>=1;
	return f[id(x)]+up+1;
}
signed main() {
	scanf("%d%lld",&n,&m),B=sqrt(m);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	dfs1(1,0),dfs2(1,0);
	for(int i=2;i<MAXN;++i) {
		if(!isc[i]) pr[++tot]=i;
		for(int j=1;j<=tot&&i*pr[j]<MAXN;++j) {
			isc[i*pr[j]]=true;
			if(i%pr[j]==0) break;
		}
	}
	for(int i=1;i<=n;++i) lim[i]=upper_bound(pr+1,pr+tot+1,lim[i])-pr-1;
	up=*max_element(lim+1,lim+n+1);
	for(ll l=1,r;l<=m;l=r+1) {
		r=m/(m/l),vl[++q]=m/l;
		if(vl[q]<=B) id1[vl[q]]=q;
		else id2[m/vl[q]]=q;
	}
	for(int i=1;i<=q;++i) g[i]=vl[i]-1;
	for(int k=1;k<=tot;++k) {
		for(int i=1;i<=q&&1ll*pr[k]*pr[k]<=vl[i];++i) {
			g[i]-=g[id(vl[i]/pr[k])]-(k-1);
		}
	}
	for(int i=1;i<=q;++i) f[i]=-g[i];
	for(int k=tot;k>up;--k) {
		for(int i=1;i<=q&&1ll*pr[k]*pr[k]<=vl[i];++i) {
			f[i]-=f[id(vl[i]/pr[k])]+k;
		}
	}
	if(pr[up]>2*B) {
		ll ans=1;
		for(int i=1;i<=n;++i) ans=ans*(max(0ll,g[1]-lim[i])+1)%MOD;
		if(up<g[1]) ans=(ans-(g[1]-up))%MOD;
		printf("%lld\n",(ans+MOD)%MOD);
		return 0;
	}
	for(int i=1;i<=n;++i) ++qc[lim[i]];
	for(int i=1;i<=q;++i) h[i]=g[i],dp[i]=1;
	for(int k=tot;k>=1;--k) {
		if(qc[k]) {
			for(int i=1;i<=q&&vl[i]>=pr[k];++i) dp[i]=dp[i]*ksm((h[i]-k+1)%MOD,qc[k])%MOD;
		}
		for(int i=1;i<=q&&1ll*pr[k]*pr[k]<=vl[i];++i) {
			for(ll pw=pr[k];pw*pr[k]<=vl[i];pw=pw*pr[k]) {
				h[i]+=1+h[id(vl[i]/pw)]-k;
			}
		}
	}
	dp[q]=1;
	ll ans=0;
	for(ll l=1,r;l<=m;l=r+1) {
		r=m/(m/l);
		ans=(ans+(F(r)-F(l-1))%MOD*dp[id(m/l)])%MOD;
	}
	printf("%lld\n",(ans+MOD)%MOD);
	return 0;
}



*E. [CF2039H2] Cool Swap Walk

Problem Link

题目大意

给定一个 1n 排列 p,定义一次操作为选择一条 (1,1)(n,n) 的格路,对于经过的每个点 (i,j),依次交换 pi,pj,构造一种在 n+4 次操作内还原该序列的方法。

数据范围:n500

思路分析

首先这条格路的形态不固定时很难刻画,为了简化操作,我们肯定想让这条格路尽可能在主对角线上移动。

此时从 (x,x) 出发,如果向右再向下,相当于交换 ax,ax+1,如果向右两步再向下两步,相当于翻转 a[x,x+2]

观察发现这个过程中的 ax 一直是原来的 a1,因此我们一定会把 a1 放到序列的末尾。

忽略 a1 的影响,那么我们能做的实际上是选择若干不相交的 [i,i+1] 并交换 ai,ai+1

这就是奇偶排序的过程,因此我们可以在 n+O(1) 的步数内还原排列。

现在还要考虑上循环移位的问题,不妨钦定操作 n+4 轮,我们只要让最终的序列变成 [n3,n2,n1,n,1,2,,n4] 即可。

暴力模拟奇偶排序的过程。

事实上由于我们的操作不能选择 i=1,因此这个奇偶排序的实际花费可能超过 n+O(1) 的界,但在本题数据下足以通过,如果无法通过也可以随机 02 次操作之后奇偶排序。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=505;
int n,a[MAXN],id[MAXN],b[MAXN];
void solve() {
	cin>>n;
	for(int i=0;i<n;++i) cin>>a[i],id[i]=i;
	if(n==2) return cout<<(a[0]<=a[1]?"0\n":"1\nRD\n"),void();
	sort(id,id+n,[&](int x,int y){ return a[x]<a[y]; });
	for(int i=0;i<n;++i) a[id[i]]=((i+4)%n+n)%n;
	cout<<n+4<<"\n";
	for(int i=0;i<n+4;++i) {
		for(int j=2;j<n;j+=2) {
			int x=(i+j-1)%n,y=(i+j)%n;
			if(y&&a[x]>a[y]) swap(a[x],a[y]),cout<<"RRDD";
			else cout<<"RDRD";
		}
		cout<<(n&1?"\n":"RD\n");
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



F. [CF2046D] For the Emperor!

Problem Link

题目大意

给定 n 个点 m 条边的有向图,第 i 个点上有 ai 个机器人,你可以激活若干个点,一个点被激活,上面的机器人就可以沿着图上的边走到其他节点并激活这些点,求激活全部点最少要手动激活多少个点。

数据范围:n200,m800

思路分析

首先强联通分量肯定可以缩点,转成 DAG 问题。

这种图上最优代价的问题肯定考虑网络流,最大流很显然无法描述本题模型,因此需要费用流。

首先我们要把每个点拆成 (li,ri) 两份并且钦定 liri 必须经过。

对于 DAG 上的边,直接连 rulv,我们需要判断的是每个点是否需要手动激活,以及如何在这个点上创造 ai 个机器人。

那么再建虚拟点 xixili 连容量为 1 费用为 1 的边表示手动激活,然后连 xiri 容量为 的边,sxi 连容量为 ai 的边即可手动控制容量。

钦定 liri 必须经过可以直接跑最小费用可行流,但一种好写的方法是把这条边的权值赋成 K,其中 K 是充分大的数,这样就能实现双关键字比较费用。

时间复杂度 O(n3m)

代码呈现

#include<bits/stdc++.h>
using namespace std;
namespace F {
const int MAXV=605,MAXE=1e4+5,inf=1e9;
struct Edge { int v,e,f,w; } G[MAXE];
int S,T,ec=1,hd[MAXV],dis[MAXV],pre[MAXV];
bool inq[MAXV];
void init() { ec=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int f,int w) { G[++ec]={v,hd[u],f,w},hd[u]=ec; }
void link(int u,int v,int f,int w) { adde(u,v,f,w),adde(v,u,0,-w); }
bool SPFA() {
	memset(dis,0x3f,sizeof(dis));
	memset(pre,0,sizeof(pre));
	memset(inq,false,sizeof(inq));
	queue <int> Q; Q.push(S),inq[S]=true,dis[S]=0;
	while(Q.size()) {
		int u=Q.front(); Q.pop(),inq[u]=false;
		for(int i=hd[u],v;i;i=G[i].e) if(G[i].f&&dis[v=G[i].v]>dis[u]+G[i].w) {
			dis[v]=dis[u]+G[i].w,pre[v]=i;
			if(!inq[v]) Q.push(v),inq[v]=true;
		}
	}
	return pre[T];
}
array<int,2> ssp() {
	int f=0,c=0;
	while(SPFA()) {
		int g=inf;
		for(int u=T;u!=S;u=G[pre[u]^1].v) g=min(g,G[pre[u]].f);
		f+=g,c+=g*dis[T];
		for(int u=T;u!=S;u=G[pre[u]^1].v) G[pre[u]].f-=g,G[pre[u]^1].f+=g;
	}
	return {f,c};
}
}
const int MAXN=205,inf=1e9;
int n,m,low[MAXN],dfn[MAXN],dcnt,stk[MAXN],tp,bl[MAXN],scnt;
bool ins[MAXN];
int a[MAXN],su[MAXN];
vector <int> G[MAXN];
void tarjan(int u) {
	low[u]=dfn[u]=++dcnt,stk[++tp]=u,ins[u]=true;
	for(int v:G[u]) {
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]) {
		++scnt;
		while(ins[u]) bl[stk[tp]]=scnt,ins[stk[tp--]]=false;
	}
}
const int V=1000;
void solve() {
	scanf("%d%d",&n,&m),F::init();
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back(v);
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
	for(int i=1;i<=n;++i) {
		su[bl[i]]+=a[i];
	}
	int s=F::S=3*scnt+1,t=F::T=3*scnt+2;
	auto L=[&](int x) { return x+scnt; };
	auto R=[&](int x) { return x+2*scnt; };
	for(int i=1;i<=scnt;++i) {
		if(su[i]) {
			F::link(s,i,su[i],0);
			F::link(i,L(i),1,1);
			F::link(i,R(i),inf,0);
		}
		F::link(L(i),R(i),1,-V);
		F::link(L(i),R(i),inf,0);
		F::link(R(i),t,inf,0);
	}
	for(int u=1;u<=n;++u) for(int v:G[u]) if(bl[u]^bl[v]) F::link(R(bl[u]),L(bl[v]),inf,0);
	int z=F::ssp()[1]+V*scnt;
	printf("%d\n",z<=scnt?z:-1);
	for(int i=1;i<=n;++i) a[i]=su[i]=stk[i]=bl[i]=low[i]=dfn[i]=ins[i]=0,G[i].clear();
	scnt=dcnt=tp=0;
}
signed main() {
	int _; scanf("%d",&_);
	while(_--) solve();
	return 0;
}



G. [CF2046E2] Cheops and a Contest

Problem Link

题目大意

给定 n 个人,有属性 (ai,bi,si),对于一个题目 (d,t),他能做出来当且仅当 ai+[si=t](biai)d

已知 n 个人属于 m 个队,构造 5nt 不相等的题目,使得第 i1 队的每个人做出的题目数量严格大于第 i 队的每个人。

数据范围:n3×105

思路分析

先从 m=2 的情况出发。

假设第 i 个队列的人的 a 最小最大值为 li,ri

如果 l1>r2 肯定解决了。否则我们要想办法让 [l1,r2] 范围内第一队的人多通过一些题,这就要通过 si=t 来实现。

此时每个人只能多通过一个题,因此 r2 不能比 l1 多过题,首先一个题目如果难度在 (l1,r2] 范围,就会导致 r2l1 多做出一题。

其次如果有一个 [l1,r2] 范围内的第二队的人 si,那么 t=si 的题不能使得 d(l1,bi],否则这个人也会比 l1 多做出一题。

为了尽可能合法,这部分的题目肯定选择最大的合法难度。

然后我们在 l1,r2+1 的位置分别放足够多的题(t 不与任何 s 相等),使得其他的人不受影响(可以证明 2 题就足够完成区分)。

这样就完成了 m=2 的构造。

一般的情况完全一样,只不过要求变成所有 (li1,ri] 范围内不能有题目,以及颜色为 si 题目难度不能在 (lx,bi] 范围内(i 属于 x+1 组且 lxai)。

依然类似上面的过程,找到所有能放题目的位置贪心地放,最后检验是否合法即可。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
const int MAXN=3e5+5,inf=2e9;
int n,m,a[MAXN],b[MAXN],ty[MAXN],bl[MAXN];
int L[MAXN],R[MAXN],id[MAXN];
vector <int> g[MAXN];
void shrk(vector<array<int,2>>&vc) {
	sort(vc.begin(),vc.end());
	vector<array<int,2>> nw;
	for(auto i:vc) if(i[0]<=i[1]) {
		if(nw.empty()||i[0]>nw.back()[1]+1) nw.push_back(i);
		else chkmax(nw.back()[1],i[1]);
	}
	vc.swap(nw);
}
bool chk(const vector<array<int,2>>&vc,int &x) {
	int i=lower_bound(vc.begin(),vc.end(),array<int,2>{x+1,x+1})-vc.begin()-1;
	if(i>=0&&x<=vc[i][1]) return x=vc[i][0]-1,true;
	return false;
}
int lc[MAXN],rc[MAXN],mn[MAXN],mx[MAXN];
void solve() {
	cin>>n>>m;
	gp_hash_table <int,bool> uty;
	for(int i=1;i<=n;++i) cin>>a[i]>>b[i]>>ty[i],uty[ty[i]]=1,id[i]=i;
	vector <array<int,2>> ban,wys;
	for(int i=1,x;i<=m;++i) {
		cin>>x,g[i].resize(x),L[i]=inf,R[i]=-inf;
		for(int &j:g[i]) cin>>j,bl[j]=i,chkmin(L[i],a[j]),chkmax(R[i],a[j]);
		if(i>1&&R[i]>L[i-1]) ban.push_back({L[i-1]+1,R[i]});
	}
	shrk(ban);
	sort(id+1,id+n+1,[&](int i,int j){ return a[i]<a[j]; });
	lc[0]=m+1,rc[n+1]=0;
	for(int i=1;i<=n;++i) lc[i]=min(lc[i-1],bl[id[i]]);
	for(int i=n;i>=1;--i) rc[i]=max(rc[i+1],bl[id[i]]);
	for(int i=2,x=0;i<=n;++i) if(a[id[i-1]]<a[id[i]]&&rc[i]<=lc[i-1]) {
		for(int o=0;o<2;++o) {
			while(uty.find(++x)!=uty.end());
			wys.push_back({a[id[i]],x});
		}
	}
	gp_hash_table <int,int> d;
	gp_hash_table <int,vector<array<int,2>>> banc;
	for(int i=1;i<m;++i) for(int j:g[i]) {
		if(a[j]<=R[i+1]) {
			if(d.find(ty[j])==d.end()) d[ty[j]]=b[j];
			else chkmin(d[ty[j]],b[j]);
		}
	}
	for(int i=2;i<=m;++i) for(int j:g[i]) {
		if(a[j]>=L[i-1]) banc[ty[j]].push_back({L[i-1]+1,b[j]});
	}
	for(auto &it:d) {
		auto &cb=banc[it.first];
		int &x=it.second; shrk(cb);
		while(chk(ban,x)||chk(cb,x));
		wys.push_back({x,it.first});
	}
	sort(wys.begin(),wys.end());
	for(int i=1;i<=m;++i) mn[i]=inf,mx[i]=-inf;
	for(int i=1;i<=n;++i) {
		int vl=lower_bound(wys.begin(),wys.end(),array<int,2>{a[i]+1,-1})-wys.begin();
		if(d.find(ty[i])!=d.end()&&a[i]<d[ty[i]]&&d[ty[i]]<=b[i]) ++vl;
		chkmin(mn[bl[i]],vl),chkmax(mx[bl[i]],vl);
	}
	bool ok=true;
	for(int i=1;i<m;++i) ok&=mn[i]>mx[i+1];
	if(!ok) cout<<"-1\n";
	else {
		cout<<wys.size()<<"\n";
		for(auto z:wys) cout<<z[0]<<" "<<z[1]<<"\n";
	}
	for(int i=1;i<=m;++i) g[i].clear();
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*H. [CF2046F2] Yandex Cuneiform

Problem Link

题目大意

给定 Y,D,X,? 构成的长度为 n 的字符串 S,把 ? 填成 Y,D,X 的某一种,使得该 S 可以由如下方式构造:从空串开始,每次向串中插入一个 Y,D,X,并且每次插入之后 S 中不存在相邻相同的字符,给出方案。

数据范围:n2×105

思路分析

先解决不存在 ? 的情况,S 合法的必要条件是三种字符数量相等,且 S 中不存在相邻相同的字符。

假设 S 满足上述条件,我们尝试删除一个 Y,D,X,并且使得得到的新 S 也满足上述条件,那么就归纳地证明了该条件的充分性,并且翻转这个过程就能得到一组构造。

不妨假设 S1=Y,由于 Y,D,X 个数相等,因此 S 中至少能找到一个 DXXD 子串 S[i,i+1]

如果 Si1Si+2,直接删除 S1,Si,Si+1 就是合法构造。

否则一定有 Si1=Si+2=Y,且 Si2Si1=Si+2 删除 Si1,Si,Si+1 就是合法构造。

因此用链表动态维护 Sset 维护每种长度为 2 的子串的出现位置即可。

接下来回到原问题,我们要找到一个 S 满足相邻字符不同,且每种字符出现次数为 n/3

此时提取出所有极长 ? 连续段,可以发现可能的 Y,D 个数构成一个凸包,把每个连续段的凸包求闵可夫斯基和,并判断 (n/3,n/3) 是否在凸包内,然后还原即可。

但这种方法太难实现,我们通过打表发现 s[1,i] 中每种字符可能的出现次数都是一个区间,并且出现次数分别在三个区间内任取都是合法的。

那么直接 dp fi,j,k 表示 s[1,i]si=j 时字符 k 的出现次数对应的区间,转移是平凡的。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
const int MAXN=2e5+5;
int n,a[MAXN],pr[MAXN],sf[MAXN],wys[MAXN];
char s[MAXN];
set <int> ps[4];
struct FenwickTree {
	int tr[MAXN],s;
	void init() { fill(tr+1,tr+n+1,0); }
	void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]+=v; }
	int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
}	TR;
int L[MAXN][4][4],R[MAXN][4][4];
void solve() {
	scanf("%s",s+1),n=strlen(s+1);
	for(int i=1;i<=n;++i) a[i]=(s[i]=='?'?0:(s[i]=='D'?1:(s[i]=='X'?2:3)));
	for(int i=1;i<=n;++i) {
		memset(L[i],0x3f,sizeof(L[i]));
		memset(R[i],-0x3f,sizeof(R[i]));
		for(int j:{1,2,3}) if(!a[i]||a[i]==j) {
			for(int k:{1,2,3}) for(int c:{1,2,3}) if(c^j) {
				chkmin(L[i][j][k],L[i-1][c][k]+(j==k));
				chkmax(R[i][j][k],R[i-1][c][k]+(j==k));
			}
		}
	}
	int c[4]={0,n/3,n/3,n/3};
	for(int i=n;i>=1;--i) {
		a[i]=0;
		for(int j:{1,2,3}) if(i==n||j!=a[i+1]) {
			bool ok=true;
			for(int k:{1,2,3}) ok&=(L[i][j][k]<=c[k]&&c[k]<=R[i][j][k]);
			if(ok) { a[i]=j; break; }
		}
		if(!a[i]) return puts("NO"),void();
		s[i]=" DXY"[a[i]],--c[a[i]];
	}
	for(int i=1;i<=n;++i) pr[i]=i-1,sf[i]=i+1;
	pr[n+1]=n,sf[0]=1;
	for(int i=1;i<n;++i) ps[a[i]^a[i+1]].insert(i);
	int tp=n;
	auto del=[&](int x) {
		wys[tp--]=x;
		int l=pr[x],r=sf[x];
		if(l>=1) ps[a[l]^a[x]].erase(l);
		if(r<=n) ps[a[x]^a[r]].erase(x);
		if(l>=1&&r<=n) ps[a[l]^a[r]].insert(l);
		sf[l]=r,pr[r]=l;
	};
	for(int _=0;_<n/3;++_) {
		int y=*ps[a[sf[0]]].begin(),z=sf[y],x=(sf[z]<=n&&a[pr[y]]==a[sf[z]]?pr[y]:sf[0]);
		del(z),del(y),del(x);
	}
	printf("YES\n%s\n",s+1);
	for(int i=1;i<=n;++i) {
		printf("%c %d%c"," DXY"[a[wys[i]]],TR.qry(wys[i])," \n"[i%3==0]);
		TR.add(wys[i],1);
	}
	TR.init();
	for(int i:{1,2,3}) ps[i].clear();
}
signed main() {
	int _; scanf("%d",&_);
	while(_--) solve();
	return 0;
}
posted @   DaiRuiChen007  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示