20251213NOI模拟赛

20251213NOI模拟赛

T1.挑战NPC

题面:

\(n\) 个逻辑变量 \(x_1,\ldots,x_n\),其中每一个变量 \(x_i\in \{{\rm true,false}\}\)。我们定义一个如下形式的表达式为合法表达式:

\[(l_{1,1}\lor l_{1,2}\lor\ldots\lor l_{1,m_1})\land(l_{2,1}\lor l_{2,2}\lor\ldots\lor l_{2,m_2})\land\ldots\land(l_{c,1}\lor l_{c,2}\lor\ldots\lor l_{c,m_c}) \]

每一个 \(l_{i,j}\) 都是形如 \(x_k\)\(\neg x_k\) 的形式,表示 \(x_k\) 这个逻辑变量或 \(x_k\) 取反。
现在小 E 在研究一种特殊的合法表达式,这个表达式有如下性质:
1.不存在 \(i,j_1,j_2,k\),使得 \(l_{i,j_1}\)\(l_{i,j_2}\) 都是关于 \(x_k\) 的变量。
2.对于一个 \(i\),若存在 \(l_{i,j_1}\) 是关于 \(x_p\) 的变量,\(l_{i,j_2}\) 是关于 \(x_q\) 的变量,且 \(p<q\),则对于任意 \([p,q]\) 区间内的整数 \(k\),都存在 \(1\leq j\leq m_i\) 使得 \(l_{i,j}\) 是关于\(x_k\) 的变量。
小 E 不仅希望你求出解的数量。请你输出满足给定表达式为 true 的 \(x_1,x_2,\ldots,x_n\) 的解的数量。由于答案可能很大,你只需要输出答案对 \(10^9+7\) 取模的结果。\(n\leq 10^6\)

题解:

顺次决定 \(x_i\) 的值,每次会使得某些限制成立,需要维护的是到目前位置还没有成立的限制有哪些。记录这些没成立的限制中左端点最小的限制编号 \(k\),由此 \([l_k,i]\) 中每个 \(x_j\) 的取值是唯一确定的,所以一个限制 \(p\) 没有成立当且仅当它与 \(k\) 在区间 \([l_p,i]\) 的值都相同。
有一个 \(n^2\) 做法即记录 \(f_{i,k}\),找到所有 \(p\) 可以使用哈希。

65 分的代码
#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=3005,B=31,D=233,MOD=19491001,P=19260817,mod=1e9+7;
int L[N],R[N],n,m;
vector<int>a[N],vec[N];
struct node{
	ll x,y;
	friend bool operator == (const node x,const node y){return x.x==y.x&&x.y==y.y;}
	friend bool operator != (const node x,const node y){return x.x!=y.x||x.y!=y.y;}
	friend node operator + (const node x,const node y){
		node z={x.x+y.x,x.y+y.y};
		if(z.x>=MOD) z.x-=MOD; if(z.y>=P) z.y-=P;
		return z;
	}
	friend node operator - (const node x,const node y){
		node z={x.x-y.x,x.y-y.y};
		if(z.x<0) z.x+=MOD; if(z.y<0) z.y+=P;
		return z;
	}
	friend node operator * (const node x,const node y){return {x.x*y.x%MOD,x.y*y.y%P};}
	friend node operator * (const node x,int b){return {x.x*b%MOD,x.y*b%P};}
	node add(int b){ return {(x*B+b)%MOD,(y*D+b)%P}; }
}fac[N];
vector<node>has[N];
int f[N][N];

void Add(int &x,int y){
	x+=y;
	if(x>=mod) x-=mod;
}

