2026 NOI 做题记录(十四)



Contest Link

\(\text{By DaiRuiChen007}\)



A. [P11648] 2236 A.D. (4.5)

Problem Link

\(k\) 个位折半,我们要求动态维护集合 \(S\),每次单点修改或查询 \(w_x=\sum_{y\in S}a_{x\lor y}\)

用 DSU 维护,每个点只有 \(k\) 个祖先会改变路径权值,加上 \(\log n\) 个轻边祖先,\(S\) 的修改和询问次数是 \(\mathcal O(n\log n)\) 的。

那么直接折半在插入删除时分别枚举 \(x\lor y\) 的前后 \(\frac k2\) 位即可。

时间复杂度 \(\mathcal O(n2^{k/2}(k+\log n))\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,MOD=998244353;
int n,m,k,L,R,a[MAXN];
ll h[16],wl[128],wr[256],z[MAXN],f[256][128];
vector <int> G[MAXN];
int hson[MAXN],siz[MAXN],fa[MAXN],b[MAXN],dfn[MAXN],efn[MAXN],rk[MAXN],dcnt;
void dfs1(int u,int fz) {
	if(fz) G[u].erase(find(G[u].begin(),G[u].end(),fz));
	fa[u]=fz,siz[u]=1,dfn[u]=++dcnt,rk[dcnt]=u;
	for(int v:G[u]) {
		dfs1(v,u),siz[u]+=siz[v];
		if(siz[v]>siz[hson[u]]) hson[u]=v;
	}
	efn[u]=dcnt;
}
int c[MAXN],st[MAXN],tp;
void dfs2(int u) {
	z[u]=h[a[u]];
	for(int v:G[u]) if(v^hson[u]) {
		dfs2(v),z[u]=(z[u]+z[v])%MOD,b[v]=1<<a[v];
		for(int i=dfn[v];i<=efn[v];++i) {
			b[rk[i]]=(i==dfn[v]?0:b[fa[rk[i]]])|1<<a[rk[i]];
			memset(f[b[rk[i]]>>k],0,sizeof(f[0]));
		}
	}
	auto add=[&](int s,int o) {
		int r=s>>k,l=s&L;
		for(int x=0;x<=L;++x) f[r][x]+=o*wl[x|l];
	};
	auto qry=[&](int s) {
		int r=s>>k,l=s&L; ll o=0;
		for(int x=0;x<R;++x) o=(o+(f[x][l]%MOD+MOD)*wr[x|r])%MOD;
		return o;
	};
	if(hson[u]) {
		dfs2(hson[u]),z[u]=(z[u]+z[hson[u]])%MOD;
		function<void(int,int)> dfs3=[&](int x,int s) {
			if(s>>a[u]&1) return ;
			if(!c[s]++) st[++tp]=s;
			for(int y:G[x]) dfs3(y,s|1<<a[y]);
		};
		dfs3(hson[u],1<<a[hson[u]]);
		for(;tp;c[st[tp--]]=0) add(st[tp],-c[st[tp]]),add(st[tp]|1<<a[u],c[st[tp]]);
	}
	z[u]=(z[u]+qry(1<<a[u]))%MOD,add(1<<a[u],1);
	for(int v:G[u]) if(v^hson[u]) {
		for(int i=dfn[v];i<=efn[v];++i) {
			b[rk[i]]|=1<<a[u];
			if(!c[b[rk[i]]]++) st[++tp]=b[rk[i]];
		}
		for(int i=1;i<=tp;++i) z[u]=(z[u]+qry(st[i])*c[st[i]])%MOD;
		for(;tp;c[st[tp--]]=0) add(st[tp],c[st[tp]]);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,k=m/2,L=(1<<k)-1,R=1<<(m-k);
	for(int i=0;i<m;++i) cin>>h[i];
	for(int s=0;s<=L;++s) {
		wl[s]=1;
		for(int i=0;i<k;++i) if(s>>i&1) wl[s]=wl[s]*h[i]%MOD;
	}
	for(int s=0;s<R;++s) {
		wr[s]=1;
		for(int i=0;i<m-k;++i) if(s>>i&1) wr[s]=wr[s]*h[i+k]%MOD;
	}
	for(int i=1;i<=n;++i) cin>>a[i],--a[i];
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs1(1,0),dfs2(1);
	for(int i=1;i<=n;++i) cout<<z[i]<<" \n"[i==n];
	return 0;
}



*B. [P14652] 这里是,终末停滞委员会 (8)

Problem Link

首先这是一个分配问题,容易想到最小割建模,但正常的最小割只有 \(u\in S,u\in T\) 两种情况。

那么需要拆点,按 \(x_u,y_u\in S\) 的状态来区分三种状态,设 \(x_u\in S,y_u\in T\)A\(x_u\in T,y_u\in S\)B\(x_u,y_u\in S\)C

那么对于二类边,双向连接 \((x_u,y_v),(x_v,y_u)\),权值为 \(1\),分讨每种情况可以证明一定合法。

对于一类边,提前加上 \(2\) 的答案,双向连接 \((x_u,y_u),(x_v,y_v),(x_u,y_v),(x_v,y_u)\),权值为 \(1\)

可以证明 \(x_u,y_u\in T\) 的情况不如把所有连通的这样的点都调整成 \(x_u,y_u\in S\)

那么我们只要求字典序前 \(k\) 小的最小割。

根据最小割的经典结论,我们对残量网络缩点,只要 \(S\) 是缩点后的一个闭合子图即可。

那么每次我们把 \(u\) 放入 \(S\) 就把残量网络上后继放入 \(S\),否则把前驱放入 \(T\)

快速判断每个 \(u\) 能否被放入需要预处理出每对 \(x_u,y_u\) 在残量网络上是否可达,分块 bitset 维护。

时间复杂度 \(\mathcal O(m\sqrt m+\frac{nm}\omega+(n+m)k)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,MAXE=1e7+5,inf=1e9,B=4096;
struct Edge { int v,f,e; } G[MAXE];
int S,T,ec=1,hd[MAXN],cur[MAXN],d[MAXN];
void link(int u,int v,int w) {
	G[++ec]={v,w,hd[u]},hd[u]=ec;
	G[++ec]={u,0,hd[v]},hd[v]=ec;
}
bool bfs() {
	memset(d,-1,sizeof(d)),memcpy(cur,hd,sizeof(hd));
	queue <int> Q; d[S]=0,Q.push(S);
	while(Q.size()) {
		int u=Q.front(); Q.pop();
		for(int i=hd[u];i;i=G[i].e) if(G[i].f&&d[G[i].v]<0) d[G[i].v]=d[u]+1,Q.push(G[i].v);
	}
	return ~d[T];
}
int dfs(int u,int f) {
	if(u==T) return f;
	int r=f;
	for(int &i=cur[u];i;i=G[i].e) if(d[G[i].v]==d[u]+1&&G[i].f) {
		int g=dfs(G[i].v,min(G[i].f,r));
		if(!g) d[G[i].v]=-1;
		r-=g,G[i].f-=g,G[i^1].f+=g;
		if(!r) return f;
	}
	return f-r;
}
int n,m1,m2,q,dfn[MAXN],low[MAXN],dcnt,st[MAXN],tp,bl[MAXN],scnt,vis[MAXN];
bool ins[MAXN],ok[MAXN];
vector <int> E[MAXN];
void tarjan(int u) {
	dfn[u]=low[u]=++dcnt,ins[st[++tp]=u]=1;
	for(int i=hd[u];i;i=G[i].e) if(G[i].f) {
		int v=G[i].v;
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]) for(++scnt;ins[u];ins[st[tp--]]=0) bl[st[tp]]=scnt;
}
bitset <B> f[MAXN];
void upd(int u,int c) {
	if(~vis[u]) return ;vis[u]=c,st[++tp]=u;
	for(int i=hd[u];i;i=G[i].e) if(bl[u]==bl[G[i].v]||G[i^c].f) upd(G[i].v,c);
}
string w;
void solve(int i) {
	if(!q) exit(0);
	if(i>n) return cout<<w<<"\n",--q,void();
	int h=tp;
	auto rb=[&]() { for(;tp>h;vis[st[tp--]]=-1); };
	if(!ok[i]&&vis[i]!=0&&vis[i+n]!=1) w[i-1]='A',upd(i,1),upd(i+n,0),solve(i+1),rb();
	if(!ok[i+n]&&vis[i]!=1&&vis[i+n]!=0) w[i-1]='B',upd(i,0),upd(i+n,1),solve(i+1),rb();
	if(vis[i]!=1&&vis[i+n]!=1) w[i-1]='C',upd(i,0),upd(i+n,0),solve(i+1),rb();
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m1>>m2>>q,S=2*n+1,T=S+1;
	for(int i=1;i<=n;++i) {
		char o; cin>>o;
		if(o=='A') link(i,T,inf),link(S,i+n,inf);
		if(o=='B') link(S,i,inf),link(i+n,T,inf);
		if(o=='C') link(S,i,inf),link(S,i+n,inf);
	}
	for(int i=1,u,v;i<=m1+m2;++i) {
		cin>>u>>v,link(u+n,v,1),link(u,v+n,1),link(v+n,u,1),link(v,u+n,1);
		if(i<=m1) link(u,v,1),link(u+n,v+n,1),link(v,u,1),link(v+n,u+n,1);
	}
	int z=2*m1;
	while(bfs()) z-=dfs(S,inf);
	cout<<z<<"\n";
	for(int i=1;i<=T;++i) if(!dfn[i]) tarjan(i);
	for(int u=1;u<=T;++u) for(int i=hd[u];i;i=G[i].e) if(G[i].f&&bl[u]!=bl[G[i].v]) E[bl[u]].push_back(bl[G[i].v]);
	for(int l=1,r;l<=2*n;l=r+1) {
		r=min(2*n,l+B-1);
		for(int i=1;i<=scnt;++i) f[i].reset();
		for(int i=l;i<=r;++i) f[bl[i]][i-l]=1;
		for(int u=1;u<=scnt;++u) for(int v:E[u]) f[u]|=f[v];
		for(int i=l;i<=r;++i) ok[i]=f[bl[i>n?i-n:i+n]][i-l];
	}
	memset(vis,-1,sizeof(vis)),upd(S,0),upd(T,1);
	w=string(n,'A'),solve(1);
	return 0;
}



*C. [P13758] 夏终 (7)

Problem Link

从链的情况开始,刻画最终的 MST,肯定是选择原图上若干条边构成一些区间,然后把每个区间的最小值向 \(\min b_i\) 连接。

那么每个区间的权值可以看成当前的 \(x=b_0+\min b_i\) 加上区间最小的 \(b\),经典做法就是维护答案关于 \(x\) 的凸壳,合并左右区间的时候矩乘闵和。

我们现在要支持单点修改 \(b\) 动态维护整体凸壳。

考虑分块,对每个块建立线段树,然后维护查询的 \(x\) 对应矩阵每个位置上的取值,然后把每个块上的数值权值乘起来。

设块长为 \(B\) 修改复杂度 \(\mathcal O(B)\),查询复杂度 \(\mathcal O(\frac nB\log B)\),瓶颈在二分凸壳,可以离线对询问基数排序后双指针做到线性。

对于一般的 MST,注意到我们只要保证生成过程中任意时刻点集连通性不变即可,因此可以按重构树 dfn 序把树变为等价的序列问题。

时间复杂度 \(\mathcal O(q\sqrt {n\log n})\),精细实现可以做到 \(\mathcal O(q\sqrt n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef basic_string<ll> vec;
const int MAXN=2e5+5,B=512;
const ll inf=1e15;
void mul(const vec&u,const vec&v,ll *w) {
	int i=0,j=0,l=u.size()-1,r=v.size()-1;
	for(w[0]=u[0]+v[0];i<l||j<r;) {
		if(j==r||(i<l&&u[i+1]-u[i]<v[j+1]-v[j])) w[i+j+1]=w[i+j]+u[i+1]-u[i],++i;
		else w[i+j+1]=w[i+j]+v[j+1]-v[j],++j;
	}
}
void sol(const vec&xl,const vec&xr,const vec&yl,const vec&yr,vec&z) {
	static ll x[B+5],y[B+5];
	mul(xl,xr,x),mul(yl,yr,y),z.resize(xl.size()+xr.size()-1);
	for(int i=0;i<(int)z.size();++i) z[i]=min(x[i],y[i]);
}
ll qry(const vec&x,ll z) {
	int p=0,m=x.size()-1;
	if(m) for(int k=1<<__lg(m);k;k>>=1) if(p+k<=m&&x[p+k-1]-x[p+k]>z) p+=k;
	return x[p]+p*z;
}
vec f[B<<1][2][2];
int _,n,m,q,dsu[MAXN],ed[MAXN],nx[MAXN],id[MAXN],ps[MAXN];
ll a0[MAXN],a[MAXN],b[MAXN],dp[MAXN][2],mn[MAXN],vl[MAXN],tw[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
void ini(int p,int x) {
	f[p][0][0]={b[x],a[x]};
	f[p][0][1]={a[x]+b[x],inf};
	f[p][1][0]={inf,0};
	f[p][1][1]={b[x],inf};
}
void psu(int p) { for(int l:{0,1}) for(int r:{0,1}) sol(f[p<<1][l][0],f[p<<1|1][0][r],f[p<<1][l][1],f[p<<1|1][1][r],f[p][l][r]); }
void init(int l,int r,int p) {
	if(l==r) return ini(p,l);
	int mid=(l+r)>>1;
	init(l,mid,p<<1),init(mid+1,r,p<<1|1);
	psu(p);
}
void upd(int x,int l,int r,int p) {
	if(l==r) return ini(p,l);
	int mid=(l+r)>>1;
	x<=mid?upd(x,l,mid,p<<1):upd(x,mid+1,r,p<<1|1);
	psu(p);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>_>>n>>m>>q>>a[0],a0[0]=a[0];
	multiset <ll> A;
	for(int i=1;i<=n;++i) cin>>a0[i],A.insert(a0[i]);
	vector <array<ll,3>> E(m);
	for(auto&e:E) cin>>e[1]>>e[2]>>e[0];
	for(int i=1;i<=n;++i) dsu[i]=ed[i]=i,E.push_back({inf,1,i});
	sort(E.begin(),E.end());
	for(auto e:E) {
		int x=find(e[1]),y=find(e[2]);
		if(x!=y) nx[ed[x]]=y,tw[ed[x]]=e[0],dsu[y]=x,ed[x]=ed[y];
	}
	for(int u=find(1),i=1;i<=n;++i,u=nx[u]) id[u]=i,b[i]=tw[u];
	for(int i=1;i<=n;++i) a[id[i]]=a0[i];
	memcpy(a0,a,sizeof(a));
	for(int i=1;i<=q;++i) {
		cin>>ps[i]>>vl[i],ps[i]=id[ps[i]];
		if(ps[i]) A.erase(A.find(a[ps[i]]));
		a[ps[i]]=vl[i];
		if(ps[i]) A.insert(a[ps[i]]);
		mn[i]=*A.begin()+a[0],dp[i][0]=-*A.begin(),dp[i][1]=inf;
	}
	for(int l=1,r;l<=n;l=r+1) {
		memcpy(a,a0,sizeof(a));
		r=min(n,l+B-1),init(l,r,1);
		for(int i=1;i<=q;++i) {
			if(l<=ps[i]&&ps[i]<=r) a[ps[i]]=vl[i],upd(ps[i],l,r,1);
			ll z0=min(dp[i][0]+qry(f[1][0][0],mn[i]),dp[i][1]+qry(f[1][1][0],mn[i]));
			ll z1=min(dp[i][0]+qry(f[1][0][1],mn[i]),dp[i][1]+qry(f[1][1][1],mn[i]));
			dp[i][0]=z0,dp[i][1]=z1;
		}
	}
	for(int i=1;i<=q;++i) cout<<dp[i][1]<<"\n";
	return 0;
}



*D. [P11066] 电梯 (9)

Problem Link

我们的思路大致是在同一个时间按某种顺序把所有电梯都激活,这样才能较自由地安排每个电梯。

一种显然的方法就是从右往左激活每个电梯,且目标点都在当前电梯右侧。

那么这种操作能直接处理所有 \(p_i>i\) 的点,如果 \(p_i\le i\) 则移动到 \(i+1\) 即可。

此时过了一秒后我们会让位置 \(1\) 空出,且 \(p_i\le i\) 的点都已经停止,类似地从左往右把 \(p_i\le i\) 移动到 \(p_i\) 上即可。

问题是 \(p_i=i+1\) 的点在一秒后会停下并阻止对 \(p_i\le i\) 的点的还原过程。

我们要让这样的点不存在,一个观察是 \(p_i=i+1\) 的点随意与任何一个 \(p_j\) 交换后 \(p_i,p_j\) 一定会合法。

那么我们把序列两两匹配,如果一对匹配中有这样的点就交换它们,用刚才的构造实现该过程。

我们的要求是目标操作不能交换相邻元素,且操作次数和移动距离有关,所以隔 \(2\) 匹配是最优的。

由于 \(m\) 一定合法,那么可以通过删掉该元素把 \(m\) 调整为偶数,再按 \(m\bmod 4\) 分类,四个元素间隔匹配即可即可,\(m=2\) 的情况特判,\(m\bmod 4=2\) 的剩余情况把最后六个元素间隔匹配。

精细讨论可以证明操作次数不超过 \(5n\)

时间复杂度 \(\mathcal O(n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef basic_string<int> vec;
const int MAXN=5e4+5;
int n,m,a[MAXN],b[MAXN];
vec z;
void sol(int *p) {
	int l=0,r=0;
	for(int i=m;i;--i) {
		if(p[i]>i) z+=p[i];
		else z+=i+1;
		r=max(r,p[i]-i),l=max(l,i-p[i]);
	}
	z+=0;
	for(int i=1;i<=m;++i) if(p[i]<=i) z+=p[i];
	z+=vec(max(r-1,l+1),0);
}
void solve() {
	z.clear();
	for(int i=1;i<=m;++i) cin>>a[i],b[i]=i;
	if(m==2) return cout<<(a[1]==1?"0\n\n":"7\n3 3 0 1 0 2 0\n"),void();
	if(m==3) {
		if(a[1]==2||a[2]==3) z={4,3,3,0,1,0,2,3,0},swap(a[1],a[2]);
	} else {
		for(int i=1,k=m-(m&1);i<=k;i+=4) {
			if(i+5==k) {
				for(int j:{0,1,2}) if(a[i+j]==i+j+1||a[i+j+3]==i+j+4) swap(a[i+j],a[i+j+3]),swap(b[i+j],b[i+j+3]);
				break;
			}
			for(int j:{0,1}) if(a[i+j]==i+j+1||a[i+j+2]==i+j+3) swap(a[i+j],a[i+j+2]),swap(b[i+j],b[i+j+2]);
		}
		sol(b);
	}
	sol(a),cout<<z.size()<<"\n";
	for(int x:z) cout<<x<<" "; cout<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	for(int q,o;_--;) {
		cin>>q>>n>>m>>o;
		while(q--) solve();
	}
	return 0;
}



E. [P12152] 催眠术 (4.5)

Problem Link

首先序列是好的当且仅当可以分成 \(k\) 段,每段值域 \([1,m]\)

那么 dp 的时候维护 \(pre_i\),以及当前分出了几个 \([1,m]\) 的段,以及当前段的情况。

\(f_{i,j,c,p,q}\) 表示 \(pre_i=j\),当前已分出 \(c\) 个段,当前段已出现的元素有 \(p\) 个和段中匹配的某个 \(a_x\) 相同,\(q\) 个没有。

转移的时候考虑加入一个 \(a\) 中元素,则要从 \(p\)\(q\) 或外界转移,是否从 \(p\) 中转移可以通过 \((j,p)\) 判断,否则我们向 \(q\) 中加入元素的时候不确定具体颜色,直到此时被取出时再确定。

然后用 \(g_{i,j,c,p,q}\) 表示当前过程中填入若干个 \(\not\in a\) 的元素的过程,注意这里的 \(p\) 已经包含了 \(a_{j+1}\) 的颜色,但我们要时刻注意填出的元素总数并不能超过 \(m\),这和被钦定的 \(a_{j+1}\) 是否为新元素有关,所以新开一维记录。

还要一些其他的状态处理每个段的末尾,以及 \(pre_i=m\) 的部分。

注意到 \(jp,cq\le mk\le n\),所以状态数 \(\mathcal O(n^3)\)

时间复杂度 \(\mathcal O(n^3)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
int n,m,k,a[405],w[405][405],fac[405],rk[405][405],ans=0,sf[405];
int f[2][805][805],g[2][805][805][2],h[2][805][405],e[2][805]; //g: a[j+1] new?
int id(int x,int y) { return x*(k+1)+y; }
inline void add(int &x,const ll &y) { x=(x+y)%MOD; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>k;
	if(m*k>n) return cout<<"0\n",0;
	for(int i=1;i<=m;++i) cin>>a[i];
	for(int i=fac[0]=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%MOD;
	for(int i=1;i<=n;++i) for(int j=0;j<=m;++j) cin>>w[i][j];
	for(int i=1;i<=m;++i) for(int j=i,c=0;j>=1;--j) if(!rk[i][a[j]]) rk[i][a[j]]=++c;
	sf[n+1]=1;
	for(int i=n;i;--i) sf[i]=1ll*sf[i+1]*w[i][m]%MOD*k%MOD;
	f[0][id(0,0)][id(0,0)]=1;
	for(int i=1,o=0,z;i<=n;++i,o^=1) {
		for(int j=0;j<=m&&j<=i;++j) for(int c=0;c<=i/k&&c<=j;++c) for(int p=0;p<=k;++p) {
			for(int q=0;p+q<=k;++q) {
				z=0,swap(z,f[o][id(j,p)][id(c,q)]);
				if(z) { //a[j+1] type
					if(rk[j][a[j+1]]&&rk[j][a[j+1]]<=p) { //in P
						add(g[o][id(j,p)][id(c,q)][1],z);
						add(h[o][id(j,p+q)][c],1ll*z*fac[k-p]);
					} else {
						if(q) add(g[o][id(j,p+1)][id(c,q-1)][1],1ll*z*q); //in Q
						add(g[o][id(j,p+1)][id(c,q)][0],z); //new
						add(h[o][id(j,p+q)][c],1ll*z*fac[k-p-1]%MOD*q); //must in Q
					}
				}
				for(int x:{0,1}) {
					z=0,swap(z,g[o][id(j,p)][id(c,q)][x]);
					if(!z) continue;
					if(p+q+x<k) add(g[o^1][id(j,p)][id(c,q+1)][x],1ll*z*w[i][j]);
					add(g[o^1][id(j,p)][id(c,q)][x],1ll*z*w[i][j]%MOD*(p+q-1));
					if(j<m-1) {
						if(p+q<k) add(f[o^1][id(j+1,p)][id(c,q)],1ll*z*w[i][j+1]);
						else add(f[o^1][id(j+1,0)][id(c+1,0)],1ll*z*w[i][j+1]%MOD*fac[q]);
					} else {
						if(p+q<k) add(e[o^1][id(c,p+q)],1ll*z*w[i][m]%MOD*fac[k-p]);
						else add(e[o^1][id(c+1,0)],1ll*z*w[i][m]%MOD*fac[k-p]%MOD*fac[c<m-1?k:0]);
					}
				}
			}
			z=0,swap(z,h[o][id(j,p)][c]);
			if(z) {
				add(h[o^1][id(j,p)][c],1ll*z*w[i][j]%MOD*(p-1));
				if(p<k-1) add(h[o^1][id(j,p+1)][c],1ll*z*w[i][j]);
				else add(f[o^1][id(j,0)][id(c+1,0)],1ll*z*w[i][j]);
			}
		}
		for(int c=0;c<=i/k&&c<=m;++c) for(int p=0;p<=k;++p) {
			z=0,swap(z,e[o][id(c,p)]);
			if(!z) continue;
			if(c==m) add(ans,1ll*z*sf[i]);
			else {
				add(e[o^1][id(c,p)],1ll*z*w[i][m]%MOD*p);
				if(p<k-1) add(e[o^1][id(c,p+1)],1ll*z*w[i][m]);
				else add(e[o^1][id(c+1,0)],1ll*z*w[i][m]%MOD*fac[c<m-1?k:0]);
			}
		}
	}
	add(ans,e[n&1][id(m,0)]);
	cout<<ans<<"\n";
	return 0;
}



F. [P14256] 平局 (3)

Problem Link

判定序列时考虑差分数组,操作只有 \([0]\to [],[+1,-1]\to [],[+1,+1]\to [-1],[-1,-1]\to [+1]\) 四种,其中前两种增加答案。

变成类似括号匹配的问题,简单维护,首先前缀中如果剩 \(+1\),则可以和 \(-1\) 匹配,否则可以把 \(-1\) 保留等待下一个 \(-1\) 生成 \(+1\)

那么栈的结构一定是栈底 \(\le 1\)\(-1\),然后若干个 \(+1\),最终栈里剩下每三个 \(+1\) 可以增加一次答案。

dp 记录栈结构以及上一个元素即可。

时间复杂度 \(\mathcal O(n^2)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3005,MOD=1e9+7;
struct info { int x,y; } f[2][MAXN][2][3];
int n,a[MAXN],ans=0;
inline void add(int&x,const int&y) { x=x+y>=MOD?x+y-MOD:x+y; }
inline void add(info&u,const info&v,int o) { add(u.x,v.x),add(u.y,v.y); if(o) add(u.y,v.x); }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) { char o; cin>>o,a[i]=o-'0'; }
	for(int c:{0,1,2}) if(a[1]>>c&1) f[1][0][0][c]={1,0};
	for(int i=2,o=1;i<=n+1;++i,o^=1) {
		for(int j=0;j<i;++j) for(int c:{0,1}) for(int x:{0,1,2}) {
			info &z=f[o][j][c][x];
			if(!z.x&&!z.y) continue;
			if(i>n) { ans=(ans+z.y+1ll*(j/3)*z.x)%MOD; continue; }
			for(int y:{0,1,2}) if(a[i]>>y&1) {
				if(x==y) add(f[o^1][j][c][y],z,1);
				else if(y==(x+2)%3) {
					add(f[o^1][j+1][c][y],z,0);
				} else {
					if(j) add(f[o^1][j-1][c][y],z,1);
					else if(c) add(f[o^1][1][0][y],z,0);
					else add(f[o^1][0][1][y],z,0);
				}
			}
			z.x=z.y=0;
		}
	}
	cout<<ans<<"\n";
	return 0;
}



*G. [P13560] 交换换 (8)

Problem Link

首先我们的要求就是 \(\forall u\in S\),对 \((i,i+u)\) 连边后,所有 \(i,p_i\) 连通。

考虑 \(k=1\) 的情况,我们二分 \(\min S\),并令 \(S=[x,n-1]\),此时图会把 \([1,n-x]\cup[x+1,n]\) 全部连通,容易快速算出最大合法的 \(w=\min S\)

注意到 \(w\ge \frac n2\),而我们能通过一些转化使得点数变为 \(n-w\),则这样递归的复杂度为 \(\mathcal O(n)\)

注意到对于一个已经合法的集合,会有 \(2^{n-1}\) 个合法序列,因此我们搜索到的序列会很少。

朴素可以通过大部分测试点,一个优化方法是对 \(n\le 60\) 的情况爆搜连通块等价类并 dp,更优的做法直接对搜索过程中的并查集记忆化即可。

代码:

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int MAXN=1e6+5;
typedef basic_string<int> vec;
mt19937_64 rnd(time(0));
ull hw[MAXN],hc[MAXN];
struct DSU {
	int n; vec fa;
	DSU(int N){ n=N,fa=vec(n,0),iota(fa.begin(),fa.end(),0); }
	int find(int x) { return fa[x]^x?fa[x]=find(fa[x]):fa[x]; }
	void link(int x,int y) { if(~x&&~y) fa[find(x)]=find(y); }
	void add(int c) { for(int i=c;i<n;++i) link(i-c,i); }
	int operator ()(int x) { return find(x); }
	ull qhs(ull z) {
		vec a(n,-1);
		for(int i=0,c=0;i<n;++i) if(fa[i]==i) a[i]=c++;
		for(int i=0;i<n;++i) z=z*hw[i]+hc[a[find(i)]];
		return z;
	}
	void shk(int m) {
		if(n==m) return ;
		vec a(n,-1),b(m,-1);
		for(int i=0;i<m;++i) {
			if(a[find(i)]<0) a[find(i)]=i;
			b[i]=a[find(i)];
		}
		n=m,fa.swap(b);
	}
};
int st[MAXN],tp; ll k;
unordered_map<ull,ll> f;
ll solve(int n,DSU&F,DSU&Q) {
	ull hs=Q.qhs(F.qhs(n));
	if(f.count(hs)&&f[hs]<k) return k-=f[hs],f[hs];
	bool ok=1;
	for(int i=0;i<n;++i) ok&=F(i)==F(Q(i));
	if(ok) {
		if(n<64&&k>(1ll<<(n-1))) return k-=1ll<<(n-1),f[hs]=1ll<<(n-1);
		for(int i=63,o=st[tp];~i;--i) if((k-1)>>i&1) st[++tp]=o+n-1-i;
		for(int i=1;i<=tp;++i) cout<<st[i]<<" \n"[i==tp];
		exit(0);
	}
	int h=n;
	vec c(n,0);
	for(int i=0;i<n;++i) c[F(i)]=max({c[F(i)],n-i-1,i});
	for(int i=0;i<n;++i) if(F(Q(i))!=F(i)) h=min({h,c[F(i)],c[F(Q(i))]});
	ll z=0;
	for(++tp;h;h--) {
		st[tp]=st[tp-1]+h; int m=max(h,n-h); DSU nF(m),nQ(m);
		auto p=[&](int x) { return x>=m?x-h:x; };
		for(int i=0;i<n;++i) nF.link(p(i),p(F(i))),nQ.link(p(i),p(Q(i)));
		nF.add(h),nQ.add(h),nF.shk(n-h),nQ.shk(n-h),z+=solve(n-h,nF,nQ);
	}
	st[tp--]=0;
	return f[hs]=z;
}
signed main() {
	for(int i=0;i<MAXN;++i) hw[i]=rnd()|1,hc[i]=rnd();
	int n; cin>>n>>k;
	DSU F(n),Q(n); bool o=1;
	for(int i=0,x;i<n;++i) cin>>x,Q.link(i,--x),o&=x==i;
	k+=o,solve(n,F,Q);
	cout<<"-1\n";
	return 0;
}



H. [P12050] 三叶虫树 (4)

Problem Link

首先枚举端点集合后确定答案,这是经典问题,每条边取到两侧点数较小值,考虑将端点的重心定根,则最优解是所有点对跨越重心匹配。

因此答案可以写成每个点到重心的距离之和。

那么降序维护该过程,线段树每次取出距离重心最近的点并删除,每次删点只会对重心产生 \(\mathcal O(1)\) 的变化,即只有 \(\mathcal O(1)\) 个点所属子树被修改,简单处理向上或向下的过程可以做到单 \(\log\)

时间复杂度 \(\mathcal O(n\log n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace IO {
int ow,olim=(1<<21)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<21];
#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(ll 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();
}
void putstr(const string &s) {
	for(auto &c:s) obuf[ow++]=c;
	if(ow>=olim) flush();
}
#undef gc
}
const int MAXN=1e6+5;
const ll inf=1e18;
struct Edge { int v,w,e; } G[MAXN*2];
int n,rt,siz[MAXN],cur[MAXN],dfn[MAXN],efn[MAXN],dcnt,hd[MAXN],ec;
int rk[MAXN],hson[MAXN],top[MAXN],fa[MAXN],di[MAXN];
ll d[MAXN],ans=0,f[MAXN];
struct BIT {
	int tr[MAXN];
	void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]+=v; }
	int qy(int x) { int s=0; for(;x;x&=x-1) s+=tr[x]; return s; }
	int fd(int s) { int x=0; for(int k=1<<19;k;k>>=1) if(x+k<=n&&s>tr[x+k]) s-=tr[x+=k]; return rk[x+1]; }
}	B;
struct Segt {
	ll ad[1<<21|5];
	array<ll,2> tr[1<<21|5];
	void adt(int p,ll k) { ad[p]+=k,tr[p][0]+=k; }
	void psu(int p) { tr[p]=min(tr[p<<1],tr[p<<1|1]),tr[p][0]+=ad[p]; }
	void init(int l=1,int r=n,int p=1) {
		if(l==r) return tr[p]={d[rk[l]],rk[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=1,int r=n,int p=1) {
		if(ul<=l&&r<=ur) return adt(p,k);
		int mid=(l+r)>>1;
		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;
void dfs1(int u,int fz) {
	siz[u]=1;
	for(int i=hd[u];i;i=G[i].e) if(G[i].v^fz) dfs1(G[i].v,u),siz[u]+=siz[G[i].v],cur[u]=max(cur[u],siz[G[i].v]);
	cur[u]=max(cur[u],n-siz[u]);
	if(!rt||cur[u]<cur[rt]) rt=u;
}
void dfs2(int u,int fz) {
	siz[u]=1,dfn[u]=++dcnt,rk[dcnt]=u,fa[u]=fz,di[u]=di[fz]+1;
	for(int i=hd[u];i;i=G[i].e) if(G[i].v^fz) {
		d[G[i].v]=d[u]+G[i].w,dfs2(G[i].v,u),siz[u]+=siz[G[i].v];
		if(siz[G[i].v]>siz[hson[u]]) hson[u]=G[i].v;
	}
	efn[u]=dcnt;
}
void dfs3(int u,int tp) {
	top[u]=tp; if(hson[u]) dfs3(hson[u],tp);
	for(int i=hd[u];i;i=G[i].e) if(G[i].v!=fa[u]&&G[i].v!=hson[u]) dfs3(G[i].v,G[i].v);
}
int LCA(int x,int y) {
	for(;top[x]^top[y];x=fa[top[x]]) if(di[top[x]]<di[top[y]]) swap(x,y);
	return di[x]<di[y]?x:y;
}
int in(int u,int v) {
	for(;di[fa[top[v]]]>di[u];v=fa[top[v]]);
	return fa[top[v]]==u?top[v]:hson[u];
}
signed main() {
	n=IO::read();
	for(int i=1,u,v,w;i<n;++i) {
		u=IO::read(),v=IO::read(),w=IO::read();
		G[++ec]={v,w,hd[u]},hd[u]=ec,G[++ec]={u,w,hd[v]},hd[v]=ec;
	}
	dfs1(1,0),dfs2(rt,0),dfs3(rt,rt);
	for(int i=1;i<=n;++i) B.add(dfn[i],1),ans+=d[i];
	T.init(),f[n/2]=ans;
	for(int i=n-1;i>=2;--i) {
		int x=T.tr[1][1];
		ans-=T.tr[1][0],B.add(dfn[x],-1),T.add(dfn[x],dfn[x],inf);
		int l=B.qy(dfn[rt]-1),r=B.qy(efn[rt]);
		if((r-l)*2<i) {
			int u=LCA(B.fd(l),rt),v=LCA(B.fd(r+1),rt);
			if(d[u]<d[v]) swap(u,v);
			ans-=d[rt]-d[u],T.add(1,n,d[u]-d[rt]),T.add(dfn[rt],efn[rt],2*(d[rt]-d[u]));
			rt=u,l=B.qy(dfn[rt]-1),r=B.qy(efn[rt]);
		}
		if(l<r) {
			int y=in(rt,B.fd((l+r+1)/2)),L=B.qy(dfn[y]-1),R=B.qy(efn[y]);
			if((R-L)*2>i) {
				int u=LCA(B.fd(L+1),B.fd(R));
				ans-=d[u]-d[rt],T.add(1,n,d[u]-d[rt]),T.add(dfn[u],efn[u],2*(d[rt]-d[u])),rt=u;
			}
		}
		f[i/2]=ans;
	}
	for(int i=1;i<=n/2;++i) IO::write(f[i]),IO::putc(" \n"[i==n/2]);
	IO::flush();
	return 0;
}



I. [P13497] 墓碑密码 (2)

Problem Link

设答案为关于出现次数为奇数的元素个数 \(y\) 的形式幂级数,所求即为 \(\sum [x^{t_i}]\prod (1+yx^{s_i})\),考虑 FWT 后的数组 \(f\)\(f_x=(1+y)^{|S|-w_x}(1-y)^{w_x}\),其中 \(w_x=\sum |x\cap s_i|\bmod 2\)

最终要求 \(\sum_{t_i}\sum_xf_x(-1)^{|t_i\cap x|}\),所以压位计算每个 \(x\) 对应的 \(\sum |x\cap s_i|\bmod 2,\sum |x\cap t_i|\bmod 2\) 即可求出 \(F(y)\),求答案是平凡的。

时间复杂度 \(\mathcal O(V+qn)\)

代码:

#include<bits/stdc++.h>
#define ll long long
#define pc __builtin_popcountll
#define u128 unsigned __int128
using namespace std;
const int h=14,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; }
int n,m,q,a[135],b[135];
ll f[135],w[135],C[135][135],pr[270],ip[270];
u128 fl[1<<h],gl[1<<h],fr[1<<h],gr[1<<h];
int ct(u128 s) { return pc(s>>64)+pc(s&(-1ull)); }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;++i) cin>>a[i];
	for(int i=0;i<m;++i) cin>>b[i];
	for(int s=0;s<(1<<h);++s) {
		for(int i=n-1;~i;--i) {
			fl[s]=fl[s]<<1|(pc(s&a[i])&1);
			fr[s]=fr[s]<<1|(pc((s<<h)&a[i])&1);
		}
		for(int i=m-1;~i;--i) {
			gl[s]=gl[s]<<1|(pc(s&b[i])&1);
			gr[s]=gr[s]<<1|(pc((s<<h)&b[i])&1);
		}
	}
	for(int s=0;s<(1<<h);++s) for(int t=0;t<(1<<h);++t) {
		w[ct(fl[s]^fr[t])]+=m-2*ct(gl[s]^gr[t]);
	}
	for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	for(int i=0;i<=n;++i) {
		w[i]=(w[i]%MOD+MOD)*ksm(1<<(2*h))%MOD;
		for(int j=0;j<=i;++j) for(int k=0;k<=n-i;++k) {
			f[j+k]=(f[j+k]+(j&1?MOD-w[i]:w[i])*C[n-i][k]%MOD*C[i][j])%MOD;
		}
	}
	cin>>q;
	ll iv=1;
	for(int i=1;i<=n;++i) iv=iv*ksm(i)%MOD;
	for(int z,o,t,ans;q--;) {
		cin>>z,pr[0]=1,o=max(0,z-n)/2,ans=0,t=z/2-o+n;
		for(int i=1;i<=t;++i) pr[i]=pr[i-1]*(o+i)%MOD;
		ip[t]=ksm(pr[t]);
		for(int i=t;i;--i) ip[i-1]=ip[i]*(o+i)%MOD;
		for(int i=0;i<=n&&i<=z;++i) ans=(ans+pr[(z-i)/2-o+n]*ip[(z-i)/2-o]%MOD*f[i])%MOD;
		cout<<ans*iv%MOD<<"\n";
	}
	return 0;
}



J. [P13699] Doomed Doom (4.5)

Problem Link

首先每个假设每个字符串已经没有相邻的相同元素。

\(n=2\) 的情况出发,此时两个串一定表示成 \(AS^aA^{-1},AS^bA^{-1}\),其中 \(S^{-k}\) 表示 \(\mathrm{rev}(S)^k\)

需要满足 \(a,b\ne 0\)\(S\) 的开头结尾不同或 \(|S|=a=b=1\)

可以证明如果 \(n>2\) 则每个串都要表示成这个形状,否则我们能得到 \(TS^aT^{-1}=S^b\),观察开头结尾字符即可导出矛盾。

那么简单计数即可。

注意可以加入一些空串,且非空串 $\le 1 $ 必定合法。

时间复杂度 \(\mathcal O(m^2\log m)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4505,MOD=998244353,i2=(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; }
int n,m;
ll f[MAXN],g[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,f[0]=1;
	for(int i=1;i<=m;++i) {
		memset(g,0,sizeof(g)),g[0]=f[1]*3%MOD;
		for(int j=1;j<=i;++j) g[j]=(f[j-1]+f[j+1]*2)%MOD;
		memcpy(f,g,sizeof(f));
	}
	ll ans=((ksm(3,m)+MOD-f[0])*n+f[0])%MOD*ksm(f[0],n-1)%MOD;
	memset(g,0,sizeof(g)),g[0]=3;
	for(int i=2;i<=m;++i) g[i]=(g[i-1]+g[i-2]*2)%MOD;
	for(int i=1;i<=m;++i) for(int j=2*i;j<=m;j+=i) g[j]=(g[j]+MOD-g[i])%MOD;
	for(int i=0;2*i+1<=m;++i) {
		ans=(ans+ksm(2,i)*3*ksm(f[2*i+1],n))%MOD;
	}
	for(int i=2;i<=m;++i) {
		for(int j=0;2*j+i<=m;++j) {
			ll s=0;
			for(int k=2*j+i;k<=m;k+=i) s=(s+f[k]*2)%MOD;
			ll z=(ksm((s+f[0])%MOD,n)+(s*n+f[0])%MOD*(MOD-ksm(f[0],n-1)))%MOD;
			ans=(ans+z*i2%MOD*g[i]%MOD*ksm(2,max(0,j-1)))%MOD;
		}
	}
	cout<<ans*ksm(3,MOD-1-n*m)%MOD<<"\n";
	return 0;
}



K. [P14135] Miserable EXperience (3.5)

Problem Link

首先从上到下操作每层的点使得它们所有点点权和本层最小值相同,这些操作是必须进行的。

然后对于某个层 \(d\),可以用一次操作令 \(a_d\gets a_d-1\),或者用 \(sz_d\)\(a[d,n]\) 减一。

从下往上贪心,每当 \(\min a[d,n]\) 减少的时候就弹出后缀中最优秀的操作。

容易发现上述过程可以用长剖优化。

时间复杂度 \(\mathcal O(n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
vector <int> G[MAXN];
int n,fa[MAXN],d[MAXN],hson[MAXN],h[MAXN],dcnt;
ll a[MAXN],w0[MAXN],sm[MAXN],f[MAXN];
ll mn[MAXN*2],sz[MAXN*2],tg[MAXN*2],vl[MAXN*2],up[MAXN*2],dp[MAXN*2];
void dfs1(int u) {
	d[u]=1;
	for(int v:G[u]) {
		dfs1(v);
		if(d[v]+1>d[u]) d[u]=d[v]+1,hson[u]=v;
	}
}
void dfs2(int u) {
	h[u]=++dcnt;
	if(hson[u]) dfs2(hson[u]);
	else up[++dcnt]=1e18;
	for(int v:G[u]) if(v^hson[u]) dfs2(v);
}
void adt(int u,int i,ll x) {
	tg[h[u]+i]+=x,mn[h[u]+i]+=x,up[h[u]+i]+=x;
}
void dfs3(int u) {
	for(int v:G[u]) dfs3(v),w0[u]+=w0[v];
	for(int v:G[u]) if(f[v]<0) return f[u]=-1,void();
	mn[h[u]]=a[u],sz[h[u]]=1,sm[u]=sm[hson[u]]+a[u];
	int e=0;
	for(int v:G[u]) if(v^hson[u]) {
		ll dx=0,dy=0;
		for(int i=1;i<=d[v];++i) {
			adt(u,i+1,tg[h[u]+i]),tg[h[u]+i]=0;
			adt(v,i,tg[h[v]+i-1]),tg[h[v]+i-1]=0;
			ll x=mn[h[u]+i]-dx,y=mn[h[v]+i-1]-dy;
			if(x<0||y<0) return f[u]=-1,void();
			if(x<y) w0[u]+=(y-x)*sz[h[v]+i-1],dy+=y-x;
			else w0[u]+=(x-y)*sz[h[u]+i],dx+=x-y;
			sm[u]+=min(x,y)-mn[h[u]+i],mn[h[u]+i]=min(x,y),sz[h[u]+i]+=sz[h[v]+i-1];
		}
		sm[u]-=(d[u]-d[v]-1)*dx,adt(u,d[v]+1,-dx),e=max(e,d[v]);
		for(int i=d[v];~i;--i) up[h[u]+i]=min(mn[h[u]+i],up[h[u]+i+1]);
		if(up[h[u]]<0) return f[u]=-1,void();
	}
	f[u]=w0[u]+sm[u];
	for(int i=0;i<=e;++i) adt(u,i+1,tg[h[u]+i]),tg[h[u]+i]=0;
	for(int i=e;~i;--i) {
		vl[h[u]+i]=max(vl[h[u]+i+1],d[u]-i-sz[h[u]+i]);
		up[h[u]+i]=min(up[h[u]+i+1],mn[h[u]+i]);
		dp[h[u]+i]=dp[h[u]+i+1]+(up[h[u]+i+1]-up[h[u]+i])*vl[h[u]+i+1];
	}
	f[u]-=dp[h[u]]+vl[h[u]]*up[h[u]];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	string O;
	cin>>n>>O;
	for(int i=2,j=0;i<=n;++i) {
		for(;O[i+j-2]^'1';++j);
		G[fa[i]=j].push_back(i);
	}
	cin>>O;
	for(int i=1,j=0;i<=n;++i) for(int x:{4,3,2,1,0}) a[i]|=(O[j++]-33)<<(x*6);
	dfs1(1),dfs2(1),dfs3(1);
	ll ans=0;
	for(int i=1;i<=n;++i) ans^=f[i]+1;
	cout<<ans<<"\n";
	return 0;
}



L. [P13542] Two avenues (3)

Problem Link

首先建立 dfs 树以及切边等价类,删掉两条割边的情况平凡,考虑删掉切边等价类里的两条边。

如果是一条树边和非树边,则只要计算跨过该树边的路径数。

如果是两条树边,则计算分别跨过两条树边的路径数,减去两倍的同时跨过两条树边的路径数。

由于同一个切边等价类的边在 dfs 树上处于一条链中,可以发现对应的权值函数满足四边形不等式,所以可以决策单调性分治快速计算,具体算权值可以用线段树合并或主席树。

时间复杂度 \(\mathcal O((n+q)\log^2 n)\)

代码:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=5e5+5,MAXS=5e7+5;
struct Segt {
	int tr[MAXS],ls[MAXS],rs[MAXS],tot;
	void init() { for(int i=1;i<=tot;++i) tr[i]=ls[i]=rs[i]=0; tot=0; }
	void ins(int x,int l,int r,int &p) {
		++tr[p?p:(p=++tot)];
		if(l==r) return ;
		int mid=(l+r)>>1;
		x<=mid?ins(x,l,mid,ls[p]):ins(x,mid+1,r,rs[p]);
	}
	int merge(int l,int r,int q,int p) {
		if(!p||!q) return p|q;
		int u=++tot; tr[u]=tr[p]+tr[q];
		if(l==r) return u;
		int mid=(l+r)>>1;
		ls[u]=merge(l,mid,ls[q],ls[p]),rs[u]=merge(mid+1,r,rs[q],rs[p]);
		return u;
	}
	int qry(int ul,int ur,int l,int r,int p) {
		if(ul<=l&&r<=ur) return tr[p];
		int mid=(l+r)>>1,s=0;
		if(ul<=mid) s+=qry(ul,ur,l,mid,ls[p]);
		if(mid<ur) s+=qry(ul,ur,mid+1,r,rs[p]);
		return s;
	}
}	T;
mt19937_64 rnd(time(0));
ull hs[MAXN],hw[MAXN];
unordered_map <ull,vector<int>> F;
int _,ty,n,m,q,d[MAXN],U[MAXN],V[MAXN],fa[MAXN];
struct Edge { int v,i; };
vector <Edge> G[MAXN];
int dfn[MAXN],st[20][MAXN],dcnt;
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[k][l],st[k][r-(1<<k)+1]);
}
void dfs1(int u,int fz) {
	fa[u]=fz,d[u]=d[fz]+1,dfn[u]=++dcnt,st[0][dcnt]=fz;
	for(auto e:G[u]) if(e.v^fz) {
		if(!d[e.v]) dfs1(e.v,u),hs[u]^=hs[e.v];
		else if(d[e.v]<d[u]) hw[e.i]=rnd(),hs[u]^=hw[e.i],hs[e.v]^=hw[e.i];
	}
	if(u>1) F[hs[u]].push_back(u);
}
array<int,5>z;
int rt[MAXN],f[MAXN],b[MAXN];
void dfs2(int u) {
	for(auto e:G[u]) if(d[e.v]==d[u]+1) dfs2(e.v),f[u]+=f[e.v],rt[u]=T.merge(1,n,rt[e.v],rt[u]);
}
void cdq(int l,int r,int L,int R) {
	if(l>r) return ;
	int v=-1,o=0,p=(l+r)>>1;
	for(int i=L;i<p&&i<=R;++i) {
		int w=f[b[i]]+f[b[p]]-2*T.qry(1,d[b[i]]-1,1,n,rt[b[p]]);
		if(w>v) v=w,o=i;
	}
	z=max(z,array<int,5>{v,fa[b[o]],b[o],fa[b[p]],b[p]});
	cdq(l,p-1,L,o),cdq(p+1,r,o,R);
}
void solve() {
	cin>>n>>m;
	for(int i=1;i<=m;++i) cin>>U[i]>>V[i],G[U[i]].push_back({V[i],i}),G[V[i]].push_back({U[i],i});
	dfs1(1,0),z={0,U[1],V[1],U[2],V[2]};
	for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
		st[k][i]=cmp(st[k-1][i],st[k-1][i+(1<<(k-1))]);
	}
	cin>>q;
	for(int i=1,u,v,w;i<=q;++i) {
		cin>>u>>v,w=LCA(u,v),++f[u],++f[v],f[w]-=2;
		T.ins(d[w],1,n,rt[u]),T.ins(d[w],1,n,rt[v]);
	}
	dfs2(1);
	vector <array<int,2>> bg;
	for(int i=2;i<=n;++i) bg.push_back({hs[i]?0:f[i],i});
	sort(bg.begin(),bg.end(),greater<>());
	z=max(z,array<int,5>{bg[0][0]+bg[1][0],fa[bg[0][1]],bg[0][1],fa[bg[1][1]],bg[1][1]});
	for(int i=1;i<=m;++i) if(hw[i]&&F.count(hw[i])) for(int x:F[hw[i]]) {
		z=max(z,array<int,5>{f[x],U[i],V[i],x,fa[x]});
	}
	for(auto&it:F) if(it.first) {
		int tp=0; for(int u:it.second) b[++tp]=u;
		sort(b+1,b+tp+1,[&](int x,int y){ return d[x]<d[y]; });
		if(tp>1) cdq(2,tp,1,tp-1);
	}
	cout<<z[0]<<"\n"<<z[1]<<" "<<z[2]<<"\n"<<z[3]<<" "<<z[4]<<"\n";
	dcnt=0,z.fill(0),F.clear(),T.init();
	for(int i=1;i<=n;++i) b[i]=d[i]=dfn[i]=f[i]=fa[i]=rt[i]=0,G[i].clear(),hs[i]=0;
	for(int i=1;i<=m;++i) hw[i]=U[i]=V[i]=0;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>_>>ty;
	while(_--) solve();
	return 0;
}



*M. [P13835] 返夏 (8.5)

Problem Link

根据矩阵树定理计算答案,我们每次要计算 \(\det(A)\to \det(A+B)\) 的类似过程。

根据 Determinant Lemma,\(\det(A+uv^T)=\det(I_m+v^TA^{-1}u)\det(A)\)

但我们无法在每次修改后动态维护 \(A^{-1}\)

考虑从初始情况开始,注意到 \(1\sim n\) 的链对应的矩阵树定理得到的矩阵 \(A\) 求逆很简单,模拟消元过程可知 \(A^{-1}_{i,j}=n-1-\max(i,j)\)

我们把 \((1,n)\) 看成一条新加的边,
此时矩阵 \(u=v\) 且为 \((n-1)\times (m+1)\) 矩阵,\(u_{u_i,i}=1,u_{v_i,i}=-1\)

那么只要动态维护 \(\det(I_m,v^T+A^{-1}u)\) 即可,那么我们要解决的就是动态地给对称矩阵 \(C\) 加入一行一列并维护 \(\det(C)\)

考虑手动维护消元的过程,\(\begin{bmatrix}C^{-1}&0\\-a^TC^{-1}&1\end{bmatrix}\times\begin{bmatrix}C&a\\a^T&b\end{bmatrix}=\begin{bmatrix}I&a\\0&b-a^TC^{-1}a\end{bmatrix}\)

\(w=b-a^TC^{-1}a\),则 \(\det(C')=\det(C)\times w\)

进一步消元得到 \(C'^{-1}=\begin{bmatrix}C^{-1}-\frac 1wC^{-1}aa^TC^{-1}&-\frac 1wC^{-1}a\\-\frac 1wa^TC^{-1}&\frac 1w\end{bmatrix}\),直接暴力维护即可。

时间复杂度 \(\mathcal O(m^3)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=998244353;
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; }
int n,m,u[805],v[805];
ll z,C[805][805],D[805][805],a[805],b[805];
int wc(int x,int y) { return max(0,min(v[x],v[y])-max(u[x],u[y])); }
signed main( ){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,u[1]=1,v[1]=n,C[1][1]=z=n,D[1][1]=ksm(n);
	for(int k=2;k<=m+1;++k) {
		cin>>u[k]>>v[k];
		if(v[k]<u[k]) swap(u[k],v[k]);
		ll w=C[k][k]=v[k]-u[k]+1;
		for(int i=1;i<k;++i) C[i][k]=C[k][i]=a[i]=wc(i,k),b[i]=0;
		for(int i=1;i<k;++i) for(int j=1;j<k;++j) b[i]=(b[i]+D[i][j]*a[j])%MOD;
		for(int i=1;i<k;++i) w=(w+a[i]*(MOD-b[i]))%MOD;
		z=z*w%MOD,D[k][k]=w=ksm(w),cout<<z<<"\n";
		for(int i=1;i<k;++i) for(int j=1;j<k;++j) D[i][j]=(D[i][j]+b[i]*b[j]%MOD*w)%MOD;
		for(int i=1;i<k;++i) D[i][k]=D[k][i]=(MOD-b[i])*w%MOD;
	}
	return 0;
}



N. [P12011] 春开,意遥遥 (3)

Problem Link

观察发现操作是异或卷积,那么 FWT 后观察所有的 \((x+y,y-x)\),乘法变成对应位置相乘。

进一步只要维护 \(w=\prod \dfrac{x+y}{y-x}\) 即可,如果有 \((x+y)(y-x)=0\) 的元素,则区间答案必定是 \(0\)

注意 \(p=2\) 的情况要特判。

那么我们要求的就是 \(w_1\sim w_k\) 乘积能生成多少数,求 \(p\) 的原根 \(g\) 以及离散对数 \(h_1\sim h_k\),答案为 \(\dfrac{p-1}{\gcd(h_i,p-1)}\)

求所有 \(p_i\) 无法接受,发现答案实际上是 \(\mathrm{lcm}(\delta_p(w_i))\),而求解只要试除法容易做到 \(\mathcal O(\log^2p)\)

区间 \(\mathrm{lcm}\) 之和由于每个左端点处 \(\mathrm{gcd}\) 只会变化 \(\mathcal O(\log p)\) 次,因此可以直接暴力维护。

时间复杂度 \(\mathcal O(n\log^2p)\)

代码:

#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const int MAXN=1e5+5,MOD=1e9+7;
int n; ll p,m; vector <ll> fc;
LL ksm(LL a,LL b) { LL s=1; for(;b;a=a*a%p,b>>=1) if(b&1) s=s*a%p; return s; }
void PR(ll x) {
	for(ll i=2;i<=x/i;++i) if(x%i==0) {
		fc.push_back(i);
		for(;x%i==0;x/=i);
	}
	if(x>1) fc.push_back(x);
}
ll a[MAXN];
ll qry(ll x) {
	ll w=m;
	for(ll k:fc) for(;w%k==0;w/=k) if(ksm(x,w/k)!=1) break;
	return w;
}
ll lcm(ll x,ll y) { return x&&y?x/__gcd(x,y)*y:0; }
array<ll,2> f[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>p,m=p-1,PR(m);
	if(p==2) {
		ll s=1ll*n*(n+1)/2;
		for(int i=1,l=0,r=0,x,y;i<=n;++i) {
			cin>>x>>y;
			if(x==y) l=i;
			else if(x&&!y) r=i;
			s+=max(0,r-l);
		}
		return cout<<s%MOD<<"\n",0;
	}
	for(int i=1;i<=n;++i) {
		ll x,y,l,r; cin>>x>>y,l=(x+y)%p,r=(y+p-x)%p;
		if(l&&r) a[i]=qry(l*ksm(r,p-2)%p);
	}
	ll s=0; f[0][0]=-1;
	for(int i=1,h=0;i<=n;++i) {
		if(!a[i]) f[h=1]={0,i};
		else {
			int k=0;
			for(int j=1;j<=h;++j) {
				ll z=lcm(f[j][0],a[i]);
				if(f[k][0]!=z) f[++k]={z,f[j][1]};
				else f[k][1]=f[j][1];
			}
			if(f[k][0]!=a[i]) f[++k]={a[i],i};
			else f[k][1]=i;
			h=k;
		}
		for(int j=1;j<=h;++j) s=(s+max(1ll,f[j][0])%MOD*(f[j][1]-f[j-1][1]))%MOD;
	}
	cout<<s<<"\n";
	return 0;
}



O. [P10121] 保险丝 (4)

Problem Link

答案难以快速维护,考虑观察暴力的性质。

部分分提示不存在二度点时搜到的点数很少,容易发现最劣情况为满二叉树,此时搜索此时也是 \(\mathcal O(n\log n)\) 级别的。

因此对每个点子树内的 \(\ge 3\) 度点建虚树并暴力搜索统计,不在虚树上的二度点只要提前二维数点求出总点数就行。

时间复杂度 \(\mathcal O(n\log n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef basic_string<int> vec;
const int MAXN=1e6+5,MOD=994007158;
vec G[MAXN],W[MAXN],D[MAXN],E[MAXN];
int n,fa[MAXN],d[MAXN],dep[MAXN],dfn[MAXN],efn[MAXN],dcnt,rk[MAXN],st[20][MAXN];
ll fb[MAXN];
void dfs1(int u) {
	dfn[u]=++dcnt,st[0][dcnt]=fa[u],dep[u]=dep[fa[u]]+1,rk[dcnt]=u;
	for(int v:G[u]) dfs1(v),d[u]=d[u]?min(d[u],d[v]+1):d[v]+1;
	efn[u]=dcnt;
}
void dfs2(int u,int x) {
	W[max(1,dep[u]-x)]+=u,D[dep[u]]+=u;
	for(int v:G[u]) dfs2(v,min(d[v],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[k][l],st[k][r-(1<<k)+1]);
}
ll f[MAXN],g[MAXN];
struct BIT {
	int tr[MAXN],s;
	void add(int x) { for(;x<=n;x+=x&-x) ++tr[x]; }
	int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
}	T;
int dsu[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs4(int u,int rt,int c) {
	g[u]=fb[G[u].size()+(u>1)];
	for(int v:E[u]) dfs4(v,rt,dep[v]-dep[u]),g[u]=g[u]*g[v]%MOD;
	f[rt]=(f[rt]+(g[u]-1)*c)%MOD;
}
void solve(int u) {
	vec V{u};
	for(int i=find(dfn[u]+1);i<=efn[u];i=find(i+1)) V.push_back(rk[i]);
	for(int i=1,m=V.size();i<m;++i) V.push_back(LCA(V[i-1],V[i]));
	sort(V.begin(),V.end(),[&](int x,int y){ return dfn[x]<dfn[y]; });
	V.erase(unique(V.begin(),V.end()),V.end());
	for(int i=1;i<(int)V.size();++i) E[LCA(V[i-1],V[i])].push_back(V[i]);
	dfs4(u,u,1);
	for(int x:V) E[x].clear(),g[x]=0;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=2;i<=n;++i) cin>>fa[i],G[fa[i]]+=i;
	fb[1]=fb[2]=1;
	for(int i=3;i<=n;++i) fb[i]=(fb[i-1]+fb[i-2])%MOD;
	dfs1(1),dfs2(1,d[1]);
	for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
		st[k][i]=min(st[k-1][i],st[k-1][i+(1<<(k-1))]);
	}
	for(int i=1;i<=n;++i) {
		for(int u:W[i]) T.add(dfn[u]);
		for(int u:D[i]) f[u]=T.qry(efn[u])-T.qry(dfn[u]-1);
	}
	iota(dsu+1,dsu+n+2,1);
	for(int i=1;i<=n;++i) if(G[i].size()<2) dsu[dfn[i]]=dfn[i]+1;
	for(int i=n;i>=1;--i) {
		for(int u:D[i]) solve(u);
		for(int u:W[i]) dsu[dfn[u]]=dfn[u]+1;
	}
	ll ans=0;
	for(int i=1;i<=n;++i) ans^=(f[i]+MOD)%MOD;
	cout<<ans<<"\n";
	return 0;
}



P. [P13242] 你的名字 (2.5)

Problem Link

后缀排序后,所有 \(t[l,r]\) 出现的位置都是一个区间,我们只要算出这个区间中每个 \(s_i\)\(\sum \min a[l,i]\)

那么前缀最小值问题可以考虑单侧递归线段树,强制在线套上主席树,注意到单侧递归时不会改被递归的点信息,因此空间复杂度不变。

时间复杂度 \(\mathcal O((n+m+q)\log^2 n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
struct SA {
	static const int MAXN=6e5+5;
	char s[MAXN];
	int n,m,sa[MAXN],rk[MAXN],ct[MAXN],t[MAXN],f[20][MAXN];
	void init() {
		m=27,memset(ct,0,(m+1)<<2),s[n+1]=0;
		for(int i=1;i<=n;++i) rk[i]=s[i]-'a'+1,++ct[rk[i]];
		for(int i=1;i<=m;++i) ct[i]+=ct[i-1];
		for(int i=n;i;--i) sa[ct[rk[i]]--]=i;
		for(int k=1;;k<<=1) {
			for(int i=1;i<=k;++i) t[i]=i+n-k;
			for(int i=1,h=k;i<=n;++i) if(sa[i]>k) t[++h]=sa[i]-k;
			memset(ct,0,(m+1)<<2);
			for(int i=1;i<=n;++i) ++ct[rk[i]];
			for(int i=1;i<=m;++i) ct[i]+=ct[i-1];
			for(int i=n;i;--i) sa[ct[rk[t[i]]]--]=t[i];
			memcpy(t,rk,(n+1)<<2),m=0,t[n+1]=0;
			for(int i=1;i<=n;++i) {
				rk[sa[i]]=m+=(i==1||t[sa[i]]!=t[sa[i-1]]||t[min(n+1,sa[i]+k)]!=t[min(n+1,sa[i-1]+k)]);
			}
			if(m==n) break;
		}
		for(int i=1,k=0;i<=n;++i) {
			for(k=max(k-1,0);s[i+k]==s[sa[rk[i]-1]+k];++k);
			f[0][rk[i]]=k;
		}
		for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
			f[k][i]=min(f[k-1][i],f[k-1][i+(1<<(k-1))]);
		}
	}
	int lcp(int x,int y) {
		if(x==y) return n-x+1;
		int l=min(rk[x],rk[y])+1,r=max(rk[x],rk[y]),k=__lg(r-l+1);
		return min(f[k][l],f[k][r-(1<<k)+1]);
	}
}	S;
int _,n,q,O,OL,OR,a[MAXN],bl[MAXN*3],rt[MAXN*3],mn[20][MAXN];
int qmn(int l,int r) {
	int k=__lg(r-l+1);
	return min(mn[k][l],mn[k][r-(1<<k)+1]);
}
struct Segt {
	static const int MAXS=MAXN*100;
	int ls[MAXS],rs[MAXS],tot,ct[MAXS];
	ll f[MAXS];
	void init(int l,int r,int &p) {
		p=++tot;
		if(l==r) return ;
		int mid=(l+r)>>1;
		init(l,mid,ls[p]),init(mid+1,r,rs[p]);
	}
	ll ask(int w,int l,int r,int p) {
		if(l==r) return 1ll*ct[p]*min(w,a[l]);
		int mid=(l+r)>>1;
		if(w<=qmn(l,mid)) return 1ll*ct[ls[p]]*w+ask(w,mid+1,r,rs[p]);
		return ask(w,l,mid,ls[p])+f[p]-f[ls[p]];
	}
	void psu(int l,int r,int p) {
		int mid=(l+r)>>1;
		f[p]=f[ls[p]]+ask(qmn(l,mid),mid+1,r,rs[p]);
	}
	void ins(int x,int l,int r,int o,int &p) {
		ct[p=++tot]=ct[o]+1;
		if(l==r) return f[p]=f[o]+a[x],void();
		int mid=(l+r)>>1;
		if(x<=mid) ins(x,l,mid,ls[o],ls[p]),rs[p]=rs[o];
		else ins(x,mid+1,r,rs[o],rs[p]),ls[p]=ls[o];
		psu(l,r,p);
	}
	int z; ll s;
	void qry(int ul,int ur,int l,int r,int p) {
		if(ul<=l&&r<=ur) return s+=ask(z,l,r,p),z=min(z,qmn(l,r)),void();
		int mid=(l+r)>>1;
		if(ul<=mid) qry(ul,ur,l,mid,ls[p]);
		if(mid<ur) qry(ul,ur,mid+1,r,rs[p]);
	}
	ll qry(int l,int r,int p) {
		return z=a[l],s=0,qry(l,r,1,n,p),s;
	}
}	T;
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>_>>n>>q>>O>>OL>>OR;
	string str;
	for(int i=1;i<=n;++i) {
		cin>>str;
		for(auto c:str) S.s[++S.n]=c,bl[S.n]=i;
		S.s[++S.n]='{';
	}
	cin>>str;
	int hd=S.n,m=str.size();
	for(auto c:str) S.s[++S.n]=c;
	for(int i=1;i<=n;++i) cin>>a[i],mn[0][i]=a[i];
	for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
		mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<(k-1))]);
	}
	S.init(),T.init(1,n,rt[0]);
	for(int i=1;i<=S.n;++i) {
		rt[i]=rt[i-1];
		if(bl[S.sa[i]]) T.ins(bl[S.sa[i]],1,n,rt[i],rt[i]);
	}
	for(ll l1,r1,l2,r2,lst=0;q--;) {
		cin>>l1>>r1>>l2>>r2;
		l1=(l1+lst-1)%n+1,r1=(r1+lst-1)%n+1,l2=(l2+lst-1)%m+1,r2=(r2+lst-1)%m+1;
		if(l1>r1) swap(l1,r1);
		if(l2>r2) swap(l2,r2);
		l1=(~OL?OL:l1),r1=(~OR?OR:r1);
		int l=S.rk[hd+l2],r=l;
		for(int k=1<<19;k;k>>=1) {
			if(l>k&&S.lcp(hd+l2,S.sa[l-k])>=r2-l2+1) l-=k;
			if(r+k<=S.n&&S.lcp(hd+l2,S.sa[r+k])>=r2-l2+1) r+=k;
		}
		cout<<(lst=T.qry(l1,r1,rt[r])-T.qry(l1,r1,rt[l-1]))<<"\n",lst*=O;
	}
	return 0;
}



*Q. [P14033] 子集乘积 (9)

Problem Link

首先我们无法分别 \(a,\overline a\),所以不妨钦定 \(a_1=0\)

不妨假设我们每次询问 \(S\) 能得到 \(S\)\(1\) 的个数,可以通过询问 \(S,S\cup\{1\}\) 实现。

那么我们尝试分治。

把待确定集合 \(Q\) 分成相等的两个部分 \(L,R\),对其分别构造还原方案,则对于 \(L,R\) 的第 \(i\) 次询问,我们询问 \(L_i\cup R_i,L_i\cup (R\setminus R_i)\) 就能得到询问 \(L_i,R_i\) 的答案。

注意到任何时候这两次询问同奇偶,那么不妨第二次询问 \(L_i\cup (R\setminus R_i)\cup\{x\}\),那么如果两次询问不同奇偶s说明 \(a_x=1\),否则 \(a_x=0\)

还需要一次额外的询问求 \(R\) 内部 \(1\) 个数,然后可以倒推 \(L\) 内部元素个数。

\(f_n\) 表示确定 \(n\) 个元素的最小代价,则转移为 \(f_{2i+f_i}\gets 2f_i+1\)

但此时一次查询需要交互两次,考虑优化。

我们只要确定 \(0,1\) 中哪种元素更多,那么我们尝试利用一些已知的元素,具体来说把所有已知的 \(a_i=x\) 的元素取出来,设有 \(c\) 个,则只要初始的 \(|S|\le c\),那么我们只要一次交互就能确定。

显然 \(c\) 至少是已知元素的一半,所以设 \(g_i\) 表示得到 \(i\) 个元素的最小代价,转移为 \(g_{i+j}\gets g_i+f_j(j\le\lceil i/2\rceil)\),此时算出 \(g_n\) 还差 \(\mathcal O(1)\) 次。

\(f\) 转移的时候可以类似三等分 \(Q\),然后用三次询问解出三个值以及两个其他位置,转移为 \(f_{3i+2f_i}\gets 3f_i+2\),此时 \(g_n\) 恰好满足题意。

时间复杂度 \(\mathcal O(n^2)\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef basic_string<int> vec;
const int MAXN=1005;
int a[MAXN],f[MAXN],g[MAXN],pf[MAXN],of[MAXN],pg[MAXN];
vec mov(vec S,int d) { for(int &x:S) x+=d; return S; }
vec rev(const vec&S,int n) {
	static bitset<MAXN> qy;
	qy.reset(); for(int u:S) qy[u]=1;
	vec T;
	for(int i=1;i<=n;++i) if(!qy[i]) T+=i;
	return T;
}
vector<vec> dp[MAXN];
void build(int n) {
	if(dp[n].size()||n==1) return ;
	auto add=[&](const vec&S) { dp[n].push_back(S); };
	if(of[n]==1) {
		build(n-1),dp[n]=dp[n-1],add({n});
		return ;
	} else if(of[n]==2) {
		int m=pf[n];
		build(m);
		for(int i=0;i<f[m];++i) {
			vec X=dp[m][i],Y=rev(X,m);
			add(X+mov(X,m)),add(X+mov(Y,m));
			if(2*m+1+i<=n) dp[n].back()+=2*m+1+i;
		}
		add({});
		for(int i=1;i<=m;++i) dp[n].back()+=m+i;
	} else {
		int m=pf[n];
		build(m);
		for(int i=0;i<f[m];++i) {
			vec X=dp[m][i],Y=rev(X,m);
			add(X+mov(X,m)+mov(X,2*m));
			add(X+mov(Y,m)+mov(X,2*m));
			if(3*m+1+2*i<=n) dp[n].back()+=3*m+1+2*i;
			add(X+mov(X,m)+mov(Y,2*m));
			if(3*m+2+2*i<=n) dp[n].back()+=3*m+2+2*i;
		}
		add({});
		for(int i=1;i<=m;++i) dp[n].back()+=m+i;
		add({});
		for(int i=1;i<=m;++i) dp[n].back()+=2*m+i;
	}
}
void calc(int l,int r,vec&Z,int ct) {
	if(ct==r-l||ct==0) {
		for(int i=l+1;i<=r;++i) a[i]=!!ct;
		return ;
	}
	int n=r-l;
	if(of[n]==1) {
		a[r]=Z.back(),Z.pop_back();
		return calc(l,r-1,Z,ct-a[r]);
	} else if(of[n]==2) {
		int m=pf[n],rc=Z.back(),lc=ct-rc; Z.pop_back();
		vec LZ,RZ;
		for(int i=0;i<f[m];++i) {
			int u=Z[2*i],v=Z[2*i+1]-rc;
			if(l+2*m+1+i<=r) a[l+2*m+1+i]=(u+v)&1,v-=(u+v)&1;
			LZ+=(u+v)/2,RZ+=(u-v)/2;
		}
		for(int i=l+2*m+1;i<=r;++i) lc-=a[i];
		calc(l,l+m,LZ,lc),calc(l+m,l+2*m,RZ,rc);
	} else {
		int m=pf[n],rc=Z.back(); Z.pop_back();
		int mc=Z.back(),lc=ct-rc-mc; Z.pop_back();
		vec LZ,MZ,RZ;
		for(int i=0;i<f[m];++i) {
			int u=Z[3*i],v=Z[3*i+1]-mc,w=Z[3*i+2]-rc;
			if(l+3*m+1+2*i<=r) a[l+3*m+1+2*i]=(u+v)&1,v-=(u+v)&1;
			if(l+3*m+2+2*i<=r) a[l+3*m+2+2*i]=(u+w)&1,w-=(u+w)&1;
			v=(u-v)/2,w=(u-w)/2,LZ+=u-v-w,MZ+=v,RZ+=w;
		}
		for(int i=l+3*m+1;i<=r;++i) lc-=a[i];
		calc(l,l+m,LZ,lc),calc(l+m,l+2*m,MZ,mc),calc(l+2*m,l+3*m,RZ,rc);
	}
}
int n,m;
int qry(const vec &S) {
	static bitset<MAXN> qy; qy.reset();
	for(int u:S) qy[u]=1;
	int c=count(a+1,a+m+1,1),o=1;
	if(m-c>c) o=0,c=m-c;
	for(int i=1;i<=m;++i) if(a[i]==o) qy[i]=1;
	cout<<"? "; for(int i=1;i<=n;++i) cout<<qy[i]; cout<<endl;
	int p,s=c+S.size(); cin>>p;
	if(!p) return o?s-c:0;
	for(int x=1;x<=s/2;++x) if(x*(s-x)==p) return o?s-x-c:x;
	return -1;
}
void solve() {
	cin>>n,a[m=1]=0;
	vec sz;
	for(int t=n;t>1;t-=pg[t]) sz+=pg[t];
	reverse(sz.begin(),sz.end());
	for(int k:sz) {
		build(k);
		vec Z,O;
		for(auto&S:dp[k]) Z+=qry(mov(S,m));
		for(int i=1;i<=k;++i) O+=m+i;
		int sb=qry(O);
		calc(m,m+k,Z,sb),m+=k;
	}
	cout<<"! "; for(int i=1;i<=n;++i) cout<<a[i]; cout<<endl;
	int o; cin>>o; if(o) return ;
	cout<<"! "; for(int i=1;i<=n;++i) cout<<1-a[i]; cout<<endl;
	cin>>o,memset(a,0,sizeof(a));
}
signed main() {
	const int N=1000;
	memset(f,0x3f,sizeof(f)),memset(g,0x3f,sizeof(g)),f[1]=g[1]=0;
	for(int i=1;i<=N;++i) {
		if(f[i+1]>f[i]+1) f[i+1]=f[i]+1,of[i+1]=1;
		for(int j=i+1;j<=min(N,2*i+f[i]);++j) if(f[j]>2*f[i]+1) f[j]=2*f[i]+1,of[j]=2,pf[j]=i;
		for(int j=i+1;j<=min(N,3*i+2*f[i]);++j) if(f[j]>3*f[i]+2) f[j]=3*f[i]+2,of[j]=3,pf[j]=i;
	}
	for(int i=1;i<=N;++i) {
		for(int j=1;i+j<=N&&j<=(i+1)/2;++j) if(g[i+j]>g[i]+f[j]+1) g[i+j]=g[i]+f[j]+1,pg[i+j]=j;
	}
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*R. [P12264] 咏叹调调律 (8.5)

Problem Link

尝试把消除问题和括号匹配建立联系,\(B,C\) 分别对应 \(-2,+1\),而 \(A\) 可以对应 \(+2\)\(-1\),容易发现一定是一个前缀为 \(+2\)

那么序列合法的必要条件是括号化后得到一个合法括号序列,且合法序列唯一对应一个合法括号序列。

但该条件并不充分,因为一个 \(A_L/B\) 对应的两个左括号不能被分进两个不同的子序列。

那么按照优先匹配这些自由度更低的括号得到贪心顺序:\(A_LB>A_LA_RA_R=CCB>CA_R\)

那么栈中维护若干 \(A_L,C\),如果加入一个 \(A_R\),那么优先和 \(A_L\) 匹配。

如果再加入一个 \(B\),那么可能会会拆开一个 \(A_LA_R\) 变成 \(A_LB+CA_R\)

这要求这个 \(A_LA_R\) 生成的时候至少存在一个 \(C\)

注意到当有 \(A_LA_R\) 时加入一个 \(A_R\) 则最优策略就是直接匹配 \(A_LA_RA_R\),因为如果这两个 \(A_R\) 分别匹配 \(CA_R\),那么把占据的两个 \(C\) 拿出来肯定优于一个 \(A_L\)

所以只要栈中记录 \(A_L,C\) 个数,以及当前有没有填 \(A_R\),是否有 \(A_LA_R\),以及生成时是否有 \(C\) 供调整。

时间复杂度 \(\mathcal O(n^3)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=505,MOD=998244353;
inline void add(int&x,const int&y) { x=x+y>=MOD?x+y-MOD:x+y; }
int n,P,Q,R,F[2][MAXN][MAXN][4];//ArB>AlArAr/CCB>CAr
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>P>>Q>>R;
	auto &f=F[0],&g=F[1];
	f[0][0][0]=1;
	for(int o=1,z;o<=n;++o) {
		for(int i=0;i<=o;++i) for(int j=0;i+j<=o;++j) for(int x:{0,1,2,3}) {
			z=0,swap(f[i][j][x],z);
			if(!z) continue;
			int za=1ll*z*P%MOD,zb=1ll*z*Q%MOD,zc=1ll*z*R%MOD;
			if(!x) add(g[i+1][j][0],za);
			if(x==2) add(g[i][j][1],za);
			else if(x==3) add(g[i][j+1][1],za); //swap C out
			else if(i&&j) add(g[i-1][j-1][3],za);
			else if(i) add(g[i-1][0][2],za);
			else if(j) add(g[0][j-1][1],za);
			if(i) add(g[i-1][j][x],zb);
			else if(x==3) add(g[i][j][1],zb);
			else if(j>=2) add(g[i][j-2][x],zb);
			add(g[i][j+1][x],zc);
		}
		swap(f,g);
		cout<<(f[0][0][0]+f[0][0][1])%MOD<<" \n"[o==n];
	}
	return 0;
}
posted @ 2025-12-22 15:21  DaiRuiChen007  阅读(100)  评论(0)    收藏  举报