2026.1.6 题解

25+8+0=33,rk8。

A. 硬币序列

发现这玩意显然具有单调性,考虑二分答案。但这样性质太少了,考虑能否再加点料。

发现假如 \(a+b\) 固定的话,随着 \(a\) 的变化,答案显然是一个下凸包状物,也就意味着我们还可以 \(wqs\)

但我们的目的不是 \(wqs\),而是借此得到的另一个性质:答案 \(\le x\)\(a\) 形成一个连续段。那我们就可以通过算出最多/最少能放多少个 0 来算出第一问,这玩意显然是简单 dp。

考虑如何构造一种方案。我们可以将 \(\max\) 的头和 \(\min\) 的尾拼在一起,这样一定有一种方案是合法的。证明不会,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,type,n,a,b,f[N][2],g[N][2],lst[N][2],tsl[N][2],pr[N],nx[N],ot[N],to[N];
string s;
inline int check(int x,int lxl){
	list<int>q,qf,qg;
	f[0][0]=f[0][1]=g[0][0]=g[0][1]=0;
	q.push_back(0),qf.push_back(0),qg.push_back(0);
	for(int i=1,ls=0;i<=n+1;i++){
		if(s[i]=='0'){
			while(q.size()&&i-q.front()>x) q.pop_front();
			while(qg.size()&&i-qg.front()>x) qg.pop_front();
			f[i][1]=1e9,g[i][1]=0,ls=i;
			while(qf.size()) qf.pop_back();
			qf.push_back(i);
			if(!q.size()) return 0;
			f[i][0]=f[q.front()][1]-q.front()+i;
			g[i][0]=g[qg.front()][1]-qg.front()+i;
			lst[i][0]=q.front(),tsl[i][0]=qg.front();
		}
		else if(s[i]=='1'){
			while(qf.size()&&i-qf.front()>x) qf.pop_front();
			f[i][0]=1e9,g[i][0]=0;
			if(i-ls>x) return 0;
			f[i][1]=f[qf.front()][0],g[i][1]=g[ls][0];
			lst[i][1]=qf.front(),tsl[i][1]=ls;
			while(q.size()) q.pop_back();
			while(qg.size()) qg.pop_back();
			q.push_back(i),qg.push_back(i);
		}
		else{
			while(q.size()&&i-q.front()>x) q.pop_front();
			while(qg.size()&&i-qg.front()>x) qg.pop_front();
			while(qf.size()&&i-qf.front()>x) qf.pop_front();
			if(!q.size()) f[i][0]=1e9,g[i][0]=0;
			else{
				f[i][0]=f[lst[i][0]=q.front()][1]-q.front()+i;
				g[i][0]=g[tsl[i][0]=qg.front()][1]-qg.front()+i;
			}
			if(i-ls>x) f[i][1]=1e9,g[i][1]=0;
			else{
				f[i][1]=f[lst[i][1]=qf.front()][0],g[i][1]=g[tsl[i][1]=ls][0];
				while(q.size()&&f[q.back()][1]-q.back()>f[i][1]-i) q.pop_back();
				while(qg.size()&&f[qg.back()][1]-qg.back()<f[i][1]-i) qg.pop_back();
				q.push_back(i),qg.push_back(i);
			}
			if(f[i][0]<1e9) qf.push_back(ls=i);
			if(f[i][0]==1e9&&f[i][1]==1e9) return 0;
		}
	}
	return min(f[n+1][0]-1,f[n+1][1])<=lxl&&lxl<=max(g[n+1][0]-1,g[n+1][1]);
}
inline void get_ot(int x,int y){
	if(!x) return;
	get_ot(lst[x][y],y^1);
	for(int i=lst[x][y]+1;i<=x;i++) ot[i]=y;
}
inline void get_to(int x,int y){
	if(!x) return;
	get_to(tsl[x][y],y^1);
	for(int i=tsl[x][y]+1;i<=x;i++) to[i]=y;
}
inline void solve(){
	cin>>n>>a>>b>>s,s=" "+s+"?";
	int l=1,r=n,as=0,num=0,len=0;
	for(int i=1;i<=n;i++) num+=s[i]=='0';
	while(l<=r){
		int mid=(l+r)>>1;
		if(!check(mid,num+a)) l=mid+1;
		else r=mid-1,as=mid;
	}
	check(as,num+a);
	get_ot(n+1,f[n+1][0]-1>f[n+1][1]);
	get_to(n+1,g[n+1][0]<=g[n+1][1]);
	int now=!ot[n];
	for(int i=1;i<n;i++){
		if(ot[i]) pr[i+1]=i;
		else pr[i+1]=pr[i],now++;
	}
	nx[n]=n+1,ot[0]=ot[n+1]=2;
	for(int i=n;i>1;i--){
		if(ot[i]) nx[i-1]=i;
		else nx[i-1]=nx[i];
	}
	if(now!=num+a) for(int i=1;i<=n;i++){
		now-=!ot[i],ot[i]=to[i],now+=!ot[i];
		if(ot[i]!=ot[i+1]&&now==num+a) break;
	}
	cout<<as<<"\n";
	if(type){
		for(int i=1;i<=n;i++) cout<<ot[i];
		cout<<"\n";
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>T>>type;
	while(T--) solve();
	return 0;
}

B. 划分线段

傻白虎日常不认原题这一块。

考虑我们一定可以将该问题写成一个树状物,考虑树形 dp。由于祖先有可能在一个后代节点内选择,考虑设 \(dp_{i,j,0/1,0/1}\) 表示第 \(i\) 个点内选了 \(j\) 个子区间,左右端点有没有被选。然后直接树上背包转移即可。时间复杂度 \(O(n^3)\)

发现实际上 \(j\in[sz_i,2sz_i)\),所以问题直接变成经典的树上背包,时间复杂度变为 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5005,inf=1e9+1;
struct sq{
	int l,r;
}s[N];
int cmp(sq x,sq y){
	return x.l<y.l;
}
int cmp2(int x,int y){
	return s[x].l<s[y].l;
}
int n,ans,sz[N];
vector<int>ve[N];
int f[N][N][2][2],g[2][N][2][2];
unordered_map<int,int>mp;
set<int>st;
void dfs(int x){
	sz[x]=1,sort(ve[x].begin(),ve[x].end(),cmp2);
	if(!ve[x].size()){
		f[x][0][1][0]=s[x].r,f[x][0][0][1]=-s[x].l;
		f[x][0][0][0]=s[x].r-s[x].l;
		return;
	}
	for(int y:ve[x]) dfs(y);
	int t=1;
	for(int y:ve[x]){
		t^=1;if(y==ve[x][0]){
			memcpy(g[t],f[y],sizeof(g[t]));
			sz[x]+=sz[y];continue;
		}
		for(int i=0;i<=n;i++)
			g[t][i][0][0]=g[t][i][0][1]=g[t][i][1][0]=g[t][i][1][1]=-inf;
		for(int i=0;i<=sz[x];i++)
			for(int j=0;j<=sz[y];j++) for(int nw=0;nw<8;nw++){
				int a=((nw>>2)&1),b=((nw>>1)&1),c=(nw&1);
				g[t][i+j+b][a][c]=max(g[t][i+j+b][a][c],g[t^1][i][a][b]+f[y][j][b][c]);
			}
		sz[x]+=sz[y];
	}
	for(int i=0;i<=n;i++)
		for(int nw=0;nw<4;nw++){
			int a=((nw>>1)&1),b=(nw&1);f[x][i][a][b]=g[t][i+1][a][b];
			if(a) f[x][i][0][b]=max(f[x][i][0][b],g[t][i][a][b]-s[x].l);
			if(b) f[x][i][a][0]=max(f[x][i][a][0],g[t][i][a][b]+s[x].r);
			if(a+b) f[x][i][a][b]=max(f[x][i][a][b],g[t][i][a][b]);
		}
}int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n,st.insert(1e9+1);
	for(int i=1;i<=n;i++) cin>>s[i].l>>s[i].r;
	sort(s+1,s+n+1,cmp);
	for(int i=1;i<=n;i++){
		mp[s[i].r]=i,st.insert(s[i].r);
		ve[mp[*st.upper_bound(s[i].r)]].push_back(i);
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
		for(int a=0;a<2;a++) for(int b=0;b<2;b++) f[i][j][a][b]=-inf;
	for(int x:ve[0])
		dfs(x),ans+=f[x][0][0][0];
	cout<<ans;
	return 0;
}

C. 打怪升级

路径不得交叉,想到 LGV,但贡献不独立还是太吃操作了,考虑先容斥一手。设 \(c_i=b_i-a_i,s=\sum c_i\),则有:

\[ans=\sum_{k=0}^{\frac s2}(-1)^k\binom{\frac s2}kk!(s-2k)!\color{blue}{\sum_{2d_i\le c_i,\sum_{d_i}=i}\frac 1{\prod\limits_{i=1}^nd_i!(c_i-2d_i)!}} \]

后面的蓝色部分可以看作是一个多项式的第 \(i\) 项,考虑生成函数。设 \(F_c(x)=\sum_{i=1}^{\frac c2}\limits\frac{x^i}{i!(c-2i)!}\),则后半部分 \(=[x^k]\prod\limits_{i=1}^nF_{c_i}(x)\)

现在整个式子都是 LGV 的形状了,那么就有 \(e(i,j)=F_{b_j-a_i}(x)\),直接 LGV 引理即可。

但有一个问题,就是 \(e\) 是一个多项式,这玩意的时间复杂度大的离谱。但考虑我们是知道最后得到的多项式的幂次的,所以我们可以随便拿 \(\frac s2+1\) 个值拉进去算答案,最后在拉格朗日插值一手就好了,时间复杂度 \(O(n^4D+nD^3)\),当然也有 \(O(n^4D+n^3D^2)\) 的写法。

#include<bits/stdc++.h>
using namespace std;
const int N=35,D=205,p=1e9+7;
int n,ac[N],bc[N],a[N][N],jc[D*N];
struct poly{
	int n,a[N*D];
	inline int gets(int x){
		int re=0;
		for(int i=n;~i;i--)
			re=(1ll*re*x+a[i])%p;
		return re;
	}
}pl[D],answ;
int num[D],mx,sm,ans;
int xc[N*D],yc[N*D],xx[N*D],m[N*D];
inline int qpow(int x,int y){
	int re=1;
	while(y){
		if(y&1) re=1ll*re*x%p;
		x=1ll*x*x%p,y>>=1;
	}
	return re;
}
inline void lag(int n,poly &re){
	m[0]=1,re.n=n-1;
	for(int i=0;i<n;i++)
		for(int j=i;~j;j--){
			m[j+1]=(m[j]+m[j+1])%p;
			m[j]=1ll*m[j]*(p-xc[i])%p;
		}
	for(int i=n-1;~i;i--)
		for(int j=0;j<n;j++)
			xx[j]=(1ll*xx[j]*xc[j]+m[i+1]*(i+1ll))%p;
	for(int i=0;i<n;i++){
		int t=1ll*yc[i]*qpow(xx[i],p-2)%p,c=m[n];
		for(int j=n-1;~j;j--)
			re.a[j]=(re.a[j]+1ll*c*t)%p,c=(m[j]+1ll*c*xc[i])%p; 
	}
}
inline int det(){
	int e=1,re=1;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++){
			while(a[i][i]){
				int dv=a[j][i]/a[i][i];
				for(int k=i;k<=n;k++)
					a[j][k]=(a[j][k]-1ll*dv*a[i][k])%p;
				swap(a[i],a[j]),e=-e;
			}
			swap(a[i],a[j]),e=-e;
		}
	for(int i=1;i<=n;i++)
		re=1ll*re*a[i][i]%p;
	return (re*e+p)%p;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	jc[0]=1;
	for(int i=1;i<=n;i++) cin>>ac[i];
	for(int i=1;i<=n;i++){
		cin>>bc[i],sm+=bc[i]-ac[i];
		if(ac[i]>bc[i]) cout<<0,exit(0);
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
		mx=max(mx,bc[j]-ac[i]);
		if((ac[i]==ac[j]||bc[i]==bc[j])&&i!=j) cout<<0,exit(0);
		if(ac[i]<ac[j]&&bc[i]>bc[j]) cout<<0,exit(0);
	}
	if(sm&1) cout<<0,exit(0);
	for(int i=1;i<=mx*n;i++) jc[i]=1ll*jc[i-1]*i%p;
	for(int i=0;i<=mx;i++){
		pl[i].n=i/2;
		for(int j=0;j<=i/2;j++)
			pl[i].a[j]=1ll*qpow(jc[i-2*j],p-2)*qpow(jc[j],p-2)%p;
	}
	for(int i=1;i<=sm/2+1;i++){
		xc[i-1]=i;
		for(int j=0;j<=mx;j++) num[j]=pl[j].gets(i);
		for(int j=1;j<=n;j++) for(int k=1;k<=n;k++)
			a[j][k]=(bc[k]>=ac[j]?num[bc[k]-ac[j]]:0);
		yc[i-1]=det();
	}
	lag(sm/2+1,answ);
	for(int i=0;i<=sm/2;i++)
		ans=(ans+((i&1)?p-1ll:1ll)*jc[sm/2]%p*qpow(jc[sm/2-i],p-2)%p*jc[sm-2*i]%p*answ.a[i])%p;
	ans=1ll*ans*qpow(qpow(2,sm/2),p-2)%p;
	cout<<ans;
	return 0;
}
posted @ 2026-01-10 09:55  white_tiger  阅读(9)  评论(0)    收藏  举报