void READ(){
	while(getchar()!='(');
	while(1){
		vector<pair<int,int> >tmp;
		while(1){
			char c=getchar();
			if(c==')') break;
			if(c=='~'){
				c=getchar();
				int s=0;
				c=getchar();
				while(c>='0'&&c<='9'){
					s=(s<<3)+(s<<1)+(c^48);
					c=getchar();
				}
				tmp.push_back({s,-1});
				if(c==')') break;
			}
			else if(c=='x'){
				int s=0;
				c=getchar();
				while(c>='0'&&c<='9'){
					s=(s<<3)+(s<<1)+(c^48);
					c=getchar();
				}
				tmp.push_back({s,1});
				if(c==')') break;
			}
		}
		sort(tmp.begin(),tmp.end());
		++m;
		L[m]=tmp.front().first; R[m]=tmp.back().first;
		for(pair<int,int> x:tmp) a[m].push_back(x.second+2);
		has[m].push_back({0,0});
		for(int x:a[m]){
			node p=has[m].back();
			has[m].push_back(p.add(x));
		}
		for(int i=L[m];i<=R[m];i++) vec[i].push_back(m);
		while(1){
			char c=getchar();
			if(c=='\n') return ;
			if(c=='(') break;
		}
	}
}

node query(int i,int l,int r){
	return has[i][r]-has[i][l-1]*fac[r-l+1];
}

bool check(int x,int y,int i){
	if(L[y]<L[x]) return 0;
	return query(x,L[y]-L[x]+1,i-L[x]+1)==query(y,1,i-L[y]+1);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();
	fac[0]={1,1}; for(int i=1;i<=n;i++) fac[i]=fac[i-1].add(0);
	READ();
//	cout<<m<<endl;
//	for(int i=1;i<=m;i++){
//		cout<<L[i]<<' '<<R[i]<<endl;	
//		for(int x:a[i]) cout<<x<<' ';cout<<endl;
//		for(node x:has[i]) cout<<x.x<<' '<<x.y<<' ';cout<<endl;
//	}
	f[0][0]=1;
	for(int i=0;i<n;i++){
		//get 0
		{
			//pre 0
			{
				int nv=0;
				bool fl=0;
				for(int x:vec[i+1])
					if(L[x]>i){
						if(R[x]==i+1&&a[x].back()!=1){
							fl=1;
							break;
						}
					}
				if(!fl){
					for(int x:vec[i+1])
						if(L[x]>i&&a[x][0]==3){
							nv=x;
							break;
						}
					Add(f[i+1][nv],f[i][0]);
				}
			}
			for(int j=1;j<=m;j++) if(f[i][j]){
				bool fl=0;
				int nv=0;
				for(int x:vec[i+1])
					if(check(j,x,i)){
						if(R[x]==i+1&&a[x].back()!=1){
							fl=1;
							break;
						}
						if(a[x][i+1-L[x]]!=1){
							if(!nv||L[x]<L[nv]) nv=x;
						}
					}
				if(!fl) Add(f[i+1][nv],f[i][j]);
			}
		}
		//1
		{
			//0
			{
				int nv=0;
				bool fl=0;
				for(int x:vec[i+1])
					if(L[x]>i){
						if(R[x]==i+1&&a[x].back()!=3){
							fl=1;
							break;
						}
					}
				if(!fl){
					for(int x:vec[i+1])
						if(L[x]>i&&a[x][0]==1){
							nv=x;
							break;
						}
					Add(f[i+1][nv],f[i][0]);
				}
			}
			for(int j=1;j<=m;j++) if(f[i][j]){
				bool fl=0;
				int nv=0;
				for(int x:vec[i+1])
					if(check(j,x,i)){
						if(R[x]==i+1&&a[x].back()!=3){
							fl=1;
							break;
						}
						if(a[x][i+1-L[x]]!=3){
							if(!nv||L[x]<L[nv]) nv=x;
						}
					}
				if(!fl) Add(f[i+1][nv],f[i][j]);
			}
		}
	}
	printf("%d",f[n][0]);
	return 0;
}

