2026.1.12 题解

20+0+20=40,rk6。

这咋回事啊,神一场鬼一场的,正倒二交替轮换啊。

部分分还是得好好打,考场策略还需要好好调整调整。


A. 最小生成树

这类题我是完全不会一点的,这还是太抽象了。

\(n\) 非常小,考虑状压 dp。能状压的东西要么是集合要么是数组,这题前者不好弄,考虑后者。

借鉴 Kruskal 的思想,我们考虑先把一条边拆成两条边(\(a,b\) 边),再从小到大排序。为了方便,我们肯定是希望对 \(a\) 边数目能产生直接影响的边在前,所以我们考虑假如 \(a\le b\),我们给 \(a\) 边一个权值 1,否则给 \(b\) 边一个权值 -1,这样就可以让产生贡献的在前了。

既然要 Kruskal,我们肯定是得维护连通块的。这玩意的种类数是 \(\operatorname{Bell}(n)\),在本题最大约是 20000 级别,那我们就直接把它设成一维。同时,我们还得考虑当前选择的 \(a\) 边数量。所以我们设 \(dp_{s,i}\) 表示选了 \(i\)\(a\) 边,连通性为 \(s\) 的最小生成森林最大是多少。对于 1/-1 类边,他们可连可不连;对于 0 类边,若两段点不连通就必须连,否则可连可不连。

时间复杂度 \(O(nm^2\operatorname{Bell}(n))\),精细实现的话 \(n\) 也能省掉。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=10,M=205;
int n,m,k,fa[N],ans[M];
struct edge{
	int u,v,w,id;
}ed[M];
inline bool cmp(edge x,edge y){
	return x.w!=y.w?x.w<y.w:!x.id;
}
ll zt;
unordered_map<ll,int>mp[M];
int main(){
	freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,a,b;i<=m;i++){
		cin>>u>>v>>a>>b,zt+=(a>b);
		ed[++k]={u,v,a,a<=b};
		ed[++k]={u,v,b,-(a>b)};
	}
	sort(ed+1,ed+k+1,cmp);
	for(int i=1;i<=n;i++) zt=zt*10+i;
	mp[0][zt]=0;
	for(int i=1;i<=k;i++){
		for(auto cc:mp[i-1]){
			ll ztn=(zt=cc.first);
			int num=cc.second;
			for(int j=n;j;j--)
				fa[j]=ztn%10,ztn/=10;
			if(fa[ed[i].u]==fa[ed[i].v]){
				mp[i][zt]=max(mp[i][zt],num);
				ztn+=ed[i].id;
				for(int j=1;j<=n;j++) ztn=ztn*10+fa[j];
				mp[i][ztn]=max(mp[i][ztn],num);
			}
			else{
				if(ed[i].id) mp[i][zt]=max(mp[i][zt],num);
				int u=fa[ed[i].u],v=fa[ed[i].v];
				if(u>v) swap(u,v);
				ztn+=ed[i].id,num+=ed[i].w;
				for(int j=1;j<=n;j++){
					if(fa[j]==v) fa[j]=u;
					ztn=ztn*10+fa[j];
				}
				mp[i][ztn]=max(mp[i][ztn],num);
			}
		}
	}
	zt=0;
	for(int i=1;i<=n;i++) zt=zt*10+1;
	for(int i=0;i<=m;i++)
		cout<<mp[k][zt+pow(10,n)*i]<<"\n";
	return 0;
}

B. 行走

失策了,考场要做这题应当是能做出来的。

考虑我们最终一定能让他进入一个进出一个子树时指针都指向父亲的情况,这时路径是好求的,就是从父亲后一个点开始遍历的欧拉序。而且,一旦一个点变成满足上述要求的点后,就会一直满足。同时,由于每一次从 1 出发又回到 1 的遍历(下称一个周期)至少会使得一个点满足要求,所以最多只需要进行 \(O(n)\) 个周期,就可以变成所有点都满足要求的情况(下称平凡周期)。那这个问题就分成了两步:

  1. 求出每个点什么时候变成满足要求的点。
  2. 求答案。

第一步很明朗,我们设 \(dep_i\) 表示到根链上的每个非根节点中,在父亲的儿子排列中后于父亲的父亲遍历到的点的数量,那么 \(dep_i+1\) 就是被变成满足点的周期数,这是显然的。

