2024 Noip 做题记录(一)


By DaiRuiChen007



Round #1 - 20240909

A. [P10997] Color

Problem Link

题目大意

你有 nm 列的一个矩阵,第 i 行第 j 列的格子(记作 (i,j))上写有一个整数 ai,j,你可以把所有格子染上红、橙、黄、绿四种颜色之一。

  • 红色格子的上方只能是红色格子,左边只能是红色或黄色格子,右边只能是红色或橙色格子。
  • 橙色格子的右边只能是橙色格子,上方只能是橙色或红色格子,下方只能是橙色或绿色格子。
  • 绿色格子的下方只能是绿色格子,右边只能是绿色或橙色格子,左边只能是绿色或黄色格子。
  • 黄色格子的左边只能是黄色格子,下方只能是黄色或绿色格子,上方只能是黄色或红色格子。

四种颜色的权值分别为 1,2,3,4,记 wi,j(i,j) 所染颜色的权值,最大化 wi,j×ai,j

数据范围:n,m2000

思路分析

这个做法可以解决 f(c,i,j) 任意给定的情况,其中 f(c,i,j) 表示方格 (i,j) 染颜色 c 的代价。

容易注意到,“红格子加绿格子将网格分成左右两部分”或“黄格子加橙格子将网格分成上下两部分”至少有一个条件成立。

不妨设红格子加绿格子能将网格分成左右两部分,剩余的情况可以旋转网格解决,那么一定存在 (i,j),使得 (i,j) 被染黄,且 (i+1,j+1)/(i+1,j)/(i+1,j+1) 至少有一个被染橙。

(i+1,j) 被染橙,那么原问题就变成了 [1,i]×[1,j],[1,i]×(j,m],(i,n]×[1,j],(i,n]×(j,m] 四个子矩形中分别要选择一个阶梯型区域染色,可以分别简单 dp 求出。

时间复杂度 O(nm)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2005;
const ll inf=4e18;
ll z[MAXN][MAXN],tmp[MAXN][MAXN],S[MAXN][MAXN];
ll a[MAXN][MAXN],b[MAXN][MAXN],c[MAXN][MAXN],d[MAXN][MAXN],tot[MAXN];
ll l[MAXN],r[MAXN],t[MAXN];
ll solve(int n,int m,int U,int D,int L,int R) {
	memset(S,0,sizeof(S)),memset(tot,0,sizeof(tot));
	ll ans=-inf;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) S[i][j]=S[i][j-1]+z[i][j];
		tot[i]=tot[i-1]+S[i][m];
	}
	memset(a,0,sizeof(a)),memset(b,0,sizeof(b));
	memset(c,0,sizeof(c)),memset(d,0,sizeof(d));
	for(int i=n;i>=1;--i) {
		ll w=0;
		for(int j=1;j<=m;++j) w=max(w,c[i+1][j]),c[i][j]=w+(L-D)*S[i][j];
		w=0;
		for(int j=m;j>=1;--j) w=max(w,d[i+1][j]),d[i][j]=w+(R-D)*(S[i][m]-S[i][j-1]);
	}
	for(int i=1;i<=n;++i) {
		ll w=0;
		for(int j=1;j<=m;++j) w=max(w,a[i-1][j]),a[i][j]=w+(L-U)*S[i][j];
		w=0;
		for(int j=m;j>=1;--j) w=max(w,b[i-1][j]),b[i][j]=w+(R-U)*(S[i][m]-S[i][j-1]);
		memset(l,0,sizeof(l)),memset(l,0,sizeof(l));
		for(int j=1;j<=m;++j) l[j]=max(l[j-1],c[i+1][j]);
		for(int j=m;j>=1;--j) r[j]=max(r[j+1],d[i+1][j]);
		for(int j=0;j<=m;++j) {
			ans=max(ans,a[i][j]+b[i][j+1]+l[j]+r[j+1]+tot[i]*U+(tot[n]-tot[i])*D);
		}
		if(i==n) break;
		memset(t,0,sizeof(t));
		for(int j=m;j>=1;--j) t[j]=max(t[j+1],b[i][j]);
		for(int j=1;j<m;++j) {
			ans=max(ans,a[i][j]+d[i+1][j+1]+l[j]+t[j+1]+tot[i]*U+(tot[n]-tot[i])*D);
		}
		memset(t,0,sizeof(t));
		for(int j=1;j<=m;++j) t[j]=max(t[j-1],a[i][j]);
		for(int j=1;j<m;++j) {
			ans=max(ans,b[i][j+1]+c[i+1][j]+t[j]+r[j+1]+tot[i]*U+(tot[n]-tot[i])*D);
		}
	}
	return ans;
}
signed main() {
	ios::sync_with_stdio(false);
	int n,m; cin>>n>>m;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) cin>>z[i][j],tmp[j][i]=z[i][j];
	ll ans=solve(n,m,1,4,3,2);
	swap(z,tmp);
	ans=max(solve(m,n,3,2,1,4),ans);
	cout<<ans<<"\n";
	return 0;
}



B. [P11038] Decomposition

Problem Link

题目大意

给定 n 个点的有根带权树,对于 k=0n1,求出:每个节点可以选择 k 条出边,将其边权变成 0,此时最小的最大深度。

数据范围:n2×105

思路分析

容易想到简单贪心,每次把最深的 k 个儿子边权清零。

fu 表示 u 子树的答案且 vson(u),那么 fumaxfv 并且用第 k+1 大的 fv+w(u,v) 更新 fu

注意到如果一个点的度数 k,那么 fumaxfv,这是简单的,因此我们实际上只要保留度数 >k 的点,并在他们构成的虚树上 dp 即可。

容易发现一个点只会出现 deg(u) 次,因此总的出现次数为均摊 O(n),虚树大小之和也是均摊 O(n)

但是对于一个度数 k 的点,他不在虚树上的儿子 v 也是有贡献的,虽然 fv=0,但我们要考虑 w(u,v) 的贡献。

用平衡树维护 u 的所有出边,如果 vu 虚树上的儿子,那么在平衡树上暴力更新 v 所在子树对应的权值,这部分同样是均摊 O(n) 次的,最后查询第 k+1 大并还原即可。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define ll long long
using namespace std;
using namespace __gnu_pbds;
const int MAXN=2e5+5;
tree<array<ll,2>,null_type,greater<array<ll,2>>,
	rb_tree_tag,tree_order_statistics_node_update> Q[MAXN];
struct Edge { int v,w; };
vector <Edge> G[MAXN],T[MAXN];
int n,deg[MAXN],dfn[MAXN],dcnt,st[MAXN][20],val[MAXN];
int dep[MAXN],up[MAXN][20];
void dfs0(int u,int fz) {
	dep[u]=dep[fz]+1,dfn[u]=++dcnt,st[dcnt][0]=up[u][0]=fz;
	for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
	for(auto e:G[u]) if(e.v^fz) {
		Q[u].insert({e.w,e.v}),val[e.v]=e.w,dfs0(e.v,u),++deg[u];
	}
}
int gs(int x,int r) {
	for(int k=19;~k;--k) if(dep[up[x][k]]>dep[r]) x=up[x][k];
	return x;
}
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int LCA(int x,int y) {
	if(x==y) return x;
	int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
	return cmp(st[l][k],st[r-bit(k)+1][k]);
}
ll f[MAXN];
void dfs1(int u,int k) {
	f[u]=0;
	for(auto e:T[u]) dfs1(e.v,k),f[u]=max(f[u],f[e.v]);
	if(deg[u]<=k) return ;
	for(auto e:T[u]) Q[u].erase({val[e.w],e.w}),Q[u].insert({f[e.v]+val[e.w],e.w});
	f[u]=max(f[u],(*Q[u].find_by_order(k))[0]);
	for(auto e:T[u]) Q[u].erase({f[e.v]+val[e.w],e.w}),Q[u].insert({val[e.w],e.w});
}
void solve(vector<int>&id,int k) {
	sort(id.begin(),id.end(),[&](int x,int y){ return dfn[x]<dfn[y]; });
	for(int i=1,m=id.size();i<m;++i) id.push_back(LCA(id[i-1],id[i]));
	sort(id.begin(),id.end(),[&](int x,int y){ return dfn[x]<dfn[y]; });
	id.erase(unique(id.begin(),id.end()),id.end());
	for(int i=1,m=id.size();i<m;++i) {
		int x=LCA(id[i-1],id[i]);
		T[x].push_back({id[i],gs(id[i],x)});
	}
	dfs1(1,k);
	for(int u:id) T[u].clear();
}
vector <int> id[MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;++i) {
		scanf("%d%d%d",&u,&v,&w);
		G[u].push_back({v,w}),G[v].push_back({u,w});
	}
	dfs0(1,0);
	for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
		st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
	}
	for(int i=0;i<n;++i) id[i].push_back(1);
	for(int u=2;u<=n;++u) for(int i=0;i<deg[u];++i) id[i].push_back(u);
	for(int i=0;i<n;++i) solve(id[i],i),printf("%lld ",f[1]);
	puts("");
	return 0;
}



C. [P10039] Tree

Problem Link

题目大意

给定 n 个点的有根树,和一个点集 S,求有多少有根树满足 S 中任意两点的 LCA 标号都相同(两棵树根可以不同)。

数据范围:n106

思路分析

容易发现题目中的限制等价于 S 的虚树在两棵树中形态不变。

先假定树根等于虚树的根。

求出虚树大小 m,那么枚举 i 个点插入在虚树的边中,剩余的 nmi 个点可以看成一些散点,要和一个大小为 m+i 的块连成树,可以用 Prufer 序列直接算答案,因此答案为:

i=0nm(nmi)(m1)i(m+i)nnmi1

注意到答案只和 n,m 有关,和树的形态无关,可以简单求出 m

如果树根不是虚树的根,那么相当于将新的根变成虚树根的父亲,即 mm+1 后重算答案即可。

时间复杂度 O(n)

代码呈现