上述 DP 总状态数是 \(\sum m_i\) 的,复杂度在于转移。
考虑在转移 \(i\) 时,将所有串从 \(i\to l_j\) 插入 trie 树,如果 \(k\)\(i\) 位置仍不合法则状态还是 \(k\),否则转移到 \(k\) 在 trie 的祖先中最深的不合法的点。对于所有的 \(k\) 求出这样的点每次复杂度是 trie 大小,总复杂度 \(\sum m_i\)
现在问题在于如何维护 trie。需要支持在每个串前加入一个字符,并删除一些串。做法是将不删除的串按下一位是 \(0/1\) 分类建两颗虚树,然后新建新根连向两个虚树的根,因为我们不需要关心下面 trie 的边是 \(0/1\) 而只关心他们之间的祖先关系。上述操作复杂度都是 trie 大小,总复杂度 \(\sum m_i\)

代码
#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=1e6+5,mod=1e9+7,M=N*3;
int L[N],R[N],n,m,pos[N],tr[M];
vector<int>a[N],vec[N],e[M],G[M],nod,ids[M],tmp[M];
int f[N],g[N],nxt[M][2];
int tot,rt,rtx,rty;
bool ok[M][2],vis[M][2];

void Add(int &x,int y){
	x+=y;
	if(x>=mod) x-=mod;
}

void READ(){
	while(getchar()!='(');
	while(1){
		vector<pair<int,int> >tmp;
		while(1){
			char c=getchar();
			if(c==')') break;
			if(c=='~'){
				c=getchar();
				int s=0;
				c=getchar();
				while(c>='0'&&c<='9'){
					s=(s<<3)+(s<<1)+(c^48);
					c=getchar();
				}
				tmp.push_back({s,-1});
				if(c==')') break;
			}
			else if(c=='x'){
				int s=0;
				c=getchar();
				while(c>='0'&&c<='9'){
					s=(s<<3)+(s<<1)+(c^48);
					c=getchar();
				}
				tmp.push_back({s,1});
				if(c==')') break;
			}
		}
		sort(tmp.begin(),tmp.end());
		++m;
		L[m]=tmp.front().first; R[m]=tmp.back().first;
		for(pair<int,int> x:tmp) a[m].push_back(x.second==1?1:0);
		for(int i=L[m];i<=R[m];i++) vec[i].push_back(m);
		while(1){
			char c=getchar();
			if(c=='\n') return ;
			if(c=='(') break;
		}
	}
}

void cmin(int &x,int y){
	if(!x||(y&&L[x]>L[y])) x=y;
}

void dfs(int x){
	for(int v:e[x]){
		ok[v][0]|=ok[x][0];
		ok[v][1]|=ok[x][1];
		cmin(nxt[v][0],nxt[x][0]);
		cmin(nxt[v][1],nxt[x][1]);
		dfs(v);
	}
}

void dfz(int x){
	for(int v:e[x]){
		dfz(v);
		ok[x][0]|=ok[v][0];
		ok[x][1]|=ok[v][1];
	}
}

bool check(int x,int v){
	return vis[x][v]||(e[x].size()==2&&ok[e[x][0]][v]&&ok[e[x][1]][v]);
}

