20250409NOI模拟赛

20250409NOI模拟赛

T1.subsequence

题面:

有两个长度为 \(n\) 的由 \(\{0,1,2\}\) 构成的序列,求他们的最长不降公共子序列。\(n\leq 5\times 10^6\)

题解:

这不是一道 \(DP\)
\(As_i,Bs_i\) 为第一个串和第二个串中 \(1\) 的个数的前缀和。
考虑枚举答案中 \(0\) 的个数 \(x\)\(2\) 的个数 \(z\),可以的到两个序列中第 \(x\)\(0\) 的位置是 \((i,i')\),倒数 \(z\)\(2\) 的位置是 \((j,j')\),那么答案对 \(x+z+\min(As_j-As_i,Bs_{j'}-Bs_{i'})\)\(\max\),限制是 \(i\leq j,i'\leq j'\)。这样 \(n^2\) 是好做的。

如果想降低复杂度必须少枚举一个东西,我们假设枚举 \(x\),发现答案的贡献式中有 \(\min\) 是不好处理的,那么考虑钦定第一个串选择的 \(1\) 的个数小于等于第二个串,统计这个情况下的答案,另一种的答案只需要 \(\rm swap\) 两个串再做一遍就行了。

设最对于一个 \(x\)\(z\) 能取到满足 \(i\leq j,i'\leq j'\) 的最大的 \(z\)\(r_x\)\(O(n)\) 是好求的。
那么现在的贡献是 \(x+z+As_j-As_i=x-As_i+(z+As_j)\),限制是 \(z\leq r_x,As_j-As_i\leq Bs_{j'}-Bs_{i'}\),将第二个限制移项得 \(As_j-Bs_{j'}\leq As_i-Bs_{i'}\)。现在这转变成了一个二维数点问题,\(O(n\log n)\) 是好做的。

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

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

const int N=5e6+6;
int n,sa[N],sb[N],ans;
pair<int,int>R[N];
char a[N],b[N];

namespace tr{
	int c[N<<1],B;
	
	void add(int x,int v){
		x+=n+1;
		while(x<=B){
			c[x]=max(c[x],v);
			x+=x&-x;
		}
	}
	
	int query(int x){
		x+=n+1;
		int res=0;
		while(x>0){
			res=max(res,c[x]);
			x-=x&-x;
		}
		return res;
	}
	
	void clear(){
		for(int i=B;i>=1;i--) c[i]=0;
	}
}

void calc(){
	vector<int>Az,Bz,Ax,Bx;
	for(int i=n;i>=1;i--){
		if(a[i]=='2') Az.push_back(i);
		else if(a[i]=='0'){
			Ax.push_back(i);
			R[Ax.size()].first=Az.size();
		}
		
		if(b[i]=='2') Bz.push_back(i);
		else if(b[i]=='0'){
			Bx.push_back(i);
			R[Bx.size()].second=Bz.size();
		}
	}
	R[Ax.size()+1].first=Az.size();R[Bx.size()+1].second=Bz.size();
	for(int i=1;i<=n;i++){
		sa[i]=sa[i-1]+(a[i]=='1');
		sb[i]=sb[i-1]+(b[i]=='1');
	}
	tr::add(sa[n]-sb[n],sa[n]);
	int z=0;
	for(int x=min(Ax.size(),Bx.size());x>=0;x--){
		int lim=min(R[Ax.size()-x+1].first,R[Bx.size()-x+1].second);
		while(z<lim){
			int i=Az[z],j=Bz[z];
			z++;
			tr::add(sa[i]-sb[j],z+sa[i]);
		}
		if(x==0) ans=max(ans,tr::query(0));
		else{
			int X=Ax[Ax.size()-x],Y=Bx[Bx.size()-x];
			ans=max(ans,tr::query(sa[X]-sb[Y])-sa[X]+x);
		}
	}
	tr::clear();
}