#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 ret=1;
	for(;b;a=a*a%MOD,b>>=1) if(b&1) ret=ret*a%MOD;
	return ret;
}
ll fac[MAXN],ifac[MAXN],pw[MAXN];
ll C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
ll calc(int n,int m) {
	ll ans=0;
	for(int i=0;i<=n-m;++i) {
		ans=(ans+fac[m+i-2]*ifac[m-2]%MOD*C(n-m,i)%MOD*(i<n-m?((m+i)*pw[n-m-i-1]%MOD):1))%MOD;
	}
	return ans;
}
vector <int> G[MAXN];
int n,m,cnt;
bool f[MAXN];
void dfs(int u) {
	bool ok=f[u];
	for(int v:G[u]) dfs(v),ok|=(f[u]&&f[v]),f[u]|=f[v];
	cnt+=ok;
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=fac[0]=pw[0]=1;i<=n;++i) fac[i]=fac[i-1]*i%MOD,pw[i]=pw[i-1]*n%MOD;
	ifac[n]=ksm(fac[n]);
	for(int i=n;i;--i) ifac[i-1]=ifac[i]*i%MOD;
	for(int i=2,u;i<=n;++i) scanf("%d",&u),G[u].push_back(i);
	for(int i=1,x;i<=m;++i) scanf("%d",&x),f[x]=true;
	dfs(1);
	ll ans=calc(n,cnt);
	if(cnt<n) ans=(ans+(n-cnt)*calc(n,cnt+1))%MOD;
	printf("%lld\n",ans);
	return 0;
}



D. [P10926] Operation

Problem Link

题目大意

给定 n,定义 U={0,,2n1}

对于一个集合 SU,定义 B(S)=USD(S)={xXxXS}

给定一个长度为 m,由 B,D 组成的操作序列。

维护一个集合序列 A,初始 A={S},支持如下 q 次操作。

  • A 的末尾中加入某个 Av 顺次经过操作序列中 lr 元素后的结果。
  • 查询是否有 kAv
  • 对某个 Av,求他在生成过程中包含了几次 k

数据范围:n18,m,q2×105

思路分析

观察发现 D(S) 等价于将 S 中元素变成 span(S) 中所有元素。

注意到 |S|2n1+[0S] 时一定有 D(S)=U

因为此时 |D(S)||S|,若 0S 那么 0D(S) 此时 |D(S)|>|S|,总有 |D(S)|>2n1|D(S)|=2n

手玩发现对于所有 SUD(S) 都满足此条件,此时 D(UD(S))=U

进一步手玩会发现 S 只可能是如下四种元素或他们的补集:SD(S)/D(US) 中不等于 U 的一个,U{0},其中 {0} 会出现是因为 D()={0}

因此我们可以用线段树快速维护某个 S 经过一段区间的操作后变成了什么,以及这个过程中 8 种可能的 S 分别出现了几次,然后就可以简单的维护上述询问了。

时间复杂度 O(2nn+mδ2logm+qδlogm),其中 δ=8

代码呈现

#include<bits/stdc++.h>
using namespace std;
typedef array<int,8> arr;
const int MAXN=1<<18;
int n,m,q,siz[8];
bool a[4][MAXN];
//0:A, 1:~A, 2:D(A), 3:~D(A), 4:U, 5:0, 6:1, 7:~1
struct LB {
	int t[20],z;
	LB() { memset(t,0,sizeof(t)),z=0; }
	void ins(int x) {
		for(int k=n-1;~k;--k) if(x>>k&1) {
			if(!t[k]) return ++z,t[k]=x,void();
			x^=t[k];
		}
	}
	void dfs(bool *f) {
		int p=0;
		for(int i=0;i<n;++i) if(t[i]) p|=1<<i;
		for(int s=0;s<(1<<n);++s) if((s&p)==s) {
			int x=0;
			for(int i=0;i<n;++i) if(s>>i&1) x^=t[i];
			f[x]=true;
		}
	}
};
arr B={1,0,3,2,5,4,7,6},D={4,4,2,4,4,6,6,4};
char op[MAXN];
int val[MAXN];
arr cnt[MAXN];
struct info {
	arr p,f[8]; //start from x, cnt #y
	info() { p.fill(0); for(int i=0;i<8;++i) f[i].fill(0); }
	friend info operator +(const info &L,const info &R) {
		info z;
		for(int x=0;x<8;++x) {
			z.f[x]=L.f[x],z.p[x]=R.p[L.p[x]];
			for(int y=0;y<8;++y) z.f[x][y]+=R.f[L.p[x]][y];
		}
		return z;
	}
	void add(int &x,arr &g) {
		for(int y=0;y<8;++y) g[y]+=f[x][y];
		x=p[x];
	}
};
struct SegmentTree {
	info tr[MAXN<<1];
	void init(int l=1,int r=m,int p=1) {
		if(l==r) {
			tr[p].p=(op[l]=='D'?D:B);
			for(int x=0;x<8;++x) ++tr[p].f[x][tr[p].p[x]];
			return ;
		}
		int mid=(l+r)>>1;
		init(l,mid,p<<1),init(mid+1,r,p<<1|1);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	void qry(int ul,int ur,int&x,arr&g,int l=1,int r=m,int p=1) {
		if(ul<=l&&r<=ur) return tr[p].add(x,g);
		int mid=(l+r)>>1;
		if(ul<=mid) qry(ul,ur,x,g,l,mid,p<<1);
		if(mid<ur) qry(ul,ur,x,g,mid+1,r,p<<1|1);
	}
}	TR;
int Q(int c,int x) { return a[c>>1][x]^(c&1); }
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m>>q;
	for(int i=0;i<(1<<n);++i) cin>>a[0][i];
	if(n==1) D[0]=0,D[1]=1;
	else {
		LB S,T;
		for(int i=0;i<(1<<n);++i) (a[0][i]?S:T).ins(i);
		if(S.z<n) D[0]=2,S.dfs(a[1]);
		if(T.z<n) D[1]=2,T.dfs(a[1]);
	}
	for(int i=0;i<(1<<n);++i) a[2][i]=1;
	a[3][0]=1;
	for(int c:{0,1,2,3}) {
		int x=0;
		for(int i=0;i<(1<<n);++i) x+=a[c][i];
		siz[c<<1]=x,siz[c<<1|1]=(1<<n)-x;
	}
	for(int i=1;i<=m;++i) cin>>op[i];
	TR.init(),val[0]=0;
	for(int z=0,w,v,k,l,r;q--;) {
		cin>>w>>v;
		if(w==1) {
			cin>>l>>r,++z,val[z]=val[v],cnt[z].fill(0);
			TR.qry(l,r,val[z],cnt[z]);
			cout<<siz[val[z]]<<"\n";
		} else {
			cin>>k;
			if(w==2) cout<<Q(val[v],k)<<"\n";
			else {
				int s=0;
				for(int c=0;c<8;++c) if(Q(c,k)) s+=cnt[v][c];
				cout<<s<<"\n";
			}
		}
	}
	return 0;
}




Round #2 - 20240910

A. [P10891] Set

Problem Link

题目大意

给定 n 阶排列 a1an,定义 Si 表示 a[i,n] 的前缀最大值对应的下标集合。

q 次询问给定 l,r,求 lxyr|SxSy|x<lr<y|SxSy|

数据范围:n,q107

思路分析

显然 |SxSy| 不好求,但可以用容斥原理转成 |SxSy|

考虑这个东西怎么刻画,不妨设 x<y,那么 SxSy 等于 Sxy 的元素构成的集合。

但这个刻画方式依然关系 Sx 的具体结构,考虑另一种刻画方法,找到 Sx<y 的最大元素 z,容易发现插入 zSz=SxSy+{z}

又因为对于所有 i[x,y),|SxSy|<Si,因此 |SxSy|=mini=xy1|Si|1

这样我们就成功地将问原式转成和 S 无关的结构了。

f(L,R)=xL,yR,xy|SxSy|,那么我们要求的就是 f([1,l),(r,n])f([l,r],[l,r]),不难发现这就等于 f([1,n],[1,n])f([1,r],[1,r])f([l,n],[l,n])

那么我们只要预处理 Li=f([1,i],[1,i])Ri=f([i,n],[i,n]) 即可,用单调栈维护 ii1/i+1 时的变化量(所有后缀 min 之和)即可,这是容易的。

时间复杂度 O(n+q)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e7+5,MOD=998244353;
int n,X,C,q,a[MAXN];
int stk[MAXN],tp,pre[MAXN],s[MAXN];
int fl[MAXN],fr[MAXN];
ll W(ll x) { return x*(x+1)>>1; }
signed main() {
	scanf("%d%d%d%d",&n,&X,&C,&q);
	iota(a+1,a+n+1,1);
	for(int i=1,l,r;i<=C;++i) {
		l=(1ll*X*(X^i))%n+1;
		r=(X^(1ll*i*i))%n+1;
		swap(a[l],a[r]);
	}
	for(int i=n;i>=1;--i) {
		while(tp&&a[stk[tp]]<a[i]) pre[stk[tp--]]=i+1;
		stk[++tp]=i,s[i]=tp;
	}
	for(int i=1;i<=tp;++i) pre[stk[i]]=1;
	stk[tp=0]=0;
	for(int i=1,sum=0;i<n;++i) {
		while(tp&&s[stk[tp]]>s[i]) {
			sum=(sum-1ll*(s[stk[tp]]-1)*(stk[tp]-stk[tp-1]))%MOD,--tp;
		}
		sum=(sum+1ll*(s[i]-1)*(i-stk[tp]))%MOD,stk[++tp]=i;
		if(sum<0) sum+=MOD;
		fl[i+1]=(fl[i]+sum)%MOD;
	}
	stk[tp=0]=n;
	for(int i=n-1,sum=0;i>=1;--i) {
		while(tp&&s[stk[tp]]>s[i]) {
			sum=(sum-1ll*(s[stk[tp]]-1)*(stk[tp-1]-stk[tp]))%MOD,--tp;
		}
		sum=(sum+1ll*(s[i]-1)*(stk[tp]-i))%MOD,stk[++tp]=i;
		if(sum<0) sum+=MOD;
		fr[i]=(fr[i+1]+sum)%MOD;
	}
	for(int i=1;i<=n;++i) s[i]=(s[i]+s[i-1])%MOD;
	int res=0;
	for(int o=1,l,r;o<=q;++o) {
		l=(1ll*X*o+(X^(1ll*X*o)))%n+1;
		r=(X-o+(X^(X+o)))%n+1;
		if(l>r) swap(l,r);
		int ans=(1ll*(r-l+1)*(s[r]-s[l-1])%MOD+fl[n]-fl[r]-fr[l])%MOD;
		ans=(ans-1ll*(l-1)*(s[n]-s[r])%MOD-1ll*(n-r)*s[l-1]%MOD)%MOD;
		if(ans<0) ans+=MOD;
		res^=ans;
	}
	printf("%d\n",res);
	return 0;
}