第二部考虑假如到了最后的平凡周期,那么直接找到欧拉序上对应的位置就行。假如我们知道非平凡周期的有效欧拉序节点,我们就可以用线段树二分或者并查集解决。而每个点只会被变成满足点一次,所以总的变化次数是 \(O(n)\) 的。直接扫描线+线段树/并查集,或者主席树即可。

时间复杂度 \(O(n\log n+q\log q)\)(扫描线+线段树)或 \(O(n\alpha(n)+q\log q)\)(并查集+线段树),鸡排后可以去掉 \(\log q\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,k,zq,mx,dep[N],ps[N<<1],sum[N],ans[N];
struct que{
	int x,id;
}qu[N];
inline bool cmp(que x,que y){
	return x.x<y.x;
}
vector<int>g[N],ve[N],idx[N];
inline void dfs1(int x,int fa){
	int ad=0;
	for(int y:g[x]){
		if(y==fa) ad=1;
		else dep[y]=dep[x]+ad,dfs1(y,x);
	}
	ve[dep[x]].push_back(x);
	mx=max(mx,dep[x]);
}
inline void dfs2(int x,int fa){
	ps[++k]=x,idx[x].push_back(k);
	int mid=(x==1?-1:0);
	if(x>1) while(g[x][mid]!=fa) mid++;
	for(int i=mid+1;i<g[x].size();i++)
		dfs2(g[x][i],x),ps[++k]=x,idx[g[x][i]].push_back(k);
	for(int i=0;i<mid;i++)
		dfs2(g[x][i],x),ps[++k]=x,idx[g[x][i]].push_back(k);
}
int sm[N<<3];
inline void chg(int x,int l,int r,int k){
	sm[x]++;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(k<=mid) chg(x<<1,l,mid,k);
	else chg(x<<1|1,mid+1,r,k);
}
inline int erf(int x,int l,int r,int v){
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(sm[x<<1]>=v) return erf(x<<1,l,mid,v);
	return erf(x<<1|1,mid+1,r,v-sm[x<<1]);
}
signed main(){
	freopen("walk.in","r",stdin);
	freopen("walk.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,k,fs;i<=n;i++){
		cin>>k>>fs;
		for(int j=2,to;j<=k;j++)
			cin>>to,g[i].push_back(to);
		g[i].push_back(fs);
	}
	dep[1]=1,dfs1(1,0),dfs2(1,0);
	for(int i=1,sumc=0;i<=mx;i++)
		sumc+=ve[i].size(),sum[i]=(sumc-1)*2+sum[i-1];
	for(int i=1;i<=m;i++) cin>>qu[i].x,qu[i].id=i;
	sort(qu+1,qu+m+1,cmp),sum[mx]=9e18,zq=2*n-2;
	for(int i=1,j=0;i<=mx&&j<m;i++){
		for(int x:ve[i]) for(int y:idx[x]) chg(1,1,k,y);
		while(j<m&&qu[j+1].x<=sum[i])
			j++,ans[qu[j].id]=ps[erf(1,1,k,(qu[j].x-sum[i-1])%zq+1)];
	}
	for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
	return 0;
}

C. 排列

这题的可达性是好做的,按照所有前缀 \(\max\) 和下标一样的 \(i\) 做分界线就行,考虑如何计算答案。

发现左右的计算是等价的,因此我们只计算每个点左侧点对它的贡献。根据 \(trick\),我们只需要求出有多少个位置 \(i\) 步到不了就行。

发现到达左侧点的方法有两种:左侧能到的最靠左的点 \(L\)、右侧能到的最小的点 \(R\),那我们维护他俩的坐标就行了。我们对于每个位置都维护 \(ls_i,rs_i\),表示左侧最靠左且 \(>a_i\) 的点和右侧最小点。初始设 \(L=R=i\),之后每次 \(L=ls_R,R=rs_L\),一直到 \(L,R\) 不变为止。那我们就从 \((L,R)\)\((ls_R,rs_L)\) 连边。容易发现,每个 \(i\) 都跑完一次后,这回形成一棵内向树。

有一种神奇的方法可以证明这棵树的总点数是 \(O(n\sqrt n)\) 级别的。由于每个点 \(L,R\) 的贡献是满足 \(i<L,a_i<R\) 的点数,所以每个点的贡献是个二维数点问题,离线下来可以根号平衡成 \(O(n\sqrt n)\) 的,关键在于建树。这里采用的是直接暴跳 + 哈希表,时间复杂度是炒大肠 \(O(n\sqrt n)\),可以通过。但还有更精妙的做法。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5,M=2e7+5;
int xlx,lxl[N];
struct hash_map{
	static const int SZ=(1<<22)-1;
	struct edge{
		ll u;
		int nx;
	}ed[M];
	int h[1<<22],cnt;
	inline void clear(){
		while(cnt) h[ed[cnt--].u&SZ]=0;
	}
	inline int add(ll u){
		int hu=u&SZ;
		ed[++cnt]={u,h[hu]};
		return h[hu]=cnt;
	}
	inline int count(ll u){
		int hu=u&SZ;
		for(int i=h[hu];i;i=ed[i].nx)
			if(ed[i].u==u) return i;
		return 0;
	}
}mp;
int n,a[N],ls[N],rs[N],fs[N];
int c[N],ad[N],num[N],kl;
inline void add(int x,int v){
	for(;x<=n;x+=x&-x) c[x]=min(c[x],v);
}
inline int minn(int x){
	int re=1e9;
	for(;x;x-=x&-x) re=min(re,c[x]);
	return re;
}
inline void addc(int x){
	for(int i=min(x/kl*kl+kl-1,n);i>=x;i--) num[i]++;
	for(int i=x/kl+1;i*kl<=n;i++) ad[i]++;
}
inline int sum(int x){
	return ad[x/kl]+num[x];
}
inline void clear(){
	for(int i=1;i<=n;i++) c[i]=1e9,ad[i]=num[i]=0;
}
int vs[M],fa[M],lc[M],rc[M],idx[M],ct[N];
ll sm[M],ans[N];
inline void dfs(int l,int r,int x){
	if(fa[x]==x) return;
	if(!vs[x]) dfs(ls[r],rs[l],fa[x]);
	sm[x]+=sm[fa[x]];
}
inline void solve(int cc){
	clear(),mp.clear(),kl=max(1.0,sqrt(n)*0.9);
	for(int i=1;i<=n;i++)
		add(n-a[i]+1,i),ls[i]=minn(n-a[i]+1),ct[i]=0;
	for(int i=n,mn=0;i;i--){
		if(a[i]<a[mn]) mn=i;
		rs[i]=mn;
	}
	for(int i=1;i<=n;i++){
		int l=i,r=i,nw=fs[i]=mp.add(1ll*i*n+i);
		while(1){
			ct[l]++;
			int lp=ls[r],rp=rs[l];
			if(lp==l&&rp==r){
				vs[fa[nw]=nw]=1;
				break;
			}
			int nx=mp.count(1ll*lp*n+rp);
			if(nx){
				fa[nw]=nx,vs[nw]=1;
				break;
			}
			fa[nw]=nx=mp.add(1ll*lp*n+rp);
			vs[nw]=0,nw=nx,l=lp,r=rp;
		}
	}
	ct[0]=0;
	for(int i=1;i<=n;i++) ct[i]+=ct[i-1];
	for(int i=1;i<=n;i++){
		int l=i,r=i,z,nw=fs[i];
		while(1){
			lc[++ct[l-1]]=l,rc[ct[l-1]]=r,idx[ct[l-1]]=nw;
			if(vs[nw]) break;
			nw=fa[nw],z=l,l=ls[r],r=rs[z];
		}
	}
	for(int i=1,j=1;i<=n&&j<=mp.cnt;i++){
		while(lc[j]<=i&&j<=mp.cnt)
			sm[idx[j]]=sum(a[rc[j]]-1),j++;
		addc(a[i]);
	}
	for(int i=1;i<=n;i++){
		int nw=fs[i];
		dfs(i,i,nw);
		if(!cc) ans[i]=sm[nw];
		else ans[n-i+1]+=sm[nw];
	}
}
int main(){
	freopen("permutation.in","r",stdin);
	freopen("permutation.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>xlx,a[0]=1e9;
	for(int i=1,j=1,mx=0,nw;i<=xlx;i++){
		cin>>lxl[i],mx=max(mx,lxl[i]);
		if(mx==i){
			for(int k=j;k<=i;k++) a[k-j+1]=lxl[k]-j+1;
			n=i-j+1,solve(0);
			for(int k=1;k<=n/2;k++) swap(a[k],a[n-k+1]);
			for(int k=1;k<=n;k++) a[k]=n-a[k]+1;
			solve(1),j=i+1;
			for(int k=1;k<=n;k++) cout<<ans[k]+n-1<<" ";
		}
	}
	return 0;
} 
posted @ 2026-01-13 16:09  white_tiger  阅读(9)  评论(0)    收藏  举报