void solve(){
	ans=0;
	scanf("%s%s",a+1,b+1);
	n=strlen(a+1);
	tr::B=n+n+1;
	calc();
	swap(a,b);
	calc();
	printf("%d\n",ans);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

T2.constructive

题面:

给定一个 \(n\) 个点 \(m\) 的有向图,给每个点赋一个整数值 \(val_i\in[1,n+1)\),要求 \(\forall (u\to v),val_u<val_v\),最小化 \(\sum_{(u\to v)}val_v-val_u\)。求最小值或判断无解。\(n\leq 300,m\leq 1500\)\(60pts\) 部分分 \(n\leq 30,m\leq 30\)

题解:

题目的答案是 \(\sum_i val_i\times (ind_i-oud_i)\)
\(60pts\) 的做法:
看数据范围想网络流。需要给每个点赋值有套路是每个点建 \(n\) 个点,第 \(i\) 的点建出来的 \(n\) 个点中的第 \(j\) 个点 \(ids(i,j)\) 表示 \(val_i\leq j\),割掉了 \(e=(ids(i,j)\to ids(i,j+1))\) 表示 \(val_i=j\)

连边 \(ids(i,j)\to ids(i,j+1)\),边权是 \(j\times (ind_i-oud_i)\),但是由于网络流不能有负权,所以可以设成 \(j\times ind_i+(n-j)\times oud_i\),最后再减掉。

对于原图的边 \(e=(x\to y)\) 建边 \(ids(x,i)\to ids(y,i+1)\),不允许割断。

\(S\) 连所有 \(ids(i,1)\)\(ids(i,n+1)\) 相当于是 \(T\),跑最小割。答案是 \(dinic()-nm\)

满分做法是线性规划然后费用流,可惜我不会。

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

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

const int N=305,M=1505;
const int inf=1e9+7;
int dfn[N],low[N],tim,nsc,n,m,val[N],ind[N],oud[N];
stack<int>st;
bool vis[N];
vector<int>G[N];

void tarjan(int x){
	vis[x]=1;st.push(x);
	dfn[x]=low[x]=++tim;
	for(int v:G[x])
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]) low[x]=min(low[x],dfn[v]);
	if(dfn[x]==low[x]){
		nsc++;
		while(!st.empty()){
			int v=st.top();
			vis[v]=0;
			st.pop();
			if(v==x) break;
		}
	}
}

namespace flow{
	int head[N*N],E[N*N],dep[N*N],cnt=1,S,T;
	struct edge{
		int v,nxt,w;
	}e[N+N*N+N+N*M<<1];
	
	int ids(int x,int y){
		return (x-1)*n+y;
	}
	
	void add(int u,int v,int w){
//		cout<<u<<' '<<v<<' '<<w<<endl;
		e[++cnt]={v,head[u],w};
		head[u]=cnt;
		e[++cnt]={u,head[v],0};
		head[v]=cnt;
	}
	
	bool bfs(){
		for(int i=S;i<=T;i++) dep[i]=0,E[i]=head[i];
		dep[S]=1;
		queue<int>q;
		q.push(S);
		while(!q.empty()){
			int x=q.front();
			q.pop();
			for(int i=head[x];i;i=e[i].nxt){
				int v=e[i].v;
				if(!dep[v]&&e[i].w){
					dep[v]=dep[x]+1;
					q.push(v);
					if(v==T) return 1;
				}
			}
		}
		return 0;
	}
	
	int dfs(int x=S,int W=inf){
		if(x==T) return W;
		int now=0;
		for(int i=E[x];i&&now<W;i=e[i].nxt){
			E[x]=i;int v=e[i].v;
			if(dep[v]==dep[x]+1&&e[i].w){
				int tmp=dfs(v,min(e[i].w,W-now));
				if(!tmp) dep[v]=-1;
				e[i].w-=tmp;
				e[i^1].w+=tmp;
				now+=tmp;
				if(now==W) return W;
			}
		}
		return now;
	}
	
	ll dinic(){
		ll ans=0; int x=0;
		while(bfs()) while(x=dfs()) ans+=x;
		return ans;
	}
	
	void calc(){
		S=0;T=n*n+1;
		for(int i=1;i<=n;i++){
			add(S,ids(i,1),inf);
			for(int j=1;j<n;j++) add(ids(i,j),ids(i,j+1),j*ind[i]+(n-j)*oud[i]);
			if(oud[i]==0) add(ids(i,n),T,n*ind[i]);
			else add(ids(i,n),T,inf);
			for(int v:G[i])
				for(int j=1;j<n;j++) add(ids(i,j),ids(v,j+1),inf);
		}
		printf("%lld\n",dinic()-n*m);
		queue<int>q;
		for(int i=S;i<=T;i++) dep[i]=0;
		dep[S]=1;
		q.push(S);
		while(!q.empty()){
			int x=q.front();
			q.pop();
			for(int i=head[x],v;i;i=e[i].nxt){
				v=e[i].v;
				if(e[i].w>0&&!dep[v]){
					dep[v]=1;
					q.push(v);
				}
			}
		}
		for(int i=1;i<=n;i++){
			if(dep[ids(i,n)]==1){
				val[i]=n;
				continue;
			}
			for(int j=n;j>1;j--)
				if(dep[ids(i,j-1)]==1&&dep[ids(i,j)]==0){
					val[i]=j-1;
					break;
				}
		}
		int mn=*min_element(val+1,val+1+n);
		for(int i=1;i<=n;i++) val[i]-=mn-1;
		for(int i=1;i<=n;i++) printf("%d ",val[i]);
		puts("");
		for(int i=S;i<=T;i++) head[i]=0;cnt=1;
	}
}
using flow::calc;