B. [P10872] String

Problem Link

题目大意

对于两个长度为 n 的 01 串 S,T,定义 f(S,T) 表示所有 S,T 的循环同构串中,公共元素最多的一对。

给定 S,T1 的个数 x,y,构造 S,T 使 S,T 最小。

数据范围:n5×105

思路分析

考虑 S1 的位置 s0sx1T1 的位置 t0ty1

容易发现偏移量为 sitj 时,sitj 会产生 1 的贡献。

那么我们就要求 fk=[sitj=k] 尽可能平均。

很显然 fk 的最小值就是 xyn,并且这是容易构造的:

  • si=itj=xjmodn,那么 tjfk 的贡献就是 f[xj,x(j+1)) 区间 +1

    容易证明这就是最平均的构造,如果 y>ngcd(n,x),那么接着取 tj=xj1modn 即可。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
signed main() {
	ios::sync_with_stdio(false);
	int n,x,y;
	cin>>n>>x>>y;
	string s(n,'0'),t(n,'0');
	cout<<(1ll*x*y+n-1)/n<<"\n";
	for(int i=0;i<x;++i) s[i]='1';
	int z=__gcd(n,x);
	for(int i=0;y;++i) {
		int p=i;
		for(int k=0;k<n/z;++k) {
			++t[(n-p)%n],p=(p+x)%n;
			if(!--y) break;
		}
	}
	cout<<s<<"\n"<<t<<"\n";
	return 0;
}



C. [P10873] Guess

Problem Link

题目大意

n 个人,每个人的帽子为黑色或白色,每个人可以看到其他人的帽子颜色。

你要为每个人确定一组猜自己头上帽子颜色的策略,使得任何情况下,如果当前有 k 个戴黑帽子的人,那么其中猜对自己帽子颜色的人不少于 k/2 个,戴白帽子且猜对的人不少于 (nk)/2 个。

数据范围:n18

思路分析

从一个简化的问题的开始,如果想让猜对自己帽子颜色的人总数不少于 n/2 怎么做。

考虑帽子为黑色的人的集合 S,对于某个人 i,设 iS,他在 SS+{i} 中只能猜对一个。

因此我们可以将 SS+{i} 连一条边,并对整张图定向,如果最终图中的边是 SS+{i},就在当前状态下猜测帽子颜色为白,否则猜测黑。

那么对于每种状态,猜对的人等于其出度,要求就是每个点出度至少为入度 1

这是经典欧拉回路模型,把所有度数为奇数的点向一个虚点连边,求出欧拉回路后每个点出度与入度之差 [1,1]

但这题要对黑帽子的人和白脑子的人分别限制,那么拆点表示两个限制,连边 (S,0),(S+{i},1) 即可。

时间复杂度 O(n22n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+5,MAXM=1e7+5;
struct Edge { int v,id,x; };
vector <Edge> G[MAXN];
bool vis[MAXM];
string S[20];
int n,m,cur[MAXN];
int ns(int s,int x) {
	int t=0;
	for(int i=0;i<n;++i) if(i^x) t=t<<1|(s>>i&1);
	return t;
}
void dfs(int u) {
	for(int &i=cur[u];i<(int)G[u].size();++i) {
		Edge e=G[u][i];
		if(vis[e.id]) continue;
		vis[e.id]=true,dfs(e.v);
		if(~e.x) S[e.x][ns(u,e.x)]="BC"[u>>e.x&1];
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=0;i<n;++i) S[i]=string(1<<(n-1),'.');
	int U=(1<<n);
	for(int s=0;s<U;++s) for(int i=0;i<n;++i) if(s>>i&1) {
		G[s|U].push_back({s^(1<<i),m,i}),G[s^(1<<i)].push_back({s|U,m,i}),++m;
	}
	for(int s=0;s<2*U;++s) if(G[s].size()&1) {
		G[s].push_back({2*U,m,-1}),G[1<<n].push_back({2*U,m,-1}),++m;
	}
	for(int i=0;i<=2*U;++i) dfs(i);
	for(int i=0;i<n;++i) cout<<S[i]<<"\n";
	return 0;
}



D. [P8421] Interval

Problem Link

题目大意

给定 a1an,b1bn,c1cn,记 f(l,r)=(ANDi=lrai)×(ORi=lrbi)×(gcdi=lrci)

q 次询问 l,r 内所有子区间的权值和。

数据范围:ai,bi,ci,n106,q5×106

思路分析

考虑 f(l,l)f(l,n) 的过程,我们发现所有 r1r 的过程中,只有 O(logV)r 会改变 a/b/c 的值。

因此会对 f(l,r) 产生改变的 r 只有 O(logV) 个。

r 扫描线,求有多少个 lf(l,r1)f(l,r) 的过程中的权值产生变化,容易发现有变化的 l 一定是一段后缀,设 k 为其中最小的 l,容易发现 l 的总量为 O(nlogV) 级别。

为了快速求答案,我们维护 si 表示所有 li 的区间的权值和。

对于 [1,k) 中的 if 的值不变,si 的变化量和前一次(r2r1 时)相同。

对于 [k,i] 中的 i,暴力计算新的 fsi 的变化量为 si1 的变化量加上 f(i,r)

因此容易把 si 写成关于 r 的一次函数的形式,总共只会均摊修改 O(nlogV) 个一次函数。

gcd 可以用 O(V)O(1) 的快速 gcd 优化复杂度。

时间复杂度 O(nlogV)

代码呈现

#include<bits/stdc++.h>
#define ui unsigned int
using namespace std;
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
	int x=0; char c=gc();
	while(!isdigit(c)) c=gc();
	while(isdigit(c)) x=x*10+(c^48),c=gc();
	return x;
}
void flush() {
	fwrite(obuf,ow,1,stdout),ow=0;
}
void write(ui x) {
	if(!x) obuf[ow++]='0';
	else {
		int t=ow;
		for(;x;x/=10) obuf[ow++]=(x%10)^48;
		reverse(obuf+t,obuf+ow);
	}
	if(ow>=olim) flush();
}
void putc(char c) {
	obuf[ow++]=c;
	if(ow>=olim) flush();
}
#undef gc
}
namespace gcd {
const int MAXN=1e6+5,B=1005;
vector <int> pr;
array<int,3> a[MAXN];
bool isc[MAXN];
int val[B][B];
void init() {
	a[1]={1,1,1};
	for(int i=2;i<MAXN;++i) {
		if(!isc[i]) pr.push_back(i),a[i]={1,1,i};
		for(int p:pr) {
			if(i*p>=MAXN) break;
			isc[i*p]=true,a[i*p]=a[i],a[i*p][0]*=p;
			sort(a[i*p].begin(),a[i*p].end());
			if(i%p==0) break;
		}
	}
	for(int i=0;i<B;++i) val[i][0]=val[0][i]=i;
	for(int i=1;i<B;++i) for(int j=1;j<=i;++j) val[j][i]=val[i][j]=val[j][i%j];
}
int q(int x,int y) {
	int ans=1;
	for(int k:a[x]) {
		int w=(k<B?val[k][y%k]:(y%k?1:k));
		ans*=w,y/=w;
	}
	return ans;
}
}
using IO::read;
const int MAXN=1e6+5,MAXQ=5e6+5;
ui x[MAXN],y[MAXN];
int a[MAXN],b[MAXN],c[MAXN];
int hd[MAXN],L[MAXQ],lst[MAXQ],ans[MAXQ];
signed main() {
	int n=read(),q=read(); gcd::init();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) b[i]=read();
	for(int i=1;i<=n;++i) c[i]=read();
	for(int i=1,r;i<=q;++i) L[i]=read(),r=read(),lst[i]=hd[r],hd[r]=i;
	for(int i=1;i<=n;++i) {
		int k=i-1;
		for(;k>0;--k) {
			int A=a[k]&a[k+1],B=b[k]|b[k+1],C=gcd::q(c[k],c[k+1]);
			if(A==a[k]&&B==b[k]&&C==c[k]) break;
			a[k]=A,b[k]=B,c[k]=C;
		}
		x[i]=x[i-1],y[i]=y[i-1];
		for(int j=k+1;j<=i;++j) {
			ui val=x[j]*(i-1)+y[j];
			x[j]=x[j-1]+(ui)a[j]*b[j]*c[j];
			y[j]=val-x[j]*(i-1);
		}
		ui V=x[i]*i+y[i];
		for(int j=hd[i];j;j=lst[j]) ans[j]=V-(x[L[j]-1]*i+y[L[j]-1]);
	}
	for(int i=1;i<=q;++i) IO::write(ans[i]),IO::putc('\n');
	IO::flush();
	return 0;
}



*E. [P10896] Bracket

Problem Link

题目大意

定义一个括号串 S 的权值为其最大括号匹配的大小。

对于 m 个括号串 S1Sm,定义其权值为:任意重排 S1Sm 后连接成完整括号串 SS 最小的权值。

已知 S1Sm 的长度,在 Si 的每个字符都均匀随机生成的情况下,求每组 S1Sm 的期望。

数据范围:M=|Si|4×106

思路分析

考虑如何刻画一个括号串 S 的权值,考虑一个经典结论,将 S 看成 ±1 序列,即每一步向右上或右下方移动的折线。

求出起点到最低点的距离 L,终点到最低点的距离 R,那么 S 中删除 L+R 个括号后就可以完美匹配,即其权值为 |S|LR2,因此我们只要求 Ex(L+R)

然后考虑刻画 S1Sm 的权值,类似对每个串定义 Li,Ri,不妨假设整个串的最低点值在 Si 上,那么第 i 个串对答案的贡献是 Li+Ri

那么对于其他的每个串 j,如果 Lj>Rj,把 Sj 放在 Si 左边,否则 Sj 放在 Si 右边,每个 j 对答案的贡献都是 |LjRj|

那么一组 S1Sn 的权值就是 maxiLi+Ri+ji|LjRj|

注意到 Li+Ri=|LiRi|+2min(Li,Ri),因此其权值又等于 j=1m|LjRj|+2maxi{min(Li,Ri)}

考虑拆贡献,先处理前一部分的贡献,我们可以对于每个 j 分别算出 Ex(|LjRj|),注意到这个值就等于 Si 的左括号个数减去右括号个数,容易做到 O(aj)

因此我们就将原问题转成求 Ex(max{min(Li,Ri)}),考虑对每个 k1 求出 max{min(Li,Ri)}k 的概率求和。