void dfp(int x,int rtx,int rty,int T){
	if(check(x,0)&&check(x,1)){
		vector<int>vec[2];
		for(int v:ids[x]){
			if(R[v]<=T) continue;
			vec[a[v][T-L[v]]].push_back(v);
		}
		if(rtx) G[rtx].push_back(x);
		else G[rt].push_back(x);
		rtx=x;
		ids[rtx]=vec[0];
		for(int v:vec[0]) pos[v]=rtx;
		++tot;
		if(rty) G[rty].push_back(tot);
		else G[rt].push_back(tot);
		rty=tot;
		ids[rty]=vec[1];
		for(int v:vec[1]) pos[v]=rty;
		nod.push_back(rtx);
		nod.push_back(rty);
	}
	else if(check(x,0)){
		if(rtx) G[rtx].push_back(x);
		else G[rt].push_back(x);
		rtx=x;
		nod.push_back(x);
	}
	else if(check(x,1)){
		if(rty) G[rty].push_back(x);
		else G[rt].push_back(x);
		rty=x;
		nod.push_back(x);
	}
	for(int v:e[x]) dfp(v,rtx,rty,T);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();
	READ();
	tot=rt=1;
	f[0]=1;
	for(int T=0;T<n;T++){
		for(int x:vec[T+1]) g[x]=0;g[0]=0;
		bool fl0=0,fl1=0;
		{
			int nv0=0,nv1=0;
			for(int x:vec[T+1])
				if(L[x]>T){
					if(R[x]==T+1){
						if(a[x].back()==0) fl1=1;
						else fl0=1;
					}
					if(a[x][0]==1) nv0=x;
					else nv1=x;
				}
			if(!fl0) Add(g[nv0],f[0]);
			if(!fl1) Add(g[nv1],f[0]);
		}
		for(int x=1;x<=tot;x++) ok[x][0]=ok[x][1]=0,nxt[x][0]=nxt[x][1]=0;
		for(int x:vec[T+1]){
			if(R[x]==T+1) ok[pos[x]][a[x].back()^1]=1;
			else cmin(nxt[pos[x]==0?rt:pos[x]][a[x][T+1-L[x]]^1],x);
		}
		dfs(rt);
		for(int x:vec[T]){
			if(R[x]==T) continue;
			if(!fl0&&!ok[pos[x]][0]) Add(g[nxt[pos[x]][0]],f[x]);
			if(!fl1&&!ok[pos[x]][1]) Add(g[nxt[pos[x]][1]],f[x]);
		}
		for(int x:vec[T+1]) f[x]=g[x];f[0]=g[0];
		for(int x=1;x<=tot;x++) ok[x][0]=ok[x][1]=vis[x][0]=vis[x][1]=0;
		rtx=0;rty=0;
		for(int x:vec[T+1]){
			if(R[x]==T+1) continue;
			if(L[x]==T+1){
				int v=a[x][0];
				if(v==0){
					if(!rtx) rtx=++tot;
					pos[x]=rtx;
					ids[rtx].push_back(x);
				}
				else{
					if(!rty) rty=++tot;
					pos[x]=rty;
					ids[rty].push_back(x);
				}
			}
			else{
				int v=a[x][T+1-L[x]];
				ok[pos[x]][v]=vis[pos[x]][v]=1;
			}
		}
		dfz(rt);
		nod.clear();nod.push_back(rt);
		if(check(rt,0)&&!rtx) rtx=++tot;
		if(check(rt,1)&&!rty) rty=++tot;
		for(int v:e[rt]) dfp(v,rtx,rty,T+1);
		if(rtx) G[rt].push_back(rtx);
		if(rty) G[rt].push_back(rty);
		nod.push_back(rtx);nod.push_back(rty);
		for(int i=1;i<=tot;i++) e[i].clear();
		tot=0;
		for(int x:nod) tr[x]=++tot;
		for(int x:vec[T+1]) pos[x]=tr[pos[x]];
		for(int x:nod) for(int v:G[x]) e[tr[x]].push_back(tr[v]);
		for(int x:nod) G[x].clear();
		for(int x:nod) swap(tmp[tr[x]],ids[x]);
		for(int i=1;i<=tot;i++) swap(ids[i],tmp[i]);
	}
	printf("%d",f[0]);
	return 0;
}

T2.删排列 delete

题面:

有一个长 \(n\) 的排列 \(p\),每次选择一个数删除,代价是 \(|i-p_i|\)。删除后 \(i\) 后位置全部前移一位,大于 \(p_i\) 的值全部减一,变成一个 \(n-1\) 排列。求所有方案中最大操作代价最小的值。\(n\leq 5\times 10^5\)

题解:

假设操作最大值是 \(m\),发现若开始时 \(|i-p_i|\) 小于等于 \(m\) 或大于 \(m\),则整个操作过程中这个关系一定保持不变,证明考虑分讨另一个操作 \((j,p_j)\)\((i,p_i)\) 各个变量的大小关系。
所以有 \(n^2\) 做法是每次取最小的 \(|i-p_i|\) 操作,并在此之后暴力更新剩余操作。
进一步的发现 \(i\)\(p_i\) 的大小关系也不会改变,所以分别维护 \(i<p_i\)\(i>p_i\) 的点对,每次取出两者间最小值进行操作。以 \(i<p_i\) 为例,\(i>p_i\) 同理。发现对于一个点对 \((i,p_i)\)\((j,p_j)\) 满足 \(j>i,p_j<p_i\)\(j\) 删除前不用考虑 \(i\),所以考虑线段树维护出所有这些关键点。
一次删除操作会使关键点的一个区间代价减一。删除一个关键点后会有新的关键点显露出来,每次线段树二分找到一个新关键点,复杂度总和是 \(n\log n\)。两个树状数组维护已删的 \(i,p_i\) 集合,以便找出新关键点后快速算出其当前权值,并加入关键点线段树中。对于 \(i<p_i\)\(i>p_i\) 分别维护上述信息。复杂度 \(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=5e5+5,inf=1e9+7;
int n,a[N];
set<int>up,dn;
struct BIT{
	int c[N];
	
	void clear(int n){
		for(int i=0;i<=n;i++) c[i]=0;
	}
	
	void add(int x,int v){
		while(x<=n){
			c[x]+=v;
			x+=x&-x;
		}
	}
	
	int query(int x){
		int res=0;
		while(x>0){
			res+=c[x];
			x-=x&-x;
		}
		return res;
	}
}X,Y;

namespace dnp{
	struct Tree{
		int v,s,tag,x;
	}t[N<<2];
	
	void pushup(int s){
		t[s].v=max(t[s<<1].v,t[s<<1|1].v);
		t[s].s=min(t[s<<1].s,t[s<<1|1].s);
		if(t[s].s==t[s<<1].s) t[s].x=t[s<<1].x;
		else t[s].x=t[s<<1|1].x;
	}
	
	void upd(int s,int v){
		t[s].s+=v;
		t[s].tag+=v;
	}
	
	void pushdown(int s){
		if(t[s].tag){
			upd(s<<1,t[s].tag);
			upd(s<<1|1,t[s].tag);
			t[s].tag=0;
		}
	}
	
	void build(int s,int l,int r){
		t[s]={-inf,inf,0,l};
		if(l==r) return ;
		pushdown(s);
		int mid=l+r>>1;
		build(s<<1,l,mid);
		build(s<<1|1,mid+1,r);
	}
	
	void update(int s,int l,int r,int L,int R,int v){
		if(L<=l&&r<=R){
			upd(s,v);
			return ;
		}
		pushdown(s);
		int mid=l+r>>1;
		if(L<=mid) update(s<<1,l,mid,L,R,v);
		if(R>mid) update(s<<1|1,mid+1,r,L,R,v);
		pushup(s);
	}
	
	void modify(int s,int l,int r,int x,int S,int v){
		if(l==r){
			t[s].s=S;t[s].v=v;
			return ;
		} 
		pushdown(s);
		int mid=l+r>>1;
		if(x<=mid) modify(s<<1,l,mid,x,S,v);
		else modify(s<<1|1,mid+1,r,x,S,v);
		pushup(s);
	}
	
	int erfen(int s,int l,int r,int x,int v){
		if(t[s].v<=v) return n+1;
		if(l==r) return l;
		pushdown(s);
		int mid=l+r>>1,res=n+1;
		if(x<=mid) res=erfen(s<<1,l,mid,x,v);
		if(res==n+1) res=erfen(s<<1|1,mid+1,r,x,v);
		return res;
	}
}

namespace ups{
	int t[N<<2];
	
	void pushup(int s){
		t[s]=min(t[s<<1],t[s<<1|1]);
	}
	
	void build(int s,int l,int r){
		t[s]=inf;
		if(l==r) return ;
		int mid=l+r>>1;
		build(s<<1,l,mid);
		build(s<<1|1,mid+1,r);
	}
	
	void modify(int s,int l,int r,int x,int v){
		if(l==r){
			t[s]=v;
			return ;
		}
		int mid=l+r>>1;
		if(x<=mid) modify(s<<1,l,mid,x,v);
		else modify(s<<1|1,mid+1,r,x,v);
		pushup(s);
	}
	
	int erfen(int s,int l,int r,int x,int v){
		if(t[s]>=v) return 0;
		if(l==r) return l;
		int mid=l+r>>1,res=0;
		if(x>mid) res=erfen(s<<1|1,mid+1,r,x,v);
		if(!res) res=erfen(s<<1,l,mid,x,v);
		return res;
	}
}

namespace upp{
	struct Tree{
		int v,s,tag,x;
	}t[N<<2];
	
	void pushup(int s){
		t[s].v=min(t[s<<1].v,t[s<<1|1].v);
		t[s].s=min(t[s<<1].s,t[s<<1|1].s);
		if(t[s].s==t[s<<1].s) t[s].x=t[s<<1].x;
		else t[s].x=t[s<<1|1].x;
	}
	
	void upd(int s,int v){
		t[s].s+=v;
		t[s].tag+=v;
	}
	
	void pushdown(int s){
		if(t[s].tag){
			upd(s<<1,t[s].tag);
			upd(s<<1|1,t[s].tag);
			t[s].tag=0;
		}
	}
	
	void build(int s,int l,int r){
		t[s]={inf,inf,0,l};
		if(l==r) return ;
		int mid=l+r>>1;
		build(s<<1,l,mid);
		build(s<<1|1,mid+1,r);
	}
	
	void update(int s,int l,int r,int L,int R,int v){
		if(L<=l&&r<=R){
			upd(s,v);
			return ;
		}
		pushdown(s);
		int mid=l+r>>1;
		if(L<=mid) update(s<<1,l,mid,L,R,v);
		if(R>mid) update(s<<1|1,mid+1,r,L,R,v);
		pushup(s);
	}
	
	void modify(int s,int l,int r,int x,int S,int v){
		if(l==r){
			t[s].s=S;t[s].v=v;
			return ;
		} 
		pushdown(s);
		int mid=l+r>>1;
		if(x<=mid) modify(s<<1,l,mid,x,S,v);
		else modify(s<<1|1,mid+1,r,x,S,v);
		pushup(s);
	}
	
	int erfen(int s,int l,int r,int x,int v){
		if(t[s].v>=v) return 0;
		if(l==r) return l;
		int mid=l+r>>1,res=0;
		pushdown(s);
		if(x>mid) res=erfen(s<<1|1,mid+1,r,x,v);
		if(!res) res=erfen(s<<1,l,mid,x,v);
		return res;
	}
}

namespace dns{
	int t[N<<2];
	
	void pushup(int s){
		t[s]=max(t[s<<1],t[s<<1|1]);
	}
	
	void build(int s,int l,int r){
		t[s]=-inf;
		if(l==r) return ;
		int mid=l+r>>1;
		build(s<<1,l,mid);
		build(s<<1|1,mid+1,r);
	}
	
	void modify(int s,int l,int r,int x,int v){
		if(l==r){
			t[s]=v;
			return ;
		}
		int mid=l+r>>1;
		if(x<=mid) modify(s<<1,l,mid,x,v);
		else modify(s<<1|1,mid+1,r,x,v);
		pushup(s);
	}
	
	int erfen(int s,int l,int r,int x,int v){
		if(t[s]<=v) return n+1;
		if(l==r) return l;
		int mid=l+r>>1,res=n+1;
		if(x<=mid) res=erfen(s<<1,l,mid,x,v);
		if(res==n+1) res=erfen(s<<1|1,mid+1,r,x,v);
		return res;
	}
}

void solve(){
	n=read();
	X.clear(n);Y.clear(n);
	upp::build(1,1,n);ups::build(1,1,n);
	dnp::build(1,1,n);dns::build(1,1,n);
	up.clear();dn.clear();
	a[0]=-inf;for(int i=1;i<=n;i++) a[i]=read();a[n+1]=inf;
	{
		int lst=inf;
		for(int i=n;i>=1;i--)
			if(i<a[i]){
				if(a[i]<lst){
					upp::modify(1,1,n,i,a[i]-i,a[i]);
					up.insert(i);
					lst=a[i];
				}
				else ups::modify(1,1,n,i,a[i]);
			}
	}
	{
		int lst=-inf;
		for(int i=1;i<=n;i++)
			if(a[i]<=i){
				if(a[i]>lst){
					dnp::modify(1,1,n,i,i-a[i],a[i]);
					dn.insert(i);
					lst=a[i];
				}
				else dns::modify(1,1,n,i,a[i]);
			}
	}
	int ans=0;
	for(int T=1;T<=n;T++){
		if(upp::t[1].s<dnp::t[1].s){
			ans=max(ans,upp::t[1].s);
			int x=upp::t[1].x;
			upp::modify(1,1,n,x,inf,inf);
			X.add(x,1); Y.add(a[x],1);
			int p;
			if(x+1<=n) p=dnp::erfen(1,1,n,x+1,a[x]);
			else p=n+1;
			if(x+1<=p-1) dnp::update(1,1,n,x+1,p-1,-1);
			int v,lim;
			auto it=up.find(x);
			if(next(it)==up.end()) v=inf;
			else v=a[*next(it)];
			if(it==up.begin()) lim=0;
			else lim=*prev(it);
			up.erase(it);
			while(lim<x-1){
				x=ups::erfen(1,1,n,x-1,v);
				if(x<=lim) break;
				v=a[x];
				upp::modify(1,1,n,x,(v-Y.query(v))-(x-X.query(x)),v);
				ups::modify(1,1,n,x,inf);
				up.insert(x);
			}
		}
		else{
			ans=max(ans,dnp::t[1].s);
			int x=dnp::t[1].x;
			dnp::modify(1,1,n,x,inf,-inf);
			X.add(x,1); Y.add(a[x],1);
			int p;
			if(1<=x-1) p=upp::erfen(1,1,n,x-1,a[x]);
			else p=0;
			if(p+1<=x-1) upp::update(1,1,n,p+1,x-1,-1);
			int v,lim;
			auto it=dn.find(x);
			if(it==dn.begin()) v=-inf;
			else v=a[*prev(it)];
			if(next(it)==dn.end()) lim=n+1;
			else lim=*next(it);
			dn.erase(it);
			while(x+1<lim){
				x=dns::erfen(1,1,n,x+1,v);
				if(x>=lim) break;
				v=a[x];
				dnp::modify(1,1,n,x,(x-X.query(x))-(v-Y.query(v)),v);
				dns::modify(1,1,n,x,-inf);
				dn.insert(x);
			}
		}
	}
	printf("%d\n",ans);
}

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

T3.查商品 check

题面:

给一个长为 \(n\) 的环形排列 \(a\),最初你在 \(1\) 位置。每次你决策有 \(p\) 的概率删除 \(a_i\),然后不管怎样都移动到下一个没有删除的位置。求最后全部删除后删除序列在所有 \(n\) 阶排列中字典序排名的期望值。\(n\leq 5000\)

题解:

考虑确定一个排列怎么求其字典序排名,设排列为 \(a_i\),其排名为 \(1+\sum_i (n-i)!\sum_{j>i} [a_j<a_i]\)
考虑环上 \(i\) 位置的元素删除时间是 \(t_i\),则序列排名为 \(1+\sum_i (n-t_i)!\sum_{t_i<t_j}[a_i>a_j]\)
所以答案期望为 \(1+\sum_i\sum_j\sum_{t_i}[a_i>a_j]P(t_i<t_j)(n-t_i)!\)
\(q=1-p\)
\(f_{n,i,j}\) 表示长度为 \(n\) 的环,\(i\) 先于 \(j\) 消失的情况下 \((n-t_i)!\) 的期望。
转移时考虑对物品重标号,将当前需要决策的物品标号为 1。考虑下一个物品时将下一个重标号为 1,则 \(i,j\) 的标号转移到 \(i-1,j-1\)。如果删除当前物品则 \(n\to n-1\)。考虑一些边界情况总体转移是:

\[f_{n,i,j}= \begin{cases} qf_{n,n,j-1}+p(n-1)! & i=1 \\ qf_{n,i-1,n} & j=1 \\ qf_{n,i-1,j-1}+pf_{n-1,i-1,j-1} & i>1\land j>1 \end{cases} \]

注意此处因为每删除一个物品则 \(n\to n-1\) 所以当删除 \(i\)\(n-t_i\) 就是此时的 \(n-1\)

进一步发现,当决策 \(i\) 时,\(1\sim i-1\) 的物品消失概率是相同的,\(i+1\sim n\) 的物品消失概率是相同的。即 \(f_{n,i,j}\) 的值对于 \([1,i-1]\)\([i+1,n]\) 分别全相等,或者你可以通过打表 \(f_{n,i,*}\) 发现这一点。
所以我们只需求 \(f_{n,i}\) 表示是上述 \(f_{n,i,i-1}\)\(g_{n,i}\) 表示上述 \(f_{n,i,i+1}\) 即可。
转移照搬上述方程式:

\[f_{n,i}= \begin{cases} qf_{n,n}+p(n-1)! & i=1 \\ qf_{n,i-1} & i=2 \\ qf_{n,i-1}+pf_{n-1,i-1} & i>2 & \end{cases} \\ g_{n,i}= \begin{cases} qg_{n,n}+p(n-1)! & i=1 \\ qg_{n,i-1}+pg_{n-1,i-1} & i<n \\ qg_{n,n-1} & i=n & \end{cases} \]

不要高斯消元,注意到对于每个 \(n\),转移方程形成了一个环,可以 \(O(n)\) 计算,总复杂度 \(O(n^2)\)

代码
#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=5005,mod=1e9+7;
int n,a[N];
ll p,q,fac[N],f[N],g[N],B[N];

ll Mod(ll x){return x>=mod?x-mod:x;}
void Add(ll &x,ll y){x=Mod(x+y);}

ll ksm(ll a,ll b){
	ll t=1;
	for(;b;b>>=1,a=a*a%mod)
		if(b&1) t=t*a%mod;
	return t;
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();p=read();q=Mod(1+mod-p);
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int T=2;T<=n;T++){
		{
			B[1]=p*fac[T-1]%mod; B[2]=0;
			for(int i=3;i<=T;i++) B[i]=f[i-1]*p%mod;
			ll k=1,b=0;
			for(int i=1;i<=T;i++){
				(k*=q)%=mod;
				b=(b*q+B[i])%mod;
			}
			f[0]=b*ksm(Mod(1+mod-k),mod-2)%mod;
			for(int i=1;i<=T;i++) f[i]=(f[i-1]*q+B[i])%mod;
		}
		{
			B[1]=p*fac[T-1]%mod; B[T]=0;
			for(int i=2;i<T;i++) B[i]=g[i-1]*p%mod;
			ll k=1,b=0;
			for(int i=1;i<=T;i++){
				(k*=q)%=mod;
				b=(b*q+B[i])%mod;
			}
			g[0]=b*ksm(Mod(1+mod-k),mod-2)%mod;
			for(int i=1;i<=T;i++) g[i]=(g[i-1]*q+B[i])%mod;
		}
	}
	ll ans=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++) if(a[i]>a[j]) Add(ans,f[i]);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++) if(a[i]>a[j]) Add(ans,g[i]);
	printf("%lld",ans);
	return 0;
}

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