void solve(){
	n=read();m=read();
	for(int i=1;i<=n;i++) dfn[i]=low[i]=0,ind[i]=oud[i]=0;
	for(int i=1;i<=n;i++) vis[i]=0;
	for(int i=1;i<=n;i++) G[i].clear();
	while(!st.empty()) st.pop();
	tim=0;nsc=0;
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		G[u].push_back(v);
		ind[v]++;oud[u]++;
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	if(nsc<n){
		puts("NIE");
		return ;
	}
	calc();
}

int main(){
//	freopen("constructive.in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

T3.string

题面:

定义将一个串划分成 \(k\) 段的价值是这 \(k\) 段中字典序最大的段。
给定一个长为 \(n\) 的串 \(S\)\(q\) 次询问,每次询问给出 \(i,k\),回答将 \(S[i:n]\) 划分成至多 \(k\) 段的所有方案中最小的价值是多少。输出 \(l,r\) 表示最小的价值是 \(S[l:r]\) 这个字符串,多个答案输出 \(l\) 最小的。\(n,q\leq 10^6\)

题解:

先考虑如果没有后缀的限制,每次询问只给出 \(k\) 怎么做。
\(S\) \(\rm lyndon\) 分解,得 \(S=\sum_i s_i^{p_i}\),选别的位置分段一定不如选 \(\rm lyndon\) 串的开头优。
根据 \(\rm lyndon\) 分解的性质有 \(s_i>s_{i+1}\),所以直接将 \(S\) 分解成 \(S=s_1^{p_1}+S'\) 不影响答案,具体可以看答案的分讨结果。

  • \(k=1\),答案是 \(l=1,r=|S|\)

只分 \(1\) 段显然只能全选。

  • \(k\geq p_1+[S'\not= \emptyset]\),答案是 \(l=1,r=|s_1|\)

根据 \(\rm lyndon\) 分解,\(s_1>S'\)\(s_1^k>s_1^{k-1}\),所以将每个 \(s_1\) 单独分段,最大的就是 \(s_1\)

  • \(k\nmid p_1\)\(S'=\emptyset\) 时,答案是 \(l=1,r=\lceil\dfrac{p_1}{k}\rceil\times |s_1|\)

除了上述两种情况,在其他时候我们应该尽可能将 \(p_1\)\(s_1\) 平均分。设 \(d=\lceil\dfrac{p_1}{k}\rceil\)\(S\) 将分成若干段的 \(s_1^d\) 和若干 \(s_1^{d-1}\)\(S'\) 接到余下的某个 \(s_1^{d-1}\) 的后面,最大的仍然是 \(s_1^d\)。(因为 \(k\nmid p_1\),所以一定有 \(s_1^{d-1}\),或者说是 \(S'=\emptyset\) 不用接)

  • \(k\mid p_1\)\(S'\not=\emptyset\) 时,答案是 \(l=(k-1)\times \frac{p_1}{k}\times |s_1|+1,r=|S|\)

第三种情况在大部分时候是正确的,但在符合以上条件的特例时是不对的,因为这时可能没有 \(s_1^{d-1}\) 的串,那就只能接到某个 \(s_1^d\) 后面,所以答案是 \(s_1^d+S'\)

那现在询问可以离线,只需要考虑怎么求出每个后缀的 \(\rm lyndon\) 分解就行了。

正常的 \(Duval\) 算法是做不到这一点的,考虑用 \(SA\) 来做。(当然可以二分哈希代替)

\(SA\) 做正常的 \(\rm lyndon\) 分解怎么做?
\(\rm lyndon\) 串满足其所有后缀中它是最小的,那么求出 \(SA\) 之后从小到大遍历后缀 \(sa[i]\),如果没有被覆盖那就选择它是一个新的 \(\rm lyndon\) 串的开头,并覆盖 \([sa[i],n]\)

后缀怎么做?
设我们现在求出了 \([i+1,n]\) 的分解,现在加入一个后缀 \(S[i:n]\),他的排名是 \(rk[i]\),那么在 \(i+1\)\(\rm lyndon\) 分解中 \(rk[j]>rk[i]\) 的都不应该出现在 \(i\) 的分解中,而且以后都不会出现在 \(\rm lyndon\) 分解中。可以使用单调栈实现这一点。

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

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

const int N=1e6+5;
int rk[N],c[N],sa[N],id[N],tmp[N],n,m,f[N];
int h[N],lgn[N],st[21][N];
pair<int,int> ans[N];
vector<pair<int,int> >upd[N];
char s[N];

int find(int l,int r){
	if(l>r) swap(l,r);
	l++;
	int mn=lgn[r-l+1];
	return min(st[mn][l],st[mn][r-(1<<mn)+1]);
}

void SA(){
	m=26;
	for(int i=1;i<=n;i++) rk[i]=s[i]-'a'+1;
	for(int i=1;i<=n;i++) c[rk[i]]++;
	for(int i=1;i<=m;i++) c[i]+=c[i-1];
	for(int i=n;i>=1;i--) sa[c[rk[i]]--]=i;
	int p=0;
	for(int k=1;;k<<=1,m=p){
		int cnt=0;
		for(int i=n-k+1;i<=n;i++) id[++cnt]=i;
		for(int i=1;i<=n;i++) if(sa[i]>k) id[++cnt]=sa[i]-k;
		for(int i=1;i<=m;i++) c[i]=0;
		for(int i=1;i<=n;i++) c[rk[i]]++;
		for(int i=1;i<=m;i++) c[i]+=c[i-1];
		for(int i=n;i>=1;i--) sa[c[rk[id[i]]]--]=id[i];
		for(int i=1;i<=n;i++) tmp[i]=rk[i];
		p=0;
		for(int i=1;i<=n;i++){
			if(tmp[sa[i]]!=tmp[sa[i-1]]||tmp[sa[i]+k]!=tmp[sa[i-1]+k]) ++p;
			rk[sa[i]]=p;
		}
		if(p==n) break;
	}
	p=0;
	for(int i=1;i<=n;i++){
		if(rk[i]==1) continue;
		if(p) p--;
		int j=sa[rk[i]-1];
		while(i+p<=n&&j+p<=n&&s[i+p]==s[j+p]) p++;
		h[rk[i]]=p;
	}
	for(int i=2;i<=n;i++) lgn[i]=lgn[i>>1]+1;
	for(int i=1;i<=n;i++) st[0][i]=h[i];
	for(int j=1;j<=lgn[n];j++)
		for(int i=1;i+(1<<j)-1<=n;i++) 
			st[j][i]=min(st[j-1][i],st[j-1][i+(1<<j-1)]);
}

int main(){
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1); SA();
	int Q=read();
	for(int i=1;i<=Q;i++){
		int l=read(),k=read();
		upd[l].push_back({i,k});
	}
	vector<int>vec;
	vec.push_back(n+1);
	for(int i=n;i>=1;i--){
		while(vec.size()>1&&rk[i]<rk[vec.back()]) vec.pop_back();
		vec.push_back(i);
		f[i]=1;
		if(vec.size()>2){
			int x=i,y=vec.end()[-2],z=vec.end()[-3];
			if(y-x==z-y&&find(rk[x],rk[y])>=y-x) f[x]+=f[y];
		}
		for(auto o:upd[i]){
			int id,k; tie(id,k)=o;
			int now=min(f[i]+1,((int)vec.size())-1);
			if(k==1) ans[id]={i,n};
			else if(k>=now) ans[id]={i,vec.end()[-2]-1};
			else{
				int len=vec.end()[-2]-i;
				if(now==f[i]+1&&now%k==1) ans[id]={i+(now-1)/k*(k-1)*len,n};
				else ans[id]={i,i+((now-1)/k+1)*len-1};
			}
		}
	}
	for(int i=1;i<=Q;i++) printf("%d %d\n",ans[i].first,ans[i].second);
	return 0;
}

posted @ 2025-04-10 17:20  programmingysx  阅读(12)  评论(0)    收藏  举报
Title