反面考虑,等价于对每个 k0 求出 max{min(Li,Ri)}k 的概率求和,这又相当于对每个 i 求出 min(Li,Ri)k 的概率乘起来。

因此我们得到:

Ex(max{min(Li,Ri)})=k=01i=1mPr(min(Li,Ri)k)

考虑如何解决单个 Pr(min(Li,Ri)k),以下均记 n=ai

这就相当于求有多少条折线长度为 n,起点 k 或终点 k,且最低高度恰好为 0

考虑 n1n 时答案的变化量,相当于求新加入一步后,会有多少路径从合法变不合法或从不合法变合法。

  • 只有起点高度 >k 且最后一步终点从 k+1k 的路径会从不合法变合法。
  • 只有起点高度 >k 且最后一步终点从 kk+1 的路径会从不合法变合法。

我们设 f(m,s,t) 表示长度为 m,起点高度 >s,终点高度 =t,的折线数量。

初始概率为 1,考虑每次答案变化量的差分得到:

Pr(min(Li,Ri)k)=1+m=1n1(f(m,k,k+1)f(m,k,k))2m1

f(m,s,t),枚举起点高度,然后反射容斥求最低高度恰好为 0 的概率,容易发现正负项可以抵消:

f(m,s,t)=i=s+1(mm+i+t2)(mm+i+t+22)=(mm+s+t+22)

带入得到:

f(m,k,k+1)f(m,k,k)=(mm+2k+32)(mm+2k+2t)

注意到 m 为偶数时 f(m,k,k+1)f(m,k,k)=0,因此我们只要考虑 m=2p1 的情况,其中 p=1n2

Pr(min(Li,Ri)k)=1+p=1n/2((2p1k+p+1)(2p1k+p))22p2

考虑用和式表示,记 w(n,k)=p=1n4p1((2p1k+p+1)(2p1k+p)),观察差分得到:

w(n,k)w(n,k1)=p=1n4p1((2p1k+p+1)2(2p1k+p)+(2p1k+p1))=p=1n4p1((2p1k+p+1)+2(2p1k+p)+(2p1k+p1)4(2p1k+p))=p=1n4p1((2p+1k+p+1)4(2p1k+p))=p=1n4p1(2p+1k+p+1)4p(2p1k+p)=22n(2n+1k+n+1)(1k+1)

边界条件是 w(n,1)=0,注意 k=0(1k+1)=1,带入原式得到:

Pr(min(Li,Ri)k)=p=0k22n/2(2n/2+1k+n/2+1)

注意到 w(n,0)w(n,1) 时的 1 恰好与原式前面的 +1 抵消了。

很显然 k>n/2 的概率肯定为 1,因此我们暴力求组合数前缀和就能 O(ai) 计算出每个 k 的答案。

时间复杂度 O(M)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e6+5,MOD=1e9+7,inv=(MOD+1)/2;
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 fac[MAXN],ifac[MAXN],ipw[MAXN],p[MAXN];
ll C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,m,a[MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),m+=a[i];
	for(int i=fac[0]=ipw[0]=1;i<=m;++i) fac[i]=fac[i-1]*i%MOD,ipw[i]=ipw[i-1]*inv%MOD;
	ifac[m]=ksm(fac[m]);
	for(int i=m;i;--i) ifac[i-1]=ifac[i]*i%MOD;
	for(int i=0;i<=m;++i) p[i]=1;
	for(int o=1;o<=n;++o) {
		int x=a[o]/2; ll w=0;
		for(int i=0;i<=x;++i) w=(w+C(2*x+1,i+x+1)*ipw[2*x])%MOD,p[i]=p[i]*w%MOD;
	}
	ll ans=0,sum=0;
	for(int i=0;i<m;++i) ans=(ans+1+MOD-p[i])%MOD;
	for(int o=1;o<=n;++o) {
		int x=a[o];
		for(int i=0;i<=x;++i) sum=(sum+ipw[x]*C(x,i)%MOD*abs(2*i-x))%MOD;
	}
	printf("%lld\n",(m+MOD-(sum+2*ans)%MOD)*inv%MOD);
	return 0;
}




Round #3 - 20240912

A. [P10163] Square

Problem Link

题目大意

给定 n 个点 m 条边的有向图,满足边看成无向边后图是森林,增加若干点和有向边,使得每个点出度都是完全平方数,且边看成无向边后图是一棵树。

数据范围:n105

思路分析

对每个点,计算出至少加多少出边后出度变成平方数,算出需要加的边数量总和 k,以及原图的联通块数 c

如果 kc,说明需要增加额外点和边,增加 kc+1 个额外点,然后每个点随便连一些出边即可。

具体构造的时候可以取出所有每个点都合法的连通块,每个点都连向其中的一个连通块,剩余的连通块每个点匹配一个即可。

如果 k<c,那么不需要增加额外点,我们用类似上面的构造把每个点的出度都调整成平方数,但图不一定联通。

注意到每个连通块中一定有出度为 0 的点,将这些点连成链即可。

时间复杂度 O(n+m)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1.1e5+5,B=317;
int w[MAXN],d[MAXN],dsu[MAXN],id[MAXN];
bool typ[MAXN];
vector <int> P[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
signed main() {
	for(int i=0,j=0;i<=B;++i) while(j<=i*i) w[j++]=i*i;
	ios::sync_with_stdio(false);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i) dsu[i]=i;
	for(int i=1,u,v;i<=m;++i) cin>>u>>v,++d[u],dsu[find(u)]=find(v);
	int c=0,k=0;
	for(int i=1;i<=n;++i) c+=(dsu[i]==i),k+=w[d[i]]-d[i];
	if(!k&&c==1) return cout<<"0 0\n",0;
	if(k>=c) {
		cout<<k-c+1<<" "<<k<<"\n";
		for(int i=1;i<=n;++i) P[find(i)].push_back(i),typ[find(i)]|=(w[d[i]]>d[i]);
		vector <int> a,b;
		for(int i=1;i<=n;++i) if(dsu[i]==i) (typ[i]?a:b).push_back(i);
		for(int i=1;i<=k-c+1;++i) b.push_back(n+i);
		int o=1;
		for(int x:a) {
			bool f=0;
			for(int u:P[x]) if(w[d[u]]>d[u]) {
				int r=w[d[u]]-d[u];
				if(!f) cout<<u<<" "<<b[0]<<"\n",--r,f=1;
				while(r--) cout<<u<<" "<<b[o++]<<"\n";
			}
		}
	} else {
		cout<<"0 "<<c-1<<"\n";
		for(int i=1;i<=n;++i) P[find(i)].push_back(i),typ[find(i)]|=(w[d[i]]>d[i]);
		vector <int> a,b;
		for(int i=1;i<=n;++i) if(dsu[i]==i) (typ[i]?a:b).push_back(i);
		if(b.empty()) b.push_back(a.back()),a.pop_back();
		int o=1;
		for(int x:a) {
			bool f=0;
			for(int u:P[x]) if(w[d[u]]>d[u]) {
				int r=w[d[u]]-d[u];
				if(!f) cout<<u<<" "<<b[0]<<"\n",--r,f=1,++d[u],dsu[find(u)]=find(b[0]);
				while(r--) cout<<u<<" "<<b[o]<<"\n",++d[u],dsu[find(u)]=find(b[o++]);
			}
		}
		for(int i=1;i<=n;++i) if(!d[i]) id[find(i)]=i;
		vector <int> z;
		for(int i=1;i<=n;++i) if(dsu[i]==i) z.push_back(id[i]);
		for(int i=1;i<(int)z.size();++i) cout<<z[i-1]<<" "<<z[i]<<"\n";
	}
	return 0;
}




B. [P10598] Bigraph

Problem Link

题目大意

给定左右各 n 个点 m 条边的二分图,求最大的完全二分子图,存在多个求左部点数最多的一个。

求在这张子图选出 k 条边后,每个点至少有一条出边被选的方案数。

数据范围:n50,m,kn2

思路分析

第一问等价于反图最大独立集,最大化左部点数可以将左部点点权设为 n+1,右部点点权设为 n,求出最大权闭合子图,跑最大流即可。

第二问只关心前一问求出的左右部点集大小 x,y,容斥两边空点数量得到答案为:

i=0xj=0y(1)i+j(xi)(yj)((xi)(yj)k)

时间复杂度 O(Flow(n,m)+k2)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
namespace F {
const int MAXV=205,MAXE=2e5+5;
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; }
void link(int u,int v,int w) { adde(u,v,w),adde(v,u,0); }
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;
}
}
using F::link;
bool g[55][55];
const int MOD=19921228;
int C[2505][2505];
signed main() {
	int n,m,k;
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),g[u][v]=true;
	int s=F::S=2*n+1,t=F::T=2*n+2;
	for(int i=1;i<=n;++i) link(s,i,101),link(i+n,t,100);
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(!g[i][j]) link(i,j+n,inf);
	int ans=201*n-F::Dinic();
	int x=ans%100,y=ans/100-x;
	printf("%d %d\n",x,y);
	int tot=x*y;
	for(int i=0;i<=tot;++i) for(int j=C[i][0]=1;j<=i;++j) {
		C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
	int cnt=0;
	for(int i=0;i<=x;++i) for(int j=0;j<=y;++j) if((x-i)*(y-j)>=k) {
		int w=1ll*C[(x-i)*(y-j)][k]*C[x][i]%MOD*C[y][j]%MOD;
		if((i+j)&1) cnt=(cnt+MOD-w)%MOD;
		else cnt=(cnt+w)%MOD;
	}
	printf("%d\n",cnt);
	return 0;
}



C. [P10646] Sentence

Problem Link

题目大意

给定字符串序列 S1Sn,对于一个字符串序列 T1Tm,其权值定义为其所有长度为 k+1 的子区间 TiTi+kS 中出现次数的最小值。

q 次询问给定 m,T1Tk,构造 Tm+1Tm+k 最大化 T 的权值。

数据范围:n,m5×105,q105,|Si|,|Ti|,k10

思路分析

显然可以把 S1Sn 映射成 [1,n] 的整数,我们可以把 S1Sn 的每个长度为 k 的子区间离散化成节点,每个长度为 k+1 的子区间对应的就是一条边。

那么我们设每条边的边权就是这个子区间的出现次数,询问等价于求从某个点出发走 m 步后路径最小值最大是多少。

注意到边权总和为 O(n) 级别,因此对于每个 x,权值 x 的边总数是均摊 O(n) 的,只考虑这些边的端点,点数也是均摊 O(n) 的。

快速回答询问只需要在反图上拓扑排序,求出每个点的最长路,仅考虑权值 x 的边构成的图时,若最长路 <m 时则该询问的答案就是 x1

对每个起点上的询问按 m 排序,每次确定答案的都是一段后缀,用栈维护。

拓扑排序时记录最优转移即可构造方案。

注意我们需要在考虑权值 x 的图时弹出一些询问,这些询问要用到权值 x1 上的最长路构造,滚动数组一下即可。

时间复杂度 O(n+m),忽略离散化过程中用 map 维护的开销。

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5,inf=1e9;
struct Edge { int u,v,c; };
struct Graph {
	int deg[MAXN],f[MAXN],nxt[MAXN],col[MAXN];
	vector <Edge> G[MAXN];
	void build(const vector<int>&V,const vector<Edge>&E) {
		if(V.empty()||E.empty()) return ;
		for(int i:V) deg[i]=f[i]=nxt[i]=col[i]=0,G[i].clear();
		for(auto e:E) ++deg[e.u],G[e.v].push_back({e.v,e.u,e.c});
		queue <int> q;
		for(int i:V) if(!deg[i]) q.push(i);
		while(q.size()) {
			int u=q.front(); q.pop();
			for(auto e:G[u]) {
				if(f[u]+1>f[e.v]) f[e.v]=f[u]+1,col[e.v]=e.c,nxt[e.v]=u;
				if(!--deg[e.v]) q.push(e.v);
			}
		}
		for(auto e:E) if(deg[e.u]&&deg[e.v]) nxt[e.u]=e.v,col[e.u]=e.c;
	}
	int qry(int x) { return deg[x]?inf:f[x]; }
	void dfs(int u,int k,vector<int>&x) {
		while(k--) x.push_back(col[u]),u=nxt[u];
	}
}	G[2];
int a[MAXN],b[MAXN],c[MAXN];
map <string,int> sid;
string wrd[MAXN],str;
map <vector<int>,int> qid;
map <array<int,2>,array<int,2>> edg;
vector <Edge> E[MAXN];
vector <int> V[MAXN];
vector <array<int,2>> Q[MAXN];
vector <int> ans[MAXN];
signed main() {
	ios::sync_with_stdio(false);
	int n,k,q,m=0;
	cin>>n>>k;
	for(int i=1,tot=0;i<=n;++i) {
		cin>>str;
		if(!sid.count(str)) sid[str]=++tot,wrd[tot]=str;
		a[i]=sid[str];
	}
	for(int i=1;i+k-1<=n;++i) {
		vector <int> t(a+i,a+i+k);
		if(!qid.count(t)) qid[t]=++m;
		b[i]=qid[t];
	}
	for(int i=1;i+k<=n;++i) {
		int u=b[i],v=b[i+1];
		if(!edg.count({u,v})) edg[{u,v}]={1,a[i+k]};
		else ++edg[{u,v}][0];
	}
	for(auto e:edg) {
		int u=e.first[0],v=e.first[1],w=e.second[0],h=e.second[1];
		c[u]=max(c[u],w),c[v]=max(c[v],w);
		for(int i=1;i<=w;++i) E[i].push_back({u,v,h});
	}
	for(int i=1;i<=m;++i) for(int j=1;j<=c[i];++j) V[j].push_back(i);
	cin>>q;
	for(int o=1,e;o<=q;++o) {
		cin>>e;
		vector <int> p;
		bool flg=1;
		for(int i=0;i<k;++i) {
			cin>>str;
			if(!sid.count(str)) p.push_back(-1),flg=0;
			else p.push_back(sid[str]);
		}
		if(!flg||!qid.count(p)) ans[o].resize(e,1);
		else Q[qid[p]].push_back({e,o});
	}
	for(int i=1;i<=m;++i) sort(Q[i].begin(),Q[i].end());
	for(int o=1;o<=n-k;++o) {
		G[o&1].build(V[o],E[o]);
		for(int i:V[o]) {
			while(Q[i].size()&&Q[i].back()[0]>G[o&1].qry(i)) {
				auto z=Q[i].back();
				if(o==1) ans[z[1]].resize(z[0],1);
				else G[(o&1)^1].dfs(i,z[0],ans[z[1]]);
				Q[i].pop_back();
			}
			if(c[i]==o) {
				while(Q[i].size()) {
					auto z=Q[i].back();
					G[o&1].dfs(i,z[0],ans[z[1]]);
					Q[i].pop_back();
				}
			}
		}
	}
	for(int i=1;i<=q;++i) {
		for(int x:ans[i]) cout<<wrd[x]<<" ";
		cout<<"\n";
	}
	return 0;
}



*D. [P9293] Addition

Problem Link

题目大意

给定二进制数 a1an,求 b1bn 满足 biaibi 二进制表示两两无交,最小化 bi

数据范围:n,m=|ai|3×105|x| 表示 x 的二进制表示的位数。。

思路分析

先考虑如何判断一个 bi 合法,从高到低考虑每个二进制位 x 和当前最大的 ai

  • 如果 |ai|<x 那么用 2x 直接消掉 ai
  • 如果 |ai|=x 那么得到 ai2x
  • 如果 |ai|>x 无解。

不断维护这个过程即可完成判定。

观察最高位 D 的取值范围,我们设 ai 已经是降序排列的,那么对于每个 i 一定有 D|ai|+i1,因为至少要花 i1 位处理 a1ai1

又观察到 D>max{|ai|+i1} 时,每个 |ai| 都小于当前的 x

因此 D 的取值范围是 [max{|ai|+i1},max{|ai|+i}]

先判定 D=max{|ai|+i1} 是否合法,设该最大值在 x 处首次取到,那么 a1ax1 直接删除,ax 删掉最高位,然后递归限定最高位 <D 的子问题。

对于 D=max{|ai|+i} 的情况,直接删除所有 a1ax 然后递归。

注意我们在递归时是有最高位限制的,即钦定 D 不超过某个值,这就可能导致一个子状态无解,但全局状态肯定有解。

注意到当前的每个 ai 都是初始 ai 的一段后缀,对每个 ai 的所有后缀排序,线段树维护 max|ai|+i1 是容易的,set 维护 ai 排序后的结果,根据上述过程模拟即可。

我们可以证明这个过程的复杂度是 O(n+m) 的。

时间复杂度 O((n+m)logm)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,inf=1e9;
int m=0;
struct SegmentTree {
	array<int,2> tr[MAXN<<2]; int tg[MAXN<<2];
	void adt(int p,int k) { tr[p][0]+=k,tg[p]+=k; }
	void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
	void init(int l=1,int r=m,int p=1) {
		tr[p]={-inf,r};
		if(l==r) return ;
		int mid=(l+r)>>1;
		init(l,mid,p<<1),init(mid+1,r,p<<1|1);
	}
	void add(int ul,int ur,int k,int l=1,int r=m,int p=1) {
		if(ul<=l&&r<=ur) return adt(p,k);
		int mid=(l+r)>>1; psd(p);
		if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
		if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
		tr[p]=max(tr[p<<1],tr[p<<1|1]);
	}
}	T;
string s[MAXN];
vector <int> rk[MAXN],idx[MAXN];
int n,d[MAXN],a[MAXN],b[MAXN],pre[MAXN];
bool ans[MAXN<<1];
set <int> S;
void upd(int x,int op) {
	if(~op) S.insert(x);
	else S.erase(x);
	T.add(x,x,op*(inf+d[x]));
	if(x>1) T.add(1,x-1,op);
}
bool dfs(int lim) {
	int mx=T.tr[1][0],x=T.tr[1][1];
	if(mx<0) return true;
	if(mx>=lim) return false;
	upd(x,-1);
	if(b[x]) upd(rk[a[x]][b[x]-1],1);
	int c=0; vector <int> del;
	while(S.size()&&*S.rbegin()>x) del.push_back(*S.rbegin()),upd(*S.rbegin(),-1),++c;
	for(int i=0;i<=c;++i) ans[mx-i]=true;
	if(dfs(mx-c)) return true;
	ans[mx-c]=false;
	if(b[x]) upd(rk[a[x]][b[x]-1],-1);
	if(mx+1<lim) {
		ans[mx+1]=true;
		return dfs(mx-c+1);
	} else {
		for(int i=0;i<=c;++i) ans[mx-i]=false;
		upd(x,1);
		for(int i:del) upd(i,1);
		return false;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n; int k=0;
	for(int i=1;i<=n;++i) {
		cin>>s[i],k=max(k,(int)s[i].size());
		reverse(s[i].begin(),s[i].end());
		for(int j=0;j<(int)s[i].size();++j) if(s[i][j]=='1') idx[j].push_back(i);
	}
	for(int i=0;i<k;++i) {
		sort(idx[i].begin(),idx[i].end(),[&](int x,int y){ return pre[x]<pre[y]; });
		for(int x:idx[i]) {
			pre[x]=++m,a[m]=x,d[m]=i;
			b[m]=rk[x].size(),rk[x].push_back(m);
		}
	}
	T.init();
	for(int i=1;i<=n;++i) upd(rk[i].back(),1);
	dfs(inf);
	for(int i=n+k+1;~i;--i) if(ans[i]) {
		for(int j=i;~j;--j) cout<<ans[j];
		return cout<<"\n",0;
	}
	cout<<"0\n";
	return 0;
}



*E. [P10016] Query

Problem Link

题目大意

给定 n 个点的树以及 z1zn,初始 w1wn 全为 0,支持如下操作:

  • 给定 l,r,将编号在 [l,r] 内的节点构成的斯坦纳树上每个点 uwu 加一,保证 l,r 随机。
  • 给定 l,r,u,求 i=lrXzgcd(i,u)wimodY,其中 X=19901991,Y=20242024

数据范围:n,q80000

思路分析

注意到 X2=Y,因此我们只关心 i=lr(zgcd(i,u)wimod2)

考虑用 bitset 维护,我们用一个 bitset 维护当前所有 wimod2 的值,一个 bitset 维护所有 zgcd(i,u) 的值,取交后左移右移一段距离去掉区间外的点即可。

先尝试用 bitset 维护所有 wimod2 的值。

那么我们只要对于每个操作一,求出 [l,r] 斯坦纳树的点集,这可以看成 lr 到根的链并,减去根到区间 LCA 父亲的路径。

第二部分是容易处理掉的,因此我们只要求 [l,r] 节点到根的链并。

考虑分块,如果 l,r 跨过了某个块,那么我们就能把 [l,r] 的链并看成 [l,kB],(kB,r] 两部分的并。

对于每个关键点 kB,考虑算出每个后缀的链并,重复修改是无意义的,那么在后缀增加的过程中,均摊增量是 O(n) 级别,前缀同理。

因此对每个关键点算前后缀链并,复杂度 O(n2B)

对于一个 [l,r] 被某个块完全包含的询问,由于数据随机,因此这种情况出现概率并不高,仅为 O(Bn) 级别,每次 O(n) 暴力可以做到 O(qB)

B=nq 得到最优复杂度 O(nq+nqω)

那么我们只要对每个 u 求出 zgcd(i,u)mod2 对应的 bitset,考虑如何刻画所有 gcd(i,u)

考虑爆搜 u 的所有质因子,当 u 的某个质因子 p 指数从 pcpc+1 变化时,会把 pc+1igcd(i,u) 乘以 p

暴力维护这个过程,精细实现后计算量 T(n)4.3×107

时间复杂度 O(nq+nqω+T(n))

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=8e4+5,B=282,X=19901991,Y=20242024;
int n,q,z[MAXN],bel[MAXN],lp[MAXN],rp[MAXN];
vector <int> G[MAXN];
int fa[MAXN],dfn[MAXN],dcnt,st[MAXN][18],dl[MAXN][18],dr[MAXN][18];
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int qdl(int l,int r) {
	int k=__lg(r-l+1);
	return min(dl[l][k],dl[r-bit(k)+1][k]);
}
int qdr(int l,int r) {
	int k=__lg(r-l+1);
	return max(dr[l][k],dr[r-bit(k)+1][k]);
}
int lca(int l,int r) {
	int k=__lg(r-l+1);
	return cmp(st[l][k],st[r-bit(k)+1][k]);
}
void dfs0(int u,int fz) {
	fa[u]=fz,dl[u][0]=dr[u][0]=dfn[u]=++dcnt,st[dcnt][0]=fz;
	for(int v:G[u]) if(v^fz) dfs0(v,u);
}
int op[MAXN],ql[MAXN],qr[MAXN];
bitset <MAXN> F[MAXN],S;
vector <int> wl[MAXN],wr[MAXN],wx[MAXN],LCA[MAXN];
void add(bitset<MAXN>&s,int u) { for(;u&&!s[u];u=fa[u]) s.set(u); }
void dfs1(int u) {
	S.set(u);
	for(int i:LCA[u]) F[i]^=S;
	for(int v:G[u]) if(v^fa[u]) dfs1(v);
	S.reset(u);
}
int pr[MAXN],tot,g[MAXN];
bool isc[MAXN];
void dfs2(int o,int x,int y) {
	for(int i:wx[x]) F[i]&=S;
	for(int i=o;i<=tot&&1ll*x*pr[i]<=n;++i) {
		int p=pr[i],w=(i==o?y*p:p);
		for(int j=w;j<=n;j+=w) S[j]=z[g[j]*=p];
		dfs2(i,x*pr[i],w);
		for(int j=w;j<=n;j+=w) S[j]=z[g[j]/=p];
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>z[i],z[i]%=2;
	for(int i=1;(i-1)*B+1<=n;++i) {
		lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
		fill(bel+lp[i],bel+rp[i]+1,i);
	}
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs0(1,0);
	for(int k=1;k<18;++k) for(int i=1;i+bit(k)-1<=n;++i) {
		st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
		dl[i][k]=min(dl[i][k-1],dl[i+bit(k-1)][k-1]);
		dr[i][k]=max(dr[i][k-1],dr[i+bit(k-1)][k-1]);
	}
	for(int i=1,x;i<=q;++i) {
		cin>>op[i]>>ql[i]>>qr[i];
		if(op[i]==1) {
			if(ql[i]==qr[i]) LCA[fa[ql[i]]].push_back(i);
			else LCA[fa[lca(qdl(ql[i],qr[i])+1,qdr(ql[i],qr[i]))]].push_back(i);
			if(bel[ql[i]]==bel[qr[i]]) for(int u=ql[i];u<=qr[i];++u) add(F[i],u);
			else wl[bel[ql[i]]].push_back(i),wr[bel[ql[i]]+1].push_back(i);
		} else cin>>x,wx[x].push_back(i);
	}
	for(int o=1;o<=bel[n];++o) {
		sort(wl[o].begin(),wl[o].end(),[&](int x,int y){ return ql[x]>ql[y]; });
		sort(wr[o].begin(),wr[o].end(),[&](int x,int y){ return qr[x]<qr[y]; });
		S.reset();
		for(int i=lp[o],j=0,s=wr[o].size();j<s;++i) {
			add(S,i);
			while(j<s&&qr[wr[o][j]]==i) F[wr[o][j++]]|=S;
		}
		S.reset();
		for(int i=rp[o],j=0,s=wl[o].size();j<s;--i) {
			add(S,i);
			while(j<s&&ql[wl[o][j]]==i) F[wl[o][j++]]|=S;
		}
	}
	S.reset(),dfs1(1);
	for(int i=1;i<=q;++i) F[i]^=F[i-1];
 	for(int i=2;i<=n;++i) if(!isc[i]) {
		pr[++tot]=i;
		for(int j=2*i;j<=n;j+=i) isc[j]=true;
	}
	S.reset();
	for(int i=1;i<=n;++i) S[i]=z[g[i]=1];
	dfs2(1,1,1);
	for(int i=1;i<=q;++i) if(op[i]==2) {
		int l=ql[i],r=qr[i];
		F[i]>>=l,F[i]<<=(MAXN-(r-l+1));
		int s=F[i].count();
		cout<<(1ll*s*(X-1)+r-l+1)%Y<<"\n";
	}
	return 0;
}




Round #4 - 20240913

A. [P10531] Choose

Problem Link

题目大意

n 个点的树选出最多的个不相交连通块,使得每个连通块内颜色数 k

数据范围:n2×105

思路分析

考虑自底向上贪心,如果一个子树颜色数 k,那么肯定把这个子树到父亲的边断掉最优。

维护每个子树剩余的颜色数直接用 std::unordered_set 启发式合并即可。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,k,c[MAXN],ans=0;
vector <int> G[MAXN];
unordered_set <int> f[MAXN];
void dfs(int u,int fz) {
	f[u].insert(c[u]);
	for(int v:G[u]) if(v^fz) {
		dfs(v,u);
		if(f[u].size()<f[v].size()) swap(f[u],f[v]);
		for(int x:f[v]) f[u].insert(x);
	}
	if((int)f[u].size()>=k) ++ans,f[u].clear();
}
signed main() {
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i) scanf("%d",&c[i]);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	dfs(1,0),printf("%d\n",ans);
	return 0;
}



B. [P10813] Swap

Problem Link

题目大意

给定 m 个操作 (pi,qi),如果序列 a 满足 api>aqi 就交换两个元素,求有多少长度为 n,值域为 [1,V]a 顺次进行 m 个操作后单调不降。

数据范围:n18,m200,V109

思路分析

考虑 01 分界,即选定一个 x,定义 S={iaix},那么对于每个 S,我们倒序维护进行操作 [x,m] 后能升序排列的所有 S,这部分复杂度 O(m2n)

然后考虑一个 ai,一组 ai 从随着 x 逐渐增加,可以看成一条从 变化到全集的路径,每次当前集合增加如若干 =x 的元素,我们只要对这样的路径计数。

注意到我们只关心 ai 中本质不同的颜色数,那么考虑每次加入一种最大的颜色,就会把当前的 S 转移到 S 的一个合法超集上,可以 FWT 解决。

全集的方案数即为合法序列数,乘一个选颜色的组合数即可。

时间复杂度 O((m+n2)2n)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
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; }
bool ok[1<<18],tk[1<<18];
int f[1<<18],g[1<<18];
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void sub(int &x,int y) { x=(x>=y)?x-y:x+MOD-y; }
ll C(int x,int y) {
	ll s=1;
	for(int i=1;i<=y;++i) s=s*ksm(i)%MOD*(x-i+1)%MOD;
	return s;
}
signed main() {
	int n,V,m;
	scanf("%d%d%d",&n,&V,&m);
	vector <array<int,2>> opr(m);
	for(auto&z:opr) scanf("%d%d",&z[0],&z[1]);
	reverse(opr.begin(),opr.end());
	ok[0]=true;
	for(int i=1;i<=n;++i) ok[((1<<i)-1)<<(n-i)]=true;
	for(auto z:opr) {
		int u=z[0]-1,v=z[1]-1;
		if(u==v) continue;
		memset(tk,0,sizeof(tk));
		for(int s=0;s<(1<<n);++s) {
			int x=s>>u&1,y=s>>v&1;
			tk[s]=x>y?ok[s^(1<<u)^(1<<v)]:ok[s];
		}
		memcpy(ok,tk,sizeof(ok));
	}
	f[0]=1;
	ll ans=0;
	for(int c=1;c<=n&&c<=V;++c) {
		memcpy(g,f,sizeof(g));
		for(int i=0;i<n;++i) for(int s=0;s<(1<<n);++s) if(s>>i&1) add(f[s],f[s^(1<<i)]);
		for(int s=0;s<(1<<n);++s) {
			if(!ok[s]) f[s]=0;
			else sub(f[s],g[s]);
		}
		ans=(ans+C(V,c)*f[(1<<n)-1])%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



C. [P10861] Division

Problem Link

题目大意

给定 n,k,d 以及 a1an,定义一个区间 [L,R] 的权值为 ai 异或和恰好为 d 的子区间个数。

1n 分成 k 段,最小化每段区间权值和。

数据范围:n105,k20,ai106

思路分析

先考虑如何算一个区间的权值,可以考虑莫队,维护 [L1,R] 中每种前缀异或和出现的次数。

原问题显然考虑 dp,每次维护 kk+1 过程的转移,显然题目中的权值函数满足四边形不等式,因此转移有决策单调性。

用分治优化 dp 过程,计算区间权值用类似莫队的方法移动指针,可以分析出每次分治时指针的移动总量是 O(nlogn) 级别的。

时间复杂度 O(nklogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MAXV=1<<20;
int a[MAXN],X,cnt[MAXV];
ll f[MAXN],g[MAXN],w=0;
void ins(int z) { w+=cnt[z^X],++cnt[z]; }
void ers(int z) { --cnt[z],w-=cnt[z^X]; }
ll qry(int ql,int qr) {
	static int l=1,r=0;
	while(l>ql) ins(a[--l]);
	while(r<qr) ins(a[++r]);
	while(r>qr) ers(a[r--]);
	while(l<ql) ers(a[l++]);
	return w;
}
void DP(int l,int r,int L,int R) {
	if(l>r) return ;
	int x=(l+r)>>1,p=L;
	f[x]=g[p]+qry(p,x);
	for(int i=L+1;i<=R&&i<=x;++i) {
		ll v=g[i]+qry(i,x);
		if(v<f[x]) f[x]=v,p=i;
	}
	DP(l,x-1,L,p),DP(x+1,r,p,R);
}
signed main() {
	int n,k;
	scanf("%d%d%d",&n,&k,&X);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),a[i]^=a[i-1];
	memset(f,0x3f,sizeof(f)),f[0]=0;
	for(int o=1;o<=k;++o) {
		memcpy(g,f,sizeof(g));
		memset(f,0x3f,sizeof(f));
		DP(0,n,0,n);
	}
	printf("%lld\n",f[n]);
	return 0;
}



D. [P11025] Grid

Problem Link

题目大意

给定 (n+1)×(m+1) 的网格图,构造一棵生成树,使得任意切掉某一行或某一列的边后,剩余连通块数量最大值最小。

数据范围:n,m1000

思路分析

先考虑答案至少为 (n+1)(m+1)1n+m=nmn+m+1,不妨记 k=nmn+m

看到 +1 我们可以想到对最后一行一列的点全部连起来,只要考虑剩余的 n×m 个点。

我们观察发现:如果每个点都在他右边和下面的点中恰好选一个点相连,那么一定可以生成树,这是因为一个环一定要有一个拐点同时向右和向下连边。

那么我们只要让每一行向下的边数和每一列向右的边数尽可能平均即可。

可以对每一行都选 k 条边向下,为了让每一列剩余向右的边数量尽可能平均,我们在第 i 行取 [ikmodn,(i+1)kmodn) 范围内的边向下即可。

容易证明这个构造的答案就是 k+1

时间复杂度 O(nm)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005;
bool f[MAXN][MAXN];
signed main() {
	ios::sync_with_stdio(false);
	int n,m; cin>>n>>m;
	int k=(n*m-1)/(n+m)+1;
	for(int i=0,j=0;i<n;++i) for(int o=0;o<k;++o) f[i][j]=true,j=(j+1)%m;
	for(int i=0;i<n;++i) {
		for(int j=0;j<m;++j) cout<<"o"<<(f[i][j]?"  ":"--");
		cout<<"o\n";
		for(int j=0;j<m;++j) cout<<(f[i][j]?"|":" ")<<"  ";
		cout<<"|\n";
	}
	for(int i=0;i<m;++i) cout<<"o--";
	cout<<"o\n";
	return 0;
}



E. [P10920] Wildcard

Problem Link

题目大意

给定 n 位 01 串 S,其中有一些通配符可以任意换成 0/1,求最大的 k 使得存在 i 满足 S[i,i+k1]=S[i+k,i+2k1]

数据范围:n105

思路分析

这个问题显然很难做到 polylog 或者根号,因此考虑 std::bitset 优化。

从大到小枚举 k,某个位置 i 不合法当且仅当 Si=0,Si+k=1Si=1,Si+k=0std::bitset 维护为 0/1 的位置,用右移和求交并操作可以维护出所有不合法位置 i 的集合。

那么我们检验 k 是否合法只要在这个集合中找到 k 的连续的 0

对于 kω 的情况可以 O(n) 暴力判断每个 k 是否合法,其中 ωstd::bitset 字长。

对于 k>ω 的情况,这个 0 区间一定跨越了 >1 个长度为 ω 的块,我们可以遍历每个块并维护当前最长全 0 后缀。

如果一个块非空,那么只有其最长 0 前缀和后缀有可能有贡献,可以 __builtin_clz__builtin_ctz 维护,需要手写 bitset

时间复杂度 O(nω+n2ω)

代码呈现

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5,S=1575;
char s[MAXN];
int n;
ull f[S],g[S],t[S];
bool chk(int x) {
	for(int i=0,c=0;i+x<n;++i) {
		if(s[i]!='?'&&s[i+x]!='?'&&s[i]!=s[i+x]) c=0;
		else ++c;
		if(c>=x) return true;
	}
	return false;
}
void solve() {
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	scanf("%d%s",&n,s);
	for(int i=0;i<n;++i) {
		if(s[i]=='0') f[i>>6]|=1ull<<(i&63);
		if(s[i]=='1') g[i>>6]|=1ull<<(i&63);
	}
	for(int i=n/2;i>64;--i) {
		memset(t,0,sizeof(t));
		int ed=(n-i)>>6;
		for(int j=0,b=i>>6,r=i&63;j<=ed;++j) {
			ull F=f[j+b]>>r,G=g[j+b]>>r;
			if(r) F|=f[j+b+1]<<(64-r),G|=g[j+b+1]<<(64-r);
			t[j]=(F&g[j])|(G&f[j]);
		}
		for(int c=(n-i);c<((ed+1)<<6);++c) t[c>>6]|=1ull<<(c&63);
		int x=t[0]?__builtin_clzll(t[0]):64;
		for(int j=1;j<=ed;++j) {
			if(!t[j]) {
				if((x+=64)>=i) return printf("%d\n",i),void();
			} else {
				if(x+__builtin_ctzll(t[j])>=i) return printf("%d\n",i),void();
				x=__builtin_clzll(t[j]);
			}
		}
	}
	for(int i=min(n/2,64);i;--i) if(chk(i)) return printf("%d\n",i),void();
	puts("0");
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



*F. [P10656] Distinct

Problem Link

题目大意

给定一个长度为 n 的元素元素序列 a 和一个长度为 m 的元素序列 b,每个元素有颜色和权值,同一序列的元素颜色两两不同。

在两个序列中分别选出一个子区间(可以为空),要求选出所有元素颜色不同,最大化得到的元素和。

数据范围:n,m5×105

思路分析

首先我们可以全选序列 a/b,否则我们选出的两个子区间权值都应该大于其对应序列总权值的一半,否则换成 a/b 中较大的一个。

不妨假设在 a 序列中选出的子区间权值大于 a 的权值总和的一半,那么选出的所有区间一定过 a 的带权中点 ax

然后考虑在 b 序列上维护答案,如果 b 中有和 ax 同色的元素,那么在对应位置把 b 分成两半,只要考虑 ax 不与 b 元素同色的情况即可。

注意到一个 b 中的区间实际上会限制 a 中选取的区间不能经过一些点,又因为 a 中选取的区间一定要过 x,因此 b 中每个元素都等价于对 a 上选取区间的左端点或右端点的限制。

从而 b 中一个区间对 a 上选取区间的限制就是左端点限制的最大值和右端点限制的最小值。

那么就可以对 b 扫描线 r,单调栈配合线段树维护每个左端点 l 当前的 b[l,r] 限制出的 a 上选取区间的左右端点,容易统计区间和和最大值。

时间复杂度 O(nlogn+mlogm)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
const ll inf=1e18;
struct SegmentTree {
	array <ll,2> tr[MAXN<<2];
	ll tg[MAXN<<2];
	void adt(int p,ll k) { tr[p][0]+=k,tg[p]+=k; }
	void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
	void psu(int p) { tr[p]=max(tr[p<<1],tr[p<<1|1]); }
	void init(int l,int r,int p) {
		tg[p]=0;
		if(l==r) return tr[p]={-inf,l},void();
		int mid=(l+r)>>1;
		init(l,mid,p<<1),init(mid+1,r,p<<1|1);
		psu(p);
	}
	void add(int ul,int ur,ll k,int l,int r,int p) {
		if(ul<=l&&r<=ur) return adt(p,k);
		int mid=(l+r)>>1; psd(p);
		if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
		if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
		psu(p);
	}
}	T;
array <ll,5> ANS;
int dlt=0; bool swp=0;
void gans(ll w,int la,int ra,int lb,int rb) {
	lb+=dlt,rb+=dlt;
	if(swp) swap(la,lb),swap(ra,rb);
	if(w>ANS[0]) ANS={w,la,ra,lb,rb};
}
int pos[MAXN<<1],le[MAXN],ri[MAXN];
int sl[MAXN],tl,sr[MAXN],tr;
void solve(ll *a,ll *b,int *c,int *d,int n,int m) {
	int z=0;
	for(int i=1;i<=n;++i) if(a[i]*2>a[n]) { z=i; break; }
	for(int i=1;i<=m;++i) if(c[z]==d[i]) {
		if(i>1) solve(a,b,c,d,n,i-1);
		if(i<m) dlt=i,solve(a,b+i,c,d+i,n,m-i),dlt=0;
		return ;
	}
	memset(pos,0,sizeof(pos));
	for(int i=1;i<=n;++i) pos[c[i]]=i;
	T.init(1,m,1);
	tl=tr=0,sl[0]=0,sr[0]=0;
	for(int i=1;i<=m;++i) {
		T.add(i,i,inf,1,m,1);
		T.add(1,i,b[i]-b[i-1],1,m,1);
		le[i]=0,ri[i]=n;
		int x=pos[d[i]];
		if(x&&x<z) le[i]=x;
		if(x&&x>z) ri[i]=x-1;
		for(;tl&&le[sl[tl]]<=le[i];--tl) {
			T.add(sl[tl-1]+1,sl[tl],a[le[sl[tl]]],1,m,1);
		}
		for(;tr&&ri[sr[tr]]>=ri[i];--tr) {
			T.add(sr[tr-1]+1,sr[tr],-a[ri[sr[tr]]],1,m,1);
		}
		T.add(sl[tl]+1,i,-a[le[i]],1,m,1),sl[++tl]=i;
		T.add(sr[tr]+1,i,a[ri[i]],1,m,1),sr[++tr]=i;
		auto Z=T.tr[1];
		int L=*lower_bound(sl+1,sl+tl+1,Z[1]),R=*lower_bound(sr+1,sr+tr+1,Z[1]);
		gans(Z[0],le[L]+1,ri[R],Z[1],i);
	}
}
ll a[MAXN],b[MAXN];
int c[MAXN],d[MAXN];
signed main() {
	ios::sync_with_stdio(false);
	int n,m; cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>c[i];
	for(int i=1;i<=n;++i) cin>>a[i],a[i]+=a[i-1];
	for(int i=1;i<=m;++i) cin>>d[i];
	for(int i=1;i<=m;++i) cin>>b[i],b[i]+=b[i-1];
	gans(a[n],1,n,0,0),gans(b[m],0,0,1,m);
	solve(a,b,c,d,n,m),swp=1,solve(b,a,d,c,m,n);
	cout<<ANS[0]<<"\n"<<ANS[1]<<" "<<ANS[2]<<"\n"<<ANS[3]<<" "<<ANS[4]<<"\n";
	return 0;
}



*G. [P10717] Random

Problem Link

题目大意

给定 n 个点的树,以及 k 个集合,第 i 个集合包含第 j 个点包含的概率是 pi,j

定义 Ti 表示第 i 个集合对应的斯坦纳树,第 u 个点的权值为 fu,S,其中 S={iuTi}

求所有点权值乘积的期望。

数据范围:n100,k8

思路分析

考虑 k=1 如何做,对于该集合,考虑 u 与其的关系:

  • 状态 0u 子树中没有集合中的点。
  • 状态 1u 子树中有集合中的点,且钦定 u 子树外还有集合中的点(即 uT1)。
  • 状态 2u 子树中有集合中的点,且钦定 u 子树外没有集合中的点(即 uT1)。
  • 状态 3:合并子树状态时的中间状态,表示仅考虑 u 子树内节点,已经有 uT1,即 u 被选或 u 至少有两个儿子是状态 1,没有儿子是状态 2

根据如上的定理,我们可以列出所有转移 xyz 表示 u 当前状态为 x,加上某个状态为 y 的子树后状态为 z

那么所有转移共 8 种:000,011,022,033,101,113,202,303

求出当前节点状态后,考虑 u 是否直接被该集合包含,如果是的话,那么 0,1,3 状态都转移成 3,否则不变。

然后如果状态为 1/3,那么说明 uT1 否则 uT1,乘上对应情况的权值。

最后我们可以考虑 T1 是否在 u 处结束,即状态 3 转移到 1/2

那么原问题可以直接 dp,设 fu,{0,1,2}k 表示 k 个集合对 u 的状态,如果我们暴力枚举每一位的转移,一共能得到 8k 种,直接暴力处理,复杂度 O(n8k) 无法通过。

但我们注意到 {0,1,3} 中的所有状态加上状态 0/1 依然在 {0,1,3} 中。

因此如果我们定义状态 3 为实际的 {0,1,3} 三种状态之和,那么转移就只剩下了 000,011,022,101,202,3336 种。

预处理出所有转移即可 O(6k) 单次合并。

我们只要在每次将 v 子树合并时提前把状态为 0/1 的位转移到状态 3 上即可,可以类似 FWT 逐位处理。

而还原求出原数组就用状态为 3 的位减去状态为 0/1 的方案数,做一个逆 FWT 的过程即可。

后面的几步转移都可以逐位处理做到 O(k4k)

最终答案就是 frt,{0,2}k

时间复杂度 O(n(6k+k4k))

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=105,MOD=998244353;
vector <int> G[MAXN];
int n,k,val[MAXN][1<<8],pr[MAXN][8],F[MAXN][1<<16],g[1<<16],e[1<<16];
inline void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline void sub(int &x,int y) { x=(x>=y)?x-y:x+MOD-y; }
void fwt(int *z,bool ifwt) {
	for(int i=0;i<k;++i) for(int s=0;s<(1<<k*2);++s) if((s>>i*2&3)<2) {
		(ifwt?sub:add)(z[s|(3<<2*i)],z[s]);
	}
}
vector <array<int,3>> Q; //(0,0,0) (0,1,1) (1,0,1) (0,2,2) (2,0,2) (3,3,3)
void dfs(int u,int fz) {
	int *f=F[u]; f[0]=1,fwt(f,0);
	for(int v:G[u]) if(v^fz) {
		dfs(v,u),fwt(F[v],0);
		memset(g,0,sizeof(g));
		for(auto z:Q) g[z[2]]=(g[z[2]]+1ll*f[z[0]]*F[v][z[1]])%MOD;
		memcpy(f,g,sizeof(g));
	}
	fwt(f,1);
	for(int i=0;i<k;++i) {
		memset(g,0,sizeof(g));
		for(int s=0;s<(1<<k*2);++s) {
			g[s]=(g[s]+1ll*f[s]*(1+MOD-pr[u][i]))%MOD;
			if((s>>i*2&3)^2) g[s|(3<<i*2)]=(g[s|(3<<i*2)]+1ll*f[s]*pr[u][i])%MOD;
		}
		memcpy(f,g,sizeof(g));
	}
	for(int s=0;s<(1<<k*2);++s) f[s]=1ll*f[s]*val[u][e[s]]%MOD;
	for(int i=0;i<k;++i) for(int s=0;s<(1<<k*2);++s) if((s>>i*2&3)>2) {
		add(f[s^(1<<i*2)],f[s]),add(f[s^(2<<i*2)],f[s]),f[s]=0;
	}
}
signed main() {
	scanf("%d%d",&n,&k);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	for(int j=0;j<k;++j) for(int i=1;i<=n;++i) scanf("%d",&pr[i][j]);
	for(int i=1;i<=n;++i) for(int s=0;s<(1<<k);++s) scanf("%d",&val[i][s]);
	vector <array<int,3>> I{{0,0,0},{0,1,1},{1,0,1},{0,2,2},{2,0,2},{3,3,3}};
	Q.push_back({0,0,0});
	for(int i=0;i<k;++i) {
		vector <array<int,3>> P;
		for(auto s:Q) for(auto z:I) {
			P.push_back({s[0]<<2|z[0],s[1]<<2|z[1],s[2]<<2|z[2]});
		}
		Q.swap(P);
	}
	for(int s=0;s<(1<<k*2);++s) for(int i=0;i<k;++i) e[s]|=(s>>i*2&1)<<i;
	dfs(1,0);
	int ans=0;
	for(int s=0;s<(1<<k*2);++s) if(!e[s]) add(ans,F[1][s]);
	printf("%d\n",ans);
	return 0;
}



*H. [P10546] Interactive

Problem Link

题目大意

交互器有 n 个点 n1 条边的有向图,将图视为无向图后是一棵树。

你可以进行如下操作,每次可以翻转若干条边,你会知道这种情况下每个点 u 能被多少个点到达,记为 fu

请在 50 次操作内还原整张图,即确定每条边的起点和终点。

数据范围:n104

思路分析

记询问次数 Q=50

对于树上问题,考虑从叶子出发逐步剥离每个点。

我们要先求出叶子,注意到如果某个叶子 u 到父亲的边是从 u 出发的,那么此时该点 fu=1

假如每条边都随机,那么有 12 的概率这个叶子的 fu=1

考虑一个经典的技巧,我们对每条边,随机选择 Q2 次询问翻转,满足任意两条边的翻转情况都不相同或无交。

那么对于一个叶子结点 u,恰有 Q2 次询问中这个点 fu=1

对于一个非叶子节点,他的两条出边至少有一个时刻不同向,那么这个点满足 fu=1 的的询问轮数一定严格 <Q2

那么这样就能每次求出一个叶子结点 u,然后考虑确定 u 到其父亲 v 的边,以及其父亲的编号。

确定 uv 的边是简单的,只需要求一条边的翻转状态和这个点 fu=1 的状态相等即可。

然后考虑找父亲,注意到 fu>1 时一定有 fu=fv+1,那么如果一个点在 fu>1 时总满足 fu=fv+1,那么 v 很大概率是 u 的父亲。

考虑什么时候会将一个错误的 w 判断为 u 的父亲,注意到如果某种情况下 fv=fw,那么当 v/w 中的一条出边被翻转后,一定有 fvfw

那么我们知道所有情况中 fv=fw 的情况出现一定能对应到至少一种 fvfw 的情况,因此总概率一定 12

因此判断错误的概率为 2Q/2,完全可以接受,并且对于每个错误的点,期望查询 O(1) 轮的结果就能发现错误。

时间复杂度 O(n2)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int Q=50,MAXN=10005;
const ll A=(1ll<<Q)-1;
mt19937 rnd(time(0));
ll gen() {
	vector <int> p;
	for(int i=0;i<Q;++i) p.push_back(i);
	shuffle(p.begin(),p.end(),rnd);
	ll z=0;
	for(int i=0;i<Q/2;++i) z|=1ll<<p[i];
	return z;
}
int n,f[Q+5][MAXN],w[Q+5][MAXN],c[MAXN],U[MAXN],V[MAXN];
ll S[MAXN];
bool del[MAXN];
unordered_map <ll,int> E;
signed main() {
	cin>>n;
	for(int i=1;i<n;++i) {
		ll x=gen();
		while(E.count(x)) x=gen();
		S[i]=x,E[x]=i,E[A^x]=-i;
	}
	for(int i=0;i<Q;++i) {
		cout<<"? ";
		for(int u=1;u<n;++u) cout<<(S[u]>>i&1);
		cout<<endl;
		for(int u=1;u<=n;++u) cin>>f[i][u],w[i][u]=1,c[u]+=(f[i][u]==1);
	}
	for(int o=1;o<n;++o) {
		int x=0,y=0;
		for(int u=1;u<=n;++u) if(!del[u]&&c[u]==Q/2) { x=u; break; }
		ll I=0; del[x]=true;
		for(int i=0;i<Q;++i) if(f[i][x]>w[i][x]) I|=1ll<<i;
		for(int u=1;u<=n;++u) if(!del[u]) {
			bool flg=1;
			for(int i=0;i<Q;++i) if((I>>i&1)&&f[i][x]-w[i][x]!=f[i][u]) {
				flg=0; break;
			}
			if(!flg) continue;
			y=u; break;
		}
		for(int i=0;i<Q;++i) if(!(I>>i&1)) w[i][y]+=w[i][x],c[y]+=(w[i][y]==f[i][y]);
		int z=E[I];
		if(z>0) U[z]=x,V[z]=y;
		else U[-z]=y,V[-z]=x;
	}
	cout<<"! ";
	for(int i=1;i<n;++i) cout<<U[i]<<" "<<V[i]<<" ";
	cout<<endl;
	return 0;
}
posted @   DaiRuiChen007  阅读(151)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示