2026 NOI 做题记录(二十一)

NOI2026 训练(二十一)



Contest Link

\(\text{By DaiRuiChen007}\)



A. [CF2157I] Hyper Smawk Bros (4.5)

Problem Link

\(m\) 是偶数的情况平凡。

简单打表一下发现相邻两个后手必胜的 \(n\) 之差 \(\in\{m+1,m+2\}\)。

尝试分析一下,假设 \(n_0\) 后手必胜,即任意 \((n_0,x)\) 都是后手必胜,则后续大部分状态都是先手必胜,除了 \((n_0+x,x)\) 这些状态。

因此 \(n_0+m+1\) 后手必胜当且节点 \((n_0+\frac{m+1}2,\frac{m+1}2)\) 后手必胜。

考虑如何检验 \((n_0+x,x)\) 是否后手必胜,首先 \(2\nmid x\) 或者 \((n_0+\frac x2,\frac x2)\) 先手必胜,其次找再往前的一个后手必胜位置 \(n_0'\),然后 \(y\gets x+n_0-n_0'\),要求 \(2\nmid y\) 或者 \((n'_0+\frac y2,\frac y2)\) 先手必胜。

注意如果 \(n_0-n_0'=m+2\),则 \((n_0-1,\frac{m+1}2)\) 也是后手必胜的,直接这样递归就能通过,可以证明均摊复杂度是 \(\mathcal O(1)\) 的。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
vector <int> f[N+5];
void solve() {
	int n,m;
	cin>>n>>m;
	if(m&1) cout<<(*lower_bound(f[m].begin(),f[m].end(),n)==n?"NO\n":"YES\n");
	else cout<<(n%(m+1)?"YES\n":"NO\n");
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	for(int m=3;m<=N;m+=2) {
		function<int(int,int)>dp=[&](int i,int x)->int{
			if(!i) return x==m;
			if(x%2==0&&dp(i,x/2)) return 0;
			int y=x+f[m][i]-f[m][i-1];
			if(f[m][i]-f[m][i-1]==m+2&&x==(m-1)/2)  return 0;
			if(y%2==0&&y/2<=m&&y/2!=x&&dp(i-1,y/2)) return 0;
			return 1;
		};
		f[m]={0,m+1};
		while(f[m].back()<=N) f[m].push_back(f[m].back()+m+1+dp(f[m].size()-1,(m+1)/2));
	}
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



B. [CF2196E2] Fuzzy Concatenation (3)

Problem Link

首先 \(t[l,r]\) 合法那么其所有子串合法,所以我们每次只要贪心找最长的合法前缀删掉即可。

由于前缀总长为 \(m\),因此先倍增再二分,则检验的 \(t[l,r]\) 总长为 \(\mathcal O(m\log m)\) 级别。

所以我们枚举被修改字符 \(t_k\),那么其对应字符 \(s_x\) 就要满足 \(s[1,x-1],s[x+1,n]\),在所有前缀、后缀中的字典序排名分别属于一个区间,直接主席树维护。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5.5e5+5;
struct SA {
	char s[MAXN];
	int n,m,sa[MAXN],rk[MAXN],ct[MAXN],t[MAXN],f[20][MAXN];
	void init() {
		m=28,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))]);
		}
	}
	array<int,2>qry(int k,int d) {
		int l=rk[k],r=rk[k];
		for(int i=19;~i;--i) {
			if(l-(1<<i)>=1&&f[i][l-(1<<i)+1]>=d) l-=1<<i;
			if(r+(1<<i)<=n&&f[i][r+1]>=d) r+=1<<i;
		}
		return {l,r};
	}
}	X,Y;
struct Segt {
	int tot,ls[MAXN*20],rs[MAXN*20],ct[MAXN*20];
	void init() {
		for(int i=1;i<=tot;++i) ct[i]=ls[i]=rs[i]=0;
		tot=0;
	}
	void ins(int x,int l,int r,int q,int&p) {
		ct[p=++tot]=ct[q]+1;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(x<=mid) ins(x,l,mid,ls[q],ls[p]),rs[p]=rs[q];
		else ins(x,mid+1,r,rs[q],rs[p]),ls[p]=ls[q];
	}
	bool qry(const int&ul,const int&ur,int l,int r,int q,int p) {
		if(ul>ur||ct[q]>=ct[p]) return 0;
		if(ul<=l&&r<=ur) return 1;
		int mid=(l+r)>>1;
		if(ul<=mid&&qry(ul,ur,l,mid,ls[q],ls[p])) return 1;
		if(mid<ur&&qry(ul,ur,mid+1,r,rs[q],rs[p])) return 1;
		return 0;
	}
}	T;
int n,m,z,rt[MAXN],sx[MAXN],sy[MAXN];
char s[MAXN],t[MAXN];
bool chk(int l,int r) {
	if(l==r) return 1;
	auto x=X.qry(n+1+l+1,r-l);
	if(sx[x[0]-1]<sx[x[1]]) return 1;
	auto y=Y.qry(n+1+m+1-(r-1),r-l);
	if(sy[y[0]-1]<sy[y[1]]) return 1;
	for(int i=l+1;i<r;++i) {
		x=X.qry(n+1+i+1,r-i),y=Y.qry(n+1+m+1-(i-1),i-l);
		if(T.qry(sy[y[0]-1]+1,sy[y[1]],1,n,rt[x[0]-1],rt[x[1]])) return 1;
	}
	return 0;
}
void solve() {
	cin>>n>>m,X.n=Y.n=n+m+1,z=0,T.init();
	for(int i=1;i<=n;++i) cin>>s[i],X.s[i]=Y.s[n-i+1]=s[i];
	for(int i=1;i<=m;++i) cin>>t[i],X.s[n+1+i]=Y.s[n+1+m-i+1]=t[i];
	X.s[n+1]=Y.s[n+1]='{',X.init(),Y.init();
	for(int i=1;i<=n+m+1;++i) sx[i]=sx[i-1]+(1<X.sa[i]&&X.sa[i]<=n),sy[i]=sy[i-1]+(1<Y.sa[i]&&Y.sa[i]<=n);
	for(int i=1;i<=n+m+1;++i) {
		rt[i]=rt[i-1];
		if(3<=X.sa[i]&&X.sa[i]<=n) T.ins(sy[Y.rk[n+1-(X.sa[i]-2)]],1,n,rt[i],rt[i]);
	}
	for(int p=1,q,r,i;p<=m;p=q+1,++z) {
		for(i=1,q=p,r=q+1;r<=m&&chk(p,r);i*=2,q=r,r=min(m+1,q+i));
		for(i=1<<19;i;i>>=1) if(q+i<r&&chk(p,q+i)) q+=i;
	}
	cout<<z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



C. [CF1889F] Doremy's Average Tree (5)

Problem Link

考虑贪心,但是当一个点的祖先中有多个均值相同的点,我们不知道操作哪一个。

讨论一下发现操作深度最大的点可以让后续操作更简单,但是无法很好的处理操作次数限制。

而操作深度最小的点就可以最小化操作次数限制,但是可能覆盖一些其他的点 \(v\),满足把这次操作的点往下调整可以让 \(v\) 匹配一个更优的点。

这个反悔的结构很简单,因为是建立在最小字典序贪心上的,所以每次调整的策略都是唯一的。

当我们加入一个 \(u\) 时,找到祖先中被操作过的点,然后贪心地调整到子树中不覆盖 \(u\) 且权值相同并且深度最小的若干个点上,可以直接简单贪心。

枚举几种操作判断哪个更优即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5005;
int n,rt,m,dfn[MAXN],efn[MAXN],rk[MAXN],dc,fa[MAXN],dsu[MAXN];
vector <int> G[MAXN];
struct frac { ll x,y; } a[MAXN],b[MAXN],f[MAXN];
ll operator-(const frac&u,const frac&v) { return u.x*v.y-v.x*u.y; }
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs(int u,int fz) {
	dfn[u]=++dc,rk[dc]=u,fa[u]=fz;
	for(int v:G[u]) if(v^fz) dfs(v,u),b[u].x+=b[v].x,b[u].y+=b[v].y;
	efn[u]=dc;
}
bool in[MAXN];
void solve() {
	cin>>n>>rt>>m,dc=0;
	for(int i=1;i<=n;++i) cin>>a[i].x,a[i].y=1,b[i]=f[i]=a[i],G[i].clear(),dsu[i]=i,in[i]=0;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs(rt,0);
	for(int x=1;x<=n;++x) {
		int h=0,p=0;
		for(int y=find(x);y;y=find(fa[y])) {
			if(in[y]) p=y;
			if(!h||b[y]-b[h]<=0) h=y;
		}
		if(!p) {
			if(m&&h&&b[h]-a[x]<0) in[h]=1,--m,f[x]=b[h];
			else f[x]=a[x];
		} else if(min(b[h]-b[p],a[x]-b[p])>=0) f[x]=b[p];
		else {
			vector <int> g;
			for(int i=dfn[p],o;i<=efn[p];++i) if((o=rk[i])<x&&a[o]-f[o]!=0) {
				int c=0;
				for(int y=find(o);y&&(efn[y]<dfn[x]||dfn[x]<dfn[y]);y=find(fa[y])) c=y; 
				g.push_back(c),i=efn[c];
				if(!c||(int)g.size()>m+1) { g.clear(); break; }
			}
			if(g.size()) {
				if(b[h]-a[x]<0&&(int)g.size()<=m) {
					in[h]=1,in[p]=0,f[x]=b[h],m-=g.size();
					for(int c:g) in[c]=1;
				} else if(a[x]-b[p]<0) {
					in[p]=0,f[x]=a[x],m-=g.size()-1;
					for(int c:g) in[c]=1;
				} else f[x]=b[p];
			} else f[x]=b[p];
		}
		for(int y=find(x);y;y=find(fa[y])) if(b[y]-f[x]!=0) dsu[y]=fa[y];
	}
	cout<<count(in+1,in+n+1,1)<<"\n";
	for(int i=1;i<=n;++i) if(in[i]) cout<<i<<" ";
	cout<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



D. [CF1784E] Infinite Game (3.5)

Problem Link

简单分讨一下发现走一圈后只会回到 \(s_1,s_2,s_3\) 中的一个字符前面,那么我们直接 dp 维护从这三个字符出发,路径上的权值和以及会回到哪个字符,要求就是得到的基环内向树的环上权值和为 \(0\)。

但是此时状态数 \(n^4\),不过注意到我们只关心一个环上的权值,所以我们直接枚举这个环的结构,由于只有三个点所以只有 \(2^3-1\) 种,然后就只用对于每种情况记录环上元素之和了。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
void add(int&x,const int&y) { x=x+y>=MOD?x+y-MOD:x+y; }
int n,Z[3],B[8],f[2][7][4][27][625];
char s[205];
void solve() {
	memset(f,0,sizeof(f));
	for(int o=1;o<8;++o) {
		B[o]=(n/2+1)*__builtin_popcount(o);
		f[1][o-1][(s[0]-'a')*2+(s[1]-'a')][s[0]==s[1]?23:21][B[o]+(o%2&&s[0]==s[1]?(s[0]=='a'?1:-1):0)]=1;
	}
	for(int i=2;i<=n+1;++i) {
		memset(f[i&1],0,sizeof(f[i&1]));
		for(int S:{0,1,2,3}) for(int P=0;P<27;++P) for(int C:{0,1}) if(s[i]=='?'||s[i]=='a'+C) {
			int p[3]={P%3,P/3%3,P/9},v[3]={0,0,0};
			for(int o:{0,1,2}) {
				if(p[o]==0||(p[o]==1&&S%2==C)) p[o]=2,v[o]=C?-1:1;
				else --p[o];
			}
			for(int o=1;o<8;++o) {
				int *L=f[(i-1)&1][o-1][S][P],*R=f[i&1][o-1][S%2*2+C][p[0]+p[1]*3+p[2]*9],d=o%2*v[0]+o/2%2*v[1]+o/4*v[2];
				for(int j=0;j<=2*B[o];++j) if(L[j]) add(R[j+d],L[j]);
			}
		}
	}
	for(int P=0;P<27;++P) {
		int p[3]={P%3,P/3%3,P/9},o=0,u=p[p[p[0]]];
		while(!(o>>u&1)) o|=1<<u,u=p[u];
		for(int j=0;j<=2*B[o];++j) for(int S:{0,1,2,3}) add(Z[(j<B[o])+(j<=B[o])],f[(n+1)&1][o-1][S][P][j]);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>s,n=strlen(s);
	if(n==1) return cout<<(s[0]!='b')<<"\n0\n"<<(s[0]!='a')<<"\n",0;
	char l=s[0],r=s[1];
	for(auto x:{'a','b'}) if(l=='?'||l==x) for(auto y:{'a','b'}) if(r=='?'||r==y) {
		s[0]=s[n]=x,s[1]=s[n+1]=y,solve();
	}
	cout<<Z[0]<<"\n"<<Z[1]<<"\n"<<Z[2]<<"\n";
	return 0;
}



*E. [CF1876E] Ball-Stackable (7)

Problem Link

我们可以按照每条边的朝向确定每个点的深度(\(d_v=d_u+1\)),可能匹配的括号满足深度相同,且中间的点深度都大于端点,可以按深度排序加入来确定每个点的颜色。

然后考虑怎么构造,从子树中向上维护构造,观察到能和外界匹配的边一定从下到上能走到根,也就是从上到下路径的前缀最小值。

注意到如果 \(u\) 有两个子树,最小深度分别为 \(d_u-x,d_u-y\),那么 \(\forall i\le \min(x,y)\),两子树中深度为 \(d_u-i\) 的边可以相互匹配。

所以每次操作后最小深度较大的一侧上的所有边都会和另一侧的边同色,所以每个 \(u\) 子树内只剩 \(d_u-\min d\) 个括号, \(u\) 子树可以等价于一条这么长的向上的链。

此时如果父边为 \(u\to fa_u\) 相当于把链长加一,否则相当于把答案加一并把链长减一。

可以发现答案是外向边数加上 \(-\min d\),枚举 \(d_x=\min d\),那么 \(rt\to x\) 路径上的边尽可能向根,其他边尽可能向外,答案就是这两种边数之和,直接换根 dp 算一下,求出 \(x\) 后用上面的办法构造。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,ot[MAXN],d[MAXN],vs[MAXN],cl[MAXN],dsu[MAXN];
array<int,2>Z={0,0};
struct Edge { int v,w; };
vector <Edge> G[MAXN],E[MAXN];
void dfs1(int u,int fz) {
	for(auto e:G[u]) if(e.v!=fz) dfs1(e.v,u),ot[u]+=ot[e.v]+(e.w>=0);
}
void dfs2(int u,int fz,int w) {
	Z=max(Z,array<int,2>{w+ot[u],u});
	for(auto e:G[u]) if(e.v!=fz) dfs2(e.v,u,w+ot[u]-ot[e.v]-(e.w>=0)+(e.w<=0));
}
void dfs3(int u,int fz) {
	for(auto e:G[u]) if(e.v!=fz) {
		if(!e.w) e.w=1;
		d[e.v]=d[u]+e.w;
		if(e.w>0) E[d[u]].push_back({u,e.v});
		else E[d[e.v]].push_back({e.v,u});
		dfs3(e.v,u);
	}
}
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;++i) cin>>u>>v>>w,G[u].push_back({v,w}),G[v].push_back({u,-w});
	dfs1(1,0),dfs2(1,0,0),dfs3(Z[1],0),cout<<Z[0]<<"\n";
	for(int i=1;i<=n;++i) dsu[i]=i,vs[i]=n+1;
	for(int o=n;o>=0;--o) {
		for(auto e:E[o]) {
			int x=find(e.w);
			if(vs[x]>o) vs[x]=o,cl[x]=++m;
			cout<<e.v<<" "<<e.w<<" "<<cl[x]<<"\n";
		}
		for(auto e:E[o]) dsu[find(e.v)]=find(e.w);
	}
	return 0;
}



F. [CF1876F] Indefinite Clownfish (4)

Problem Link

枚举两条路径相交的位置 \((v,v+1)\)。

可以证明我们只要找相邻的两个 \(a_l=a_r=v\),然后递减序列从 \(l\) 开始向左右匹配,递增序列从 \(r\) 向右匹配,为了保证不经过 \(l\),从 \(l\) 开始向左匹配。

交换 \(l,r\)、递增递减序列有四种情况,可以证明包含了所有的解,分别处理。

设递增序列左右的长度为 \(a,b\),递减序列左右的长度为 \(c,d\),那么 \(b-a=c-d,a+b+c+d=k-2\),可以推出 \(b+d=a+c=\frac{k-2}2\)。

我们发现 \(b,d\) 恰好都是左侧长度,\(a,c\) 恰好都是右侧长度,所以两个问题是独立的,可以分别解决,枚举 \(b,d\) 哪个更小,二分并用倍增判断即可。

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

代码:

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=2e5+5;
int n,m,z,a[MAXN],b[MAXN],fl[18][MAXN],fr[18][MAXN],gl[18][MAXN],gr[18][MAXN];
int up(int(*f)[MAXN],int x,int k) {
	for(int i=17;~i;--i) if(k>>i&1) x=f[i][x];
	return x;
}
int sol(int(*f)[MAXN],int(*g)[MAXN],int x,int y) {
	int p=0;
	for(int t=1<<17;t;t>>=1) if(p+t<=m&&up(f,x,m-t-p)<=up(g,y,t+p)) p+=t;
	int s=min(up(f,x,m-p),up(g,y,p));
	if(p<m) s=max(s,up(g,y,p+1));
	return s>0?s:-n;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,z=n+1;
	for(int i=1;i<=n;++i) cin>>a[i];
	if(m&1) return cout<<"-1\n",0;
	for(int i=1;i<=n;++i) {
		fl[0][i]=b[a[i]-1],gl[0][i]=b[a[i]+1],b[a[i]]=i;
		for(int k=1;k<18;++k) fl[k][i]=fl[k-1][fl[k-1][i]],gl[k][i]=gl[k-1][gl[k-1][i]];
	}
	fill(b,b+n+2,0);
	for(int i=1;i<=n;++i) {
		fr[0][i]=b[a[n-i+1]+1],gr[0][i]=b[a[n-i+1]-1],b[a[n-i+1]]=i;
		for(int k=1;k<18;++k) fr[k][i]=fr[k-1][fr[k-1][i]],gr[k][i]=gr[k-1][gr[k-1][i]];
	}
	fill(b,b+n+2,0),m=m/2-1;
	for(int i=1,j;i<=n;++i) {
		if(b[a[i]]) {
			j=b[a[i]],z=min(z,n+1-sol(fr,gr,n-i+1,n-i+1)-max(sol(fl,gl,j,i),sol(fl,gl,i,j)));
			z=min(z,n+1-sol(fl,gl,j,j)-max(sol(fr,gr,n-j+1,n-i+1),sol(fr,gr,n-i+1,n-j+1)));
		}
		b[a[i]]=i;
	}
	cout<<(z>n?-1:z)<<"\n";
	return 0;
}



G. [CF2206A] Compare Suffixes (4.5)

Problem Link

记 \(t_i=s[i,n]\) 倒序加入每个后缀 \(t_i\),考虑如何优化 \(\log n\) 次的二分。

如果我们知道 \(s_i,s_j\) 的大小关系就只要比较 \(t_{i+1},t_{j+1}\) 了,这个不需要询问就能解决。

考虑如何通过 \(t_{i+1}\sim t_n\) 的后缀之间的大小关系确定 \(s\) 中的字符,考虑把所有后缀按顺序排列,如果 \(t_x<t_y,t_{x+1}>t_{y+1}\) 说明 \(s_x<s_y\)。

所以按照 \(t_{x+1}\) 可以把后缀数组分成 \(\le|\Sigma|\) 个递增段,不同段的后缀开头必定不同,但段内每个字符的开头也可能不同(递增)。

在段间二分求出每个 \(t_i\) 属于哪个段,然后直接用 \(t_{i+1}\) 的排名算出一个预期位置,检验是否正确,如果不正确则重新二分。

对于每个后缀检验位置的询问次数是 \(\log |\Sigma|+2\),而重新二分的复杂度 \(\log n\),但是最多进行 \(|\Sigma|\) 次因为每次都会新增一个段。

需要优化一次询问,注意到检验时需要同时判断左右两侧的后缀是否有 \(t_l<t_i<t_r\)。

考虑在二分属于哪个段的时候,我们直接算出如果 \(t_i\) 落在这个段中则按 \(t_{i+1}\) 应该属于哪个位置,直接在这些位置的上二分 \(t_l<t_i\),则只要检验最终位置是否有 \(t_i<t_r\)。

交互次数 \(\le n(\log|\Sigma|+1)+|\Sigma|\log n=6260\)。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005;
bool cmp(int x,int y) { string o; return cout<<"query "<<x<<" "<<y<<endl,cin>>o,o=="first"; }
int n,a[MAXN],b[MAXN],c[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,a[1]=n,b[n]=1;
	for(int i=n-1;i>=1;--i) {
		vector <int> s;
		for(int j=1,k=1;j<=n-i;++j) {
			c[j]=b[a[j]+1];
			if(j>1&&c[j]<c[j-1]) s.push_back(lower_bound(c+k,c+j,b[i+1])-c),k=j;
			if(j==n-i) s.push_back(lower_bound(c+k,c+j+1,b[i+1])-c);
		}
		s.erase(unique(s.begin(),s.end()),s.end());
		int p=s[0]>1?-1:0;
		for(int k=16;k;k>>=1) if(p+k<(int)s.size()&&cmp(a[s[p+k]-1],i)) p+=k;
		if(p<0||(s[p]<=n-i&&cmp(a[s[p]],i))) {
			p=n-i+1;
			for(int k=512;k;k>>=1) if(p-k>0&&cmp(i,a[p-k])) p-=k;
		} else p=s[p];
		for(int j=n-i;j>=p;--j) ++b[a[j+1]=a[j]];
		a[p]=i,b[i]=p;
	}
	cout<<"answer";
	for(int i=1;i<=n;++i) cout<<" "<<a[i];
	cout<<endl;
	return 0;
}



*H. [CF2061I] Kevin and Nivek (8)

Problem Link

显然二维 dp 记录 \(f_{i,j}\) 表示 \(1\sim i\) 赢了 \(j\) 次的最小代价,考虑从 \(f_{l,i}\to f_r\) 时,如果 \(i\ge \lceil r/2\rceil\),那么每个 \(a=-1\) 的位置都必赢,同理如果 \(i+r-l<\lceil r/2\rceil\),则每个 \(a=-1\) 的位置都必输。

所以只有 \(\mathcal O(r-l)\) 个 \(f_{l,i}\) 有特殊的状态,而对于两侧的 \(f_{l,i}\),我们只要转移 \(h_{i+j}=\min_j(f_i+g_j)\),其中 \(q_j\) 是 \(a\ne -1\) 的元素中前 \(j\) 小值之和,显然具有凸性,从而转移函数 \(w(i,i+j)\) 满足四边形不等式,从而可以分治优化决策单调性的转移。

此时 \(f_{l}\to f_r\) 的复杂度是 \(\mathcal O((r-l)^2+n\log n)\),如果分块转移 \(f_{kB}\to f_{(k+1)B}\) 则可以做到 \(\mathcal O(n\sqrt{n\log n})\)。

但可以做到更优,分块能解决的问题尝试分治,如果我们要解决 \(f_{l}\to f_r\) 的转移,那么我们对于两侧的 \(f_{l,i}\) 直接分治转移,中间的 \(\mathcal O(r-l)\) 个 \(f_i\),我们递归成 \(f_{l}\to f_{mid}\to f_r\) 来转移。

那么分治解决的问题形如 \(f_{x,[l_x,r_x]}\to f_{y,[l_y,r_y]}\),初始 \(x=l_x=r_x=l_y=0,y=r_y=n\),转移时会取 \(z=\frac{x+y}2\),并且 \(r_z-l_z=\mathcal O(y-x)\),然后分成 \(x\to z,z\to y\) 两部分处理。

显然 \(\sum r_i-l_i=\mathcal O(n\log n)\),算上决策单调性优化的复杂度为 \(\mathcal O(n\log^2n)\)。

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

代码:

#include<bits/stdc++.h> 
#define ll long long
using namespace std;
const int MAXN=3e5+5;
void chkmin(ll&x,const ll&y) { x=y<x?y:x; }
int n,m,a[MAXN],L[MAXN],R[MAXN];
ll F[MAXN*60],*f[MAXN],b[MAXN];
void cdq(ll*g,ll*h,int s,int t,int l,int r,int d) {
	if(l>r) return ;
	int p=(l+r)>>1,o=-1; ll z=1e18;
	for(int i=max(s,p-d);i<=min(t,p);++i) if(g[i]+b[p-i]<z) z=g[i]+b[p-i],o=i;
	chkmin(h[p],z),cdq(g,h,s,o,l,p-1,d),cdq(g,h,o,t,p+1,r,d);
}
void DP(int l,int r) {
	if(R[r]<0) L[r]=L[l],R[r]=R[l]+r-l,f[r]=F+m-L[r],m+=R[r]-L[r]+1;
	if(r-l==1) {
		for(int i=L[l];i<=R[l];++i) {
			chkmin(f[r][i],f[l][i]);
			if(~a[r]||2*i>=l) chkmin(f[r][i+1],f[l][i]+max(0,a[r]));
		}
		return ;
	}
	int x=min(max(L[l],(r+1)/2-(r-l)),R[l]+1),y=max(min(R[l],(r+1)/2-1),L[l]-1),q=0;
	for(int i=l+1;i<=r;++i) if(~a[i]) b[++q]=a[i];
	sort(b+1,b+q+1);
	for(int i=1;i<=q;++i) b[i]+=b[i-1];
	if(L[l]<x) cdq(f[l],f[r],L[l],x-1,L[l],x-1+q,q);
	if(y<R[l]) {
		for(int i=r-l;i>=0;--i) b[i]=b[max(0,i-(r-l-q))];
		cdq(f[l],f[r],y+1,R[l],y+1,R[l]+r-l,r-l);
	}
	if(x<=y) {
		int p=(l+r)>>1;
		L[l]=x,R[l]=y,DP(l,p),DP(p,r);
	}
}
void solve() {
	cin>>n,m=1,F[0]=0,f[0]=F,L[0]=R[0]=0;
	for(int i=1;i<=n;++i) cin>>a[i],L[i]=R[i]=-1;
	DP(0,n);
	for(int k=0;k<=n;++k) cout<<f[n][k]<<" \n"[k==n];
	memset(F,0x3f,8*m);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	memset(F,0x3f,sizeof(F));
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



I. [CF1787H] Codeforces Scoreboard (3)

Problem Link

首先把代价减去 \(a_i\),变成选择若干个 \(b-kt\) 使和最大,显然我们会按 \(k\) 升序排列,那么按这个顺序 dp,\(f_{i,j}=\min(f_{i-1,j},f_{i-1,j-1}+b-kj)\)。

显然是 dp 维护差分的问题,可以证明答案具有凸性且每次被影响的都是一段后缀。

考虑对差分数组 \(d\) 的影响,即找到首个 \(d_i\le b-ki\),然后把 \(d[i,m]\) 向后平移一位并且整体 \(-i\),最后插入 \(d_i=b-ki\),直接平衡树维护。

证明这两个结论只需证明所有的 \(d_i-d_{i+1}\ge k\),容易归纳。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
mt19937 rnd(time(0));
int n,rt,pr[MAXN],ls[MAXN],rs[MAXN],sz[MAXN];
ll ad[MAXN],vl[MAXN],su;
void adt(int p,ll k) { if(p) ad[p]+=k,vl[p]+=k; }
void psd(int p) { if(ad[p]) adt(ls[p],ad[p]),adt(rs[p],ad[p]),ad[p]=0; }
void psu(int p) { sz[p]=sz[ls[p]]+1+sz[rs[p]]; }
void split(int p,ll b,ll t,int&x,int&y) {
	if(!p) return x=y=0,void();
	psd(p); ll c=b-t*(sz[ls[p]]+1);
	if(vl[p]>c) x=p,split(rs[p],c,t,rs[x],y),psu(x);
	else y=p,split(ls[p],b,t,x,ls[y]),psu(y);
}
int merge(int x,int y) {
	if(!x||!y) return x|y;
	psd(x),psd(y);
	if(pr[x]<pr[y]) return rs[x]=merge(rs[x],y),psu(x),x;
	else return ls[y]=merge(x,ls[y]),psu(y),y;
}
void dfs(int x) {
	if(!x) return ;
	psd(x),dfs(ls[x]),su+=max(0ll,vl[x]),dfs(rs[x]);
}
array<ll,2>a[MAXN];
void solve() {
	cin>>n,su=0,rt=0;
	for(int i=1,x,y,z;i<=n;++i) cin>>x>>y>>z,a[i]={x,y-z},su+=z;
	sort(a+1,a+n+1,greater<>());
	for(int i=1,x,y;i<=n;++i) {
		split(rt,a[i][1]-a[i][0],a[i][0],x,y);
		sz[i]=1,ls[i]=rs[i]=0,pr[i]=rnd(),ad[i]=0;
		vl[i]=a[i][1]-a[i][0]*(sz[x]+1),adt(y,-a[i][0]);
		rt=merge(merge(x,i),y);
	}
	dfs(rt),cout<<su<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



J. [CF1726G] A Certain Magical Party (4)

Problem Link

简单分析一下,如果我们知道最后的值 \(k\),那么限制就是 \(a_i\) 操作时有多少个 \(<a_i\) 或 \(\le a_i\) 的被操作了,直接按 \(a\) 升序计算插入的方案数就行。

算 \(k\) 的值时我们只要考虑首个被操作的 \(\min a\) 就行。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e5+5,MOD=998244353;
int n,a[MAXN],b[MAXN],c0[MAXN],c1[MAXN],ct[MAXN],fc[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=fc[0]=1;i<=n;++i) fc[i]=1ll*fc[i-1]*i%MOD;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) cin>>b[i],(b[i]?++c1[a[i]]:++c0[a[i]]);
	for(int i=1;i<=2*n;++i) ct[i]=ct[i-1]+c0[i]+c1[i];
	int mn=*min_element(a+1,a+n+1);
	if(ct[mn]==n) return cout<<fc[n]<<"\n",0;
	if(c0[mn]||c1[mn]>1) return cout<<"0\n",0;
	int K=mn+n-ct[mn],z=1,s=1;
	for(int i=mn+1;i<=2*n;++i) {
		if(c0[i]) {
			if(K<i||K>i+s) return cout<<"0\n",0;
			z=1ll*z*fc[c0[i]]%MOD,s+=c0[i];
		}
		if(c1[i]) {
			if(K==i) {
				if(ct[i]!=n) return cout<<"0\n",0;
				for(int j=1;j<=c1[i];++j) z=1ll*z*(++s)%MOD;
			} else if(c1[i]>1||K<i+n-ct[i]||K>i+n-ct[i]+s) return cout<<"0\n",0;
			else ++s;
		}
	}
	cout<<z<<"\n";
	return 0;
}



*K. [CF2207G] Toothless (7)

Problem Link

显然考虑鸽巢原理,在没有必须格子时,构造 \(S_c=\{(x,y)\mid (x+y)\bmod 3\ne c\}\) 即可。

现在钦定了一些位置必须被选,那么直接调整,把产生的环上的一个普通格子删掉即可,可以证明这样的操作一定成功,实现的时候只要先加入特殊格子和必须格子,最后加入普通格子。

注意到每个必须格子最多删掉三个普通格子,所以 \(\sum S_c\) 最多减少 \(2ak\),其中 \(k\) 是必须格子数量,一定不小于 \(2P\)。

时间复杂度 \(\mathcal O(nm)\)。

代码:

#include<bits/stdc++.h>
using namespace std;
const int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
int n,m,A,B,q,dsu[2005],id[2005][2005],S,Z;
bool in[2005];
char a[2005][2005];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
int add(int x,int y) {
	vector <int> h;
	for(int i:{0,1,2,3}) if(in[id[x+dx[i]][y+dy[i]]]) h.push_back(find(id[x+dx[i]][y+dy[i]]));
	for(int i=0;i<(int)h.size();++i) for(int j=i+1;j<(int)h.size();++j) if(h[i]==h[j]) return 0;	
	in[id[x][y]]=1; for(int o:h) dsu[find(o)]=id[x][y];
	return a[x][y]=='x'?B:A;
}
void dfs(int x,int y) {
	cout<<x<<" "<<y<<"\n",in[id[x][y]]=0;
	for(int i:{0,1,2,3}) if(in[id[x+dx[i]][y+dy[i]]]) dfs(x+dx[i],y+dy[i]);
}
void solve() {
	cin>>n>>m>>A>>B,S=q=0;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		cin>>a[i][j],id[i][j]=++q,S+=(a[i][j]=='.'?A:0)+(a[i][j]=='x'?B:0);
	}
	for(int k:{0,1,2}) {
		memset(in,0,sizeof(in)),iota(dsu+1,dsu+q+1,1),Z=0;
		for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(a[i][j]=='#') Z+=add(i,j);
		for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(a[i][j]=='x'&&(i+j)%3!=k) Z+=add(i,j);
		for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(a[i][j]=='.'&&(i+j)%3!=k) Z+=add(i,j);
		if(3*Z<2*S) continue;
		cout<<count(in+1,in+q+1,1)<<"\n";
		for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(in[id[i][j]]) dfs(i,j);
		return ;
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) {
		solve();
		for(int i=1;i<=n;++i) memset(id[i],0,4*m+4);
	}
	return 0;
}



L. [CF1930I] Counting Is Fun (5)

Problem Link

记 \(s_i\) 为 \(\pm 1\) 前缀和,容斥 \(i\) 非法相当于 \(s[0,i]<s[i+1,n]\) 或者 \(s[0,i]>s[i+1,n]\),根据 \(p_i\) 决定,那么我们只会同时选择若干 \(p=0\) 或 \(p=1\) 的位置。

对于中间的每一个段,我们要 \(f_x\) 算有多少序列满足 \(s_0\le s_i\le s_x\),对每个 \(s_x\) 算答案。

枚举 \(s_x\) 后双反射容斥,可以用 \(\mathcal O(n\log n)\) 的时间把问题变成所有路径的 \(\sum w_{s_x}\),即 \(f_k=[x^0]W(x)\times( x+x^{-1})^k\)。

可以分块,但尝试用分治优化,记录 \(F_k=W(x)\times (x+x^{-1})^k\),如果要算 \(f_u\sim f_{u+d}\),则只需要 \(F_u\) 的 \(x^{-d}\sim x^d\) 系数。

那么我们定义分治 \((l,r)\) 的过程就是已知 \(F_l\) 的 \(l-r\sim r-l\) 系数算出 \(f_l\sim f_r\),只要取 \(p=\frac {l+r}2\),然后 NTT 算出 \(F_p\) 并递归 \((l,p-1),(p,r)\) 即可。

容斥时再 CDQ 分治优化一下 dp。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<19,MAXN=1e5+5;
int fac[N],ifac[N],inv[N];
int C(int x,int y) { return y<0||y>x?0:1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) s=1ll*s*a%MOD; return s; }
namespace P {
int rev[N],w[N<<1];
void poly_init() {
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
	for(int k=1;k<=N;k<<=1) {
		int x=ksm(3,(MOD-1)/k); w[k]=1;
		for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
	}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y;  }
void ntt(int *f,bool idft,int n) {
	for(int i=0;i<n;++i) {
		rev[i]=(rev[i>>1]>>1);
		if(i&1) rev[i]|=n>>1;
	}
	for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
	for(int k=2,x,y;k<=n;k<<=1) {
		for(int i=0;i<n;i+=k) {
			for(int j=i;j<i+k/2;++j) {
				x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
				f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
			}
		}
	}
	if(idft) {
		reverse(f+1,f+n);
		for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
	}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
	static int a[N],b[N];
	for(int i=0;i<n;++i) a[i]=f[i];
	for(int i=0;i<m;++i) b[i]=g[i];
	int len=plen(n+m-1);
	ntt(a,0,len),ntt(b,0,len);
	for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
	ntt(h,1,len);
	memset(a,0,sizeof(int)*len);
	memset(b,0,sizeof(int)*len);
}
}
int n,m,a[N],b[N],c[N],F[MAXN*100],*f[MAXN],g[MAXN],h[MAXN],dp[MAXN];
bool st[MAXN];
void cdq(int u,int v) {
	if(u==v) return ;
	int p=(u+v)/2+1,d=v-u,e=p-u,s=v-p;
	for(int i=0;i<=2*d;++i) a[i]=f[u][i-d];
	for(int i=0;i<=e;++i) b[2*i]=C(e,i);
	P::poly_mul(a,b,c,2*d+1,2*e+1);
	f[p]=F+m+s,m+=2*s+1;
	for(int i=-s;i<=s;++i) f[p][i]=c[i+d+e];
	g[p]=f[p][0],cdq(u,p-1),cdq(p,v);
}
void DP(int l,int r) {
	if(l==r) return ;
	int p=(l+r)>>1;
	DP(l,p);
	for(int i=l;i<=p;++i) a[i-l]=st[i]?MOD-dp[i]:0;
	P::poly_mul(a,g,b,p-l+1,r-l);
	for(int i=p+1;i<=r;++i) if(st[i]) dp[i]=(dp[i]+b[i-l-1])%MOD;
	DP(p+1,r);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,P::poly_init();
	for(int i=1;i<=n;++i) { char o; cin>>o,st[i]=o-'0'; }
	f[0]=F+n,m=2*n+1;
	for(int p=1;p<=n;++p) {
		for(int x=p,o=0;-n<=x&&x<=n;x=(o&1?2*p+2:-2)-x,o^=1) f[0][x]+=o&1?-1:1;
		for(int x=p,o=0;-n<=x&&x<=n;x=(o&1?-2:2*p+2)-x,o^=1) f[0][x]+=o&1?-1:1;
		--f[0][p];
	}
	for(int i=-n;i<=n;++i) if(f[0][i]<0) f[0][i]+=MOD;
	g[0]=h[0]=1,cdq(0,n);
	for(int i=1;i<=n;++i) h[i]=(2ll*h[i-1]+(i&1?C(i-1,i/2+1)+MOD-C(i-1,i/2):0))%MOD;
	int z=ksm(2,n);
	for(int o:{0,1}) {
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;++i) if(st[i]) dp[i]=MOD-h[i-1];
		DP(1,n);
		for(int i=1;i<=n;++i) if(st[i]) z=(z+1ll*h[n-i]*dp[i])%MOD;
		for(int i=1;i<=n;++i) st[i]^=1;
	}
	cout<<z<<"\n";
	return 0;
}



M. [CF2205G] Simons and Diophantus Equation (3.5)

Problem Link

首先莫反变成对于每个 \(d\) 算 \(d\mid\gcd(i\oplus j,j\oplus k)\) 的方案数,枚举 \(j\) 后 \(i,k\) 选法独立,枚举 \(k\) 并把 $j\oplus kd\le m $ 的 \(j\) 的 \(f_j\) 加一,计算 \(\sum f_j^2\) 即为所求。

在 Trie 上进行这个过程,把子树 \(1\) 标记 pushdown 到无标记位置上求和即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1<<20|5;
int n,m,k,sz[MAXN],w[MAXN]; bool in[MAXN];
ll f[MAXN],z;
void solve() {
	cin>>n>>m,z=0,k=__lg(m+1)+1,memset(f,0,sizeof(f));
	for(int x=0;x<=m;++x) for(int u=1,i=k-1;~i;--i) u=u<<1|(x>>i&1),++sz[u];
	for(int g=(1<<k)-1;g>=1;--g) {
		f[g]=-m-1;
		for(int x=0;x<(1<<k);x+=g) {
			for(int u=1,i=k-1;~i;--i) {
				in[u]=1,u=u<<1|(x>>i&1);
				if((m+1)>>i&1) ++w[u],u^=1;
			}
		}
		function<void(int)>dfs=[&](int u) {
			if(in[u]) w[u<<1]+=w[u],w[u<<1|1]+=w[u],w[u]=in[u]=0,dfs(u<<1),dfs(u<<1|1);
			else f[g]+=1ll*w[u]*w[u]*sz[u],w[u]=0;
		};
		dfs(1);
		for(int x=2*g;x<(1<<k);x+=g) f[g]-=f[x];
		if(n%g==0) z+=f[g];
	}
	cout<<z<<"\n",memset(sz,0,8<<k);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*N. [CF2147I2] Longest Increasing Path (8)

Problem Link

首先可以想到 Zig-Zag 序列,依次经过 \(l,r,l-1,r+1,l-2,r+2,\cdots\),把每个点变成 \(b\) 个可以把一个 \(n,m\) 较小的序列放大。

考虑如何构造一个 \(n,m\) 较小的序列,取 \(2^0\sim 2^k\),然后按 \((i,j)\) 的顺序依次访问所有 \(i>j+1\) 的点对。

在 \(2^i,2^j\) 之间的 Zig-Zag 序列构造之后我们会到达 \(2^i+b\),我们需要回到 \(2^i\) 上,直接在右侧放一个中转点即可。

由于 \((i,j)\) 之间已经放了 \(\mathcal O(b)\) 条边,因此再加一个点中转不太影响,这样就构造就能满足题意。

更优的做法考虑取出 \(k=\frac{2^i-2^j-b}2\),然后可以 \(2^i+t\to p+1\to 2^j+b-t\to p+2\to 2^i+t+5\),用两个点让序列长度翻倍,这是很优秀的。

需要对坐标进行一定的缩放,取 \(k=45,b=268\) 可以构造出长度 \(\ge10^6\) 的序列。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll V=1e18,K=45,B=268;
ll a[K+5][B+5],n,m;
void out(ll x) {
	cout<<x<<" ";
	if(!--n) cout<<"\n",exit(0);
}
signed main() {
	cin>>n>>m;
	if(n==8) return cout<<"1 1 3 6 10 3 11 1\n",0;
	for(int i=1;i<=K;++i) for(int j=0;j<=B;++j) a[i][j]=(1ll<<(i+15))+j*5;
	for(int i=3;i<=K;++i) for(int j=i-2;j>=1;--j) {
		ll p=(a[j][B]+a[i][0])/2; out(a[i][0]);
		for(int k=0;k<B;++k) out(p+1),out(a[j][B-k]),out(p+2),out(a[i][k+1]);
		out(a[i][B]*2-p-1);
	}
	return 0;
}



O. [CF1696G] Fishingprince Plays With Array Again (5)

Problem Link

设 \(x>y\) 线性规划对偶后变成 \(xf_i+yf_{i+1}\le 1,xf_{i+1}+yf_i\le 1,\max \sum a_if_i\)。

对于这种线性规划问题我们猜测所有点都在不能调整的极限上,对于 \(n=2\) 的情况,一定有 \(f_i\in\{0,\frac 1{x+y},\frac 1x\}\),尝试证明一般情况下该条件也满足。

我们把 \(\max(xf_i+yf_{i+1},xf_{i+1}+yf_i)=1\) 的限制连起来,如果图上有连通块无环,那么对奇偶位置分别正负微调一定更优,所有每个连通块都有环,从而可以推出刚才的结论。

然后动态 dp 维护即可。

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

代码:

#include<bits/stdc++.h>
#define LL __int128
using namespace std;
const int N=1<<18;
int n,q,X,Y;
struct Mat {
	LL f[3][3];
	Mat() { memset(f,0,sizeof(f)); }
	friend Mat operator *(const Mat&u,const Mat&v) {
		Mat w;
		for(int x:{0,1,2}) for(int y:{0,1,2}) for(int z:{0,1,2}) if(y+z<=2) for(int t:{0,1,2}) {
			w.f[x][t]=max(w.f[x][t],u.f[x][y]+v.f[z][t]);
		}
		return w;
	}
};
Mat tr[N<<1|5];
void upd(int x,LL z) {
	tr[x+=N]=Mat(),tr[x].f[1][1]=X*z,tr[x].f[2][2]=(X+Y)*z;
	for(x>>=1;x;x>>=1) tr[x]=tr[x<<1]*tr[x<<1|1];
}
void qry(int l,int r) {
	Mat sl,sr;
	for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
		if(~l&1) sl=sl*tr[l^1];
		if(r&1) sr=tr[r^1]*sr;
	}
	LL z=0; sl=sl*sr;
	for(int x:{0,1,2}) for(int y:{0,1,2}) z=max(z,sl.f[x][y]);
	cout<<fixed<<setprecision(20)<<(long double)z/X/(X+Y)<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q>>X>>Y; if(X<Y) swap(X,Y);
	for(int i=1,w;i<=n;++i) cin>>w,upd(i,w);
	for(int o,x,y;q--;) cin>>o>>x>>y,o==1?upd(x,y):qry(x,y);
	return 0;
}



P. [CF1762G] Unequal Adjacent Elements (5)

Problem Link

首先猜测无解当且节点存在绝对众数,那么考虑随便操作直到序列存在出现次数恰为一半的数。

每次删掉 \(a_1\) 和首个 \(\ne a_1\) 的元素放到 \(b_1,b_2\),分析这个过程,发现每当我们删掉的元素变成一个前缀时,这个前缀中一定有一种元素出现次数恰为一半。

因此这样把序列分段,段内元素必定合法,段间元素如果相同那么交换奇偶下标即可,注意最后一段要满足下标限制时要把众数放到前面。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
int n,m,q,w,a[MAXN],b[MAXN],c[MAXN],f[MAXN],p[MAXN];
bool h[MAXN];
void del(int x) { --c[b[x]--],++c[b[x]],w-=!c[w]; }
void solve() {
	cin>>n,w=q=m=0;
	for(int i=1;i<=n;++i) b[i]=c[i]=h[i]=0;
	for(int i=1;i<=n;++i) cin>>a[i],++b[a[i]];
	for(int i=1;i<=n;++i) ++c[b[i]],w=max(w,b[i]);
	if(w>(n+1)/2) return cout<<"NO\n",void();
	cout<<"YES\n";
	for(int i=1,j=0;i<=n&&w<(n+1-m)/2;++i) if(!h[i]) {
		for(j=max(j,i+1);h[j]||a[j]==a[i];++j);
		if(m==i-1) p[++q]=m+1;
		f[++m]=j,f[++m]=i,h[i]=h[j]=1,del(a[i]),del(a[j]);
	}
	for(int i=1,x=p[q+1]=m+1,y=m+2,o=max_element(b+1,b+n+1)-b;i<=n;++i) if(!h[i]) {
		if(a[i]==o) f[x]=i,x+=2;
		else f[y]=i,y+=2;
	}
	for(int i=q-1;i>=1;--i) if(a[f[p[i+1]-1]]==a[f[p[i+1]]]) {
		for(int j=p[i];j<p[i+1];j+=2) swap(f[j],f[j+1]);
	}
	for(int i=1;i<=n;++i) cout<<f[i]<<" \n"[i==n];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



Q. [CF1718D] Permutation for Burenka (4.5)

Problem Link

建立笛卡尔树,那么问题变成每个位置要填的 \(x_i\in[l_i,r_i]\),且子树内的 \(x_v\) 要比 \(x_u\) 小。

注意到子树内的 \(l_v\le l_u,r_v\le r_u\),所以 \(x_v>x_u\) 时直接交换 \(x_u,x_v\) 就行。

那么我们只要把所有 \(S\) 中元素和 \([l_i,r_i]\) 匹配就行。

Hall 定理判定完美匹配,\(\forall L,R\) 要求 \(f(L,R)=\sum_i[L\le l_i\le r_i\le R]-\sum_{x\in S}[L\le x\le R]\le0\)。

考虑去掉 \(d\) 后 \(f\) 的结构,显然所有 \(f(L,R)\le 1\) 且 \(d\) 属于所有 \(f(L,R)=1\) 的 \([L,R]\) 交集内部。

容易扫描线线段树维护交集,从而算出 \(d\) 的合法区间。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+5,MAXS=1<<21|5;
struct info { int l,r,z; };
info operator+(const info&a,const info&b) {
	return {max(a.l,b.l),max(a.r,b.r),max({a.z,b.z,a.l+b.r})};
}
int n,m,q,K,a[MAXN],b[MAXN],c[MAXN],d[MAXN],h[MAXN];
struct Segt {
	array<int,2>mx[MAXS]; int ad[MAXS];
	void adt(int p,int k) { ad[p]+=k,mx[p][0]+=k; }
	void psd(int p) { if(ad[p]) adt(p<<1,ad[p]),adt(p<<1|1,ad[p]),ad[p]=0; }
	void psu(int p) { mx[p]=max(mx[p<<1],mx[p<<1|1]); }
	void init(int l=1,int r=m,int p=1) {
		mx[p]={0,r},ad[p]=0;
		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>ur) return ;
		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;
int ls[MAXN],rs[MAXN],st[MAXN],L[MAXN],R[MAXN];
void dfs(int u) {
	if(b[u]) L[u]=b[u]=lower_bound(h+1,h+m+1,b[u])-h,R[u]=min(R[u],L[u]);
	for(int v:{ls[u],rs[u]}) if(v) R[v]=R[u],dfs(v),L[u]=max(L[u],L[v]);
}
vector <int> f[MAXN];
bool ic[MAXN];
void solve() {
	cin>>n>>q,m=K=0;
	for(int i=1;i<=n;++i) ls[i]=rs[i]=0;
	for(int i=1,tp=0;i<=n;ls[st[tp]]=i,st[++tp]=i++) {
		for(cin>>a[i];tp&&a[st[tp]]<a[i];rs[i]=st[tp--]);
	}
	for(int i=1;i<=n;++i) {
		cin>>b[i],K+=!b[i],L[i]=1;
		if(b[i]) h[++m]=b[i];
	}
	for(int i=1;i<K;++i) cin>>c[i],h[++m]=c[i];
	for(int i=1;i<=q;++i) cin>>d[i],h[++m]=d[i];
	sort(h+1,h+m+1),R[st[1]]=m,dfs(st[1]),T.init();
	for(int i=1;i<=m;++i) f[i].clear(),ic[i]=0;
	int l=1,r=m;
	for(int i=1;i<=n;++i) {
		if(!b[i]) f[R[i]].push_back(L[i]);
		else if(b[i]<L[i]||R[i]<b[i]) r=0;
	}
	for(int i=1;i<K;++i) ic[lower_bound(h+1,h+m+1,c[i])-h]=1;
	for(int i=1;i<=m;++i) {
		if(ic[i]) T.add(1,i,-1);
		for(int x:f[i]) T.add(1,x,1);
		auto o=T.mx[1];
		if(o[0]>1) r=0;
		else if(o[0]==1) r=min(r,i),l=max(l,o[1]);
	}
	for(int i=1;i<=q;++i) {
		d[i]=lower_bound(h+1,h+m+1,d[i])-h;
		cout<<(l<=d[i]&&d[i]<=r?"YES\n":"NO\n");
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



R. [CF1781G] Diverse Coloring (3.5)

Problem Link

猜测答案为 \(n\bmod 2\),除非树是 \((1,2),(2,3),(2,4)\)。

构造可以直接 dp 记录子树内黑白点差 \(\in[-2,2]\) 的所有状态是否可以生成然后直接实现。

更优秀的做法考虑把图分成若干大小为 \(2,3\) 的连通块,每个连通块内间隔染色,调整两种染色方法的三元连通块个数就能平衡。

划分时可能剩下根节点单独一个,此时稍微合并周围的 \(\le 2\) 个连通块,然后爆搜即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
vector <int> G[MAXN];
int a[MAXN],m,o,fa[MAXN],z[MAXN];
void dfs1(int u) {
	int c=0;
	for(int v:G[u]) dfs1(v),c+=!a[v];
	if(c) {
		a[u]=++m;
		for(int v:G[u]) if(!a[v]) a[v]=m;
	}
}
void dfs2(int u) {
	if(a[u]!=a[fa[u]]) {
		vector <int> c;
		for(int v:G[u]) if(a[v]==a[u]) c.push_back(v);
		if(c.size()==1) z[u]=1;
		else z[u]=o,z[c[0]]=z[c[1]]=o^=1;
	}
	for(int v:G[u]) dfs2(v);
}
void solve() {
	int n; cin>>n,m=0;
	for(int i=1;i<=n;++i) G[i].clear(),a[i]=z[i]=0;
	for(int i=2;i<=n;++i) {
		cin>>fa[i],G[fa[i]].push_back(i);
		cout<<(i==4&&G[2].size()==2?2:i%2)<<"\n";
	}
	if(n==4&&G[2].size()==2) return cout<<"wbww\n",void();
	dfs1(1),o=0;
	if(!a[1]) {
		vector <int> b{1};
		if(G[1].size()==2) {
			for(int i=1,x=a[G[1][0]],y=a[G[1][1]];i<=n;++i) if(a[i]==x||a[i]==y) a[i]=0,b.push_back(i);
		} else {
			for(int i=1,x=a[2];i<=n;++i) if(a[i]==x) a[i]=0,b.push_back(i);
			if(b.size()==4) {
				int q=0;
				for(int u:b) for(int v:G[u]) if(a[v]) q=a[v];
				for(int i=1;i<=n;++i) if(a[i]==q) a[i]=0,b.push_back(i);
			}
		}
		int t=b.size(); vector<vector<int>>E(t);
		for(int i=1,j;i<t;++i) {
			j=find(b.begin(),b.end(),fa[b[i]])-b.begin();
			E[i].push_back(j),E[j].push_back(i);
		}
		for(int s=0;s<(1<<t);++s) if(__builtin_popcount(s)==t/2) {
			for(int i=0;i<t;++i) {
				int q=0;
				for(int j:E[i]) q|=(s>>j&1)^(s>>i&1);
				if(!q) goto nx;
			}
			for(int i=0;i<t;++i) z[b[i]]=s>>i&1;
			break;
			nx:;
		}
	}
	dfs2(1);
	for(int i=1;i<=n;++i) cout<<"wb"[z[i]];
	cout<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



S. [CF2178G] deCH OR Dations (3)

Problem Link

很难检验经过每个点的路径数 \(x_i\) 是否都是偶数,考虑经典随机化技巧,随机权值 \(w\) 后判断 \(\oplus w_i\times x_i\) 是否为 \(0\)。

这样每次加一个点 \(n\) 时只要维护所有在 \(n\) 结束的路径上的 \(\oplus w\),这是简单数据结构问题。

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

代码:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e6+5;
mt19937_64 rnd(time(0));
int n;
struct BIT {
	ull tr[MAXN],s;
	void init() { fill(tr,tr+2*n+1,0); }
	void upd(int x,ull z) { for(;x<=2*n;x+=x&-x) tr[x]^=z; }
	ull qry(int x) { for(s=0;x;x&=x-1) s^=tr[x]; return s; }
}	T;
void solve() {
	cin>>n,T.init();
	ull s=0;
	for(int i=1,l,r;i<=n;++i) {
		cin>>l>>r;
		ull w=rnd()>>1,z=T.qry(r)^T.qry(l-1)^1;
		if(z&1) z^=w<<1;
		T.upd(l,z),T.upd(r,z),s^=z>>1;
		cout<<(!s);
	}
	cout<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



T. [CF1942H] Farmer John's Favorite Intern (3.5)

Problem Link

看成把每个生长时间和 \(b_i\) 或收获事件进行匹配,用 Hall 定理判定是否有完美匹配,\(|S|-|N(S)|\) 的结构可以用简单树形 dp 刻画,动态问题只要 ddp 维护即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,N=1<<18;
const ll inf=1e18;
struct info { ll a,b,c,d; } O={0,-inf,0,-inf};
info operator *(const info&u,const info&v) {
	return {u.a+v.a,max(u.b+v.a,u.c+v.b),u.c+v.c,max(u.d+v.a,v.d)};
}
int n,q,hson[MAXN],fa[MAXN],siz[MAXN],top[MAXN],dfn[MAXN],dc,bt[MAXN];
vector <int> G[MAXN];
ll a[MAXN],b[MAXN],c[MAXN],f[MAXN][2],su[MAXN][3];
struct zkw {
	info tr[N<<1];
	void upd(int x,info o) {
		for(tr[x+=N]=o,x>>=1;x;x>>=1) tr[x]=tr[x<<1|1]*tr[x<<1];
	}
	info qry(int l,int r) {
		info sl=O,sr=O;
		for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
			if(~l&1) sl=tr[l^1]*sl;
			if(r&1) sr=sr*tr[r^1];
		}
		return sr*sl;
	}
}	T;
void qf(int x) {
	auto o=T.qry(dfn[x],dfn[bt[x]]);
	f[x][0]=max({o.a,o.b,o.d}),f[x][1]=o.c;
}
void gm(int u) {
	T.upd(dfn[u],{max(su[u][0]+b[u],su[u][2])-a[u],su[u][1]+b[u]+c[u]-a[u],su[u][1]+b[u]+c[u]-a[u],su[u][2]-a[u]});
}
void al(int x,int y,int o) {
	su[y][0]+=o*f[x][0],su[y][1]+=o*f[x][1],su[y][2]+=o*max(0ll,f[x][0]);
}
void dfs1(int u) {
	hson[u]=0,siz[u]=1;
	for(int v:G[u]) {
		dfs1(v),siz[u]+=siz[v];
		if(siz[v]>siz[hson[u]]) hson[u]=v;
	}
}
void dfs2(int u,int rt) {
	top[u]=rt,dfn[u]=++dc,bt[u]=u;
	if(hson[u]) dfs2(hson[u],rt),bt[u]=bt[hson[u]];
	for(int v:G[u]) if(v^hson[u]) dfs2(v,v),qf(v),al(v,u,1);
	gm(u);
}
void upd(int x) {
	for(gm(x),x=top[x];x!=1;x=fa[x],gm(x),x=top[x]) al(x,fa[x],-1),qf(x),al(x,fa[x],1);
}
void solve() {
	cin>>n>>q,dc=0;
	for(int i=1;i<=n;++i) G[i].clear(),a[i]=b[i]=c[i]=0,f[i][0]=f[i][1]=0,su[i][0]=su[i][1]=su[i][2]=0;
	for(int i=2;i<=n;++i) cin>>fa[i],G[fa[i]].push_back(i);
	for(int i=1;i<=n;++i) cin>>b[i];
	dfs1(1),dfs2(1,1); 
	for(int t,x,v;q--;) {
		cin>>t>>x>>v,t==1?a[x]+=v:c[x]+=v;
		upd(x),qf(1),cout<<(max(f[1][0],f[1][1])>0?"NO\n":"YES\n");
	}
	for(int i=1;i<=n;++i) T.upd(i,O);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_,fill(T.tr,T.tr+N*2,O);
	while(_--) solve();
	return 0;
}



U. [CF2045D] Aquatic Dragon (4.5)

Problem Link

dp 时难以记录剩余体力,只能按照每次飞行来划分状态。

考虑飞到 \(i\) 的时候,此时 \(\le i\) 的点都被选了,因此这部分的边肯定没用,而 \(>i\) 的边被选了则此时动不了,所以只要考虑 \([i,n]\) 的后缀,唯一可能的影响就是 \(a_i\) 有没有提前被取走。

两种状态类似,假设 \(a_i\) 没有被取,转移考虑飞到 \(i\) 然后走到 \(j\),最后飞到 \(j+1\) 的过程,此时要求 \(\sum_{k=i}^j a_k>(j-i)d\)。

然后考虑要走几次隧道的,如果 \(\sum_{t=i}^k a_t<(k+1-i)t\),那么必须往返 \(k\to k+1\) 的隧道,可以证明往返这些隧道就足够了。

可以写出 \(n^2\) dp,记 \(s_i=\sum_{j\le i}a_i-ik\),那么代价大概就是区间内 \(s_k<s_i\) 的个数,对 \(s\) 开线段树优化转移即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e5+5,MAXS=1<<20|5;
const ll inf=1e18;
int n,m;
ll D,Ts,Tf,Tw,a[MAXN],s[MAXN],f[MAXN][2],h[MAXN];
struct Segt {
	ll f[MAXS],tg[MAXS];
	Segt() { memset(f,0x3f,sizeof(f)); }
	void adt(int p,ll k) { f[p]+=k,tg[p]+=k; }
	void psd(int p) { if(tg[p]) adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
	void psu(int p) { f[p]=min(f[p<<1],f[p<<1|1]); }
	void add(int ul,int ur,ll k,int l=1,int r=m,int p=1) {
		if(ul>ur) return ;
		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);
	}
	void ins(int x,ll k,int l=1,int r=m,int p=1) {
		if(l==r) return f[p]=min(f[p],k),void();
		int mid=(l+r)>>1; psd(p);
		x<=mid?ins(x,k,l,mid,p<<1):ins(x,k,mid+1,r,p<<1|1);
		psu(p);
	}
	ll qry(int ul,int ur,int l=1,int r=m,int p=1) {
		if(ul>ur) return inf;
		if(ul<=l&&r<=ur) return f[p];
		int mid=(l+r)>>1; ll z=inf; psd(p);
		if(ul<=mid) z=min(z,qry(ul,ur,l,mid,p<<1));
		if(mid<ur) z=min(z,qry(ul,ur,mid+1,r,p<<1|1));
		return z;
	}
}	T;
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>D>>Ts>>Tf>>Tw,Tw*=2;
	for(int i=1;i<=n;++i) cin>>a[i],s[i]=s[i-1]+a[i]-D,h[++m]=s[i]+D,h[++m]=s[i-1];
	sort(h+1,h+m+1),m=unique(h+1,h+m+1)-h-1,memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;++i) {
		f[i][0]=T.qry(1,lower_bound(h+1,h+m+1,s[i]+2*D)-h-1)+Tf+Ts*(i-1)+Tw;
		f[i][1]=T.qry(1,lower_bound(h+1,h+m+1,s[i-1]+D)-h-1)+Tf+Ts*(i-1);
		T.add(upper_bound(h+1,h+m+1,s[i-1])-h,m,Tw);
		if(i==1) f[i][1]=0;
		if(i==n) break;
		T.ins(lower_bound(h+1,h+m+1,s[i]+D)-h,f[i][0]-Ts*i);
		T.ins(lower_bound(h+1,h+m+1,s[i-1])-h,f[i][1]-Ts*i);
	}
	cout<<min({f[n][0],f[n][1],T.qry(1,upper_bound(h+1,h+m+1,s[n]+D)-h-1)+Ts*n})<<"\n";
	return 0;
}



*V. [CF2061H2] Kevin and Stones (8.5)

Problem Link

猜一些必要结论:首先 \(s\ne t\) 时必定能操作,也就是存在 \(s\to V,t\to V\) 的完美匹配。

其次对于每个是二分图的连通块,左右部上 \(s,t\) 点数相等或交换后相等。

我们发现经过这两个条件筛选之后得到的图已经具有很强的性质,不妨猜测这是充分的,下面给出构造性证明。

首先假设该条件充分,那么我们只要保留两个匹配中的边,此时每个点度数 \(\le 4\),还是有点困难。

考虑到本题和路径距离的奇偶性有关,所以拆点 \(u_0,u_1\) 表示走了偶数或奇数步到 \(u\),由于所有点同时移动,因此所有终点要么同时为 \(t_0\) 要么同时为 \(t_1\),可以根据原始二分图上 \(s,t\) 点数判断。

实际上相当于我们把算匹配的二分图保留下来了,此时每个点度数 \(\le 2\),且图为二分图。

进一步简化这张图,如果图上有长度 \(>2\) 的偶环,那么每条边必定交替是 \(s,t\) 中匹配的边,可以发现把所有 \(t\) 中匹配的边替换成 \(s\) 中匹配的边依旧能得到一个完美匹配。

执行这样的操作后图上只有长度为 \(2\) 的环,重边去掉后就变成若干条链,不过我们的图还没有保证连通性正确,把剩下的能加入的边都加入,图会变成若干棵树,并且所有起点深度同奇偶、终点深度奇偶。

此时我们在必要条件成立时把问题转成了树上简单问题。

构造可以考虑删边递归,初始每个点在 \(s\) 匹配边上折返,每次我们找到一条不卡住其他路径的匹配边,具体来说就是两个端点出栈序较大值最小的一条边。

此时如果这条边是 \(t\) 上的匹配边,找一个距离最近的 \(s\) 匹配边上的点走过去,然后在 \(t\) 上折返到结束,注意这里距离要算两条边最近的两个端点的距离,否则可能和另一个 \(s\) 上折返的点相撞。

此时我们删掉这两个点递归,如果下一条 \(s\to t\) 的路径和这条路径相遇在 \(x\),那么他们到 \(x\) 距离相等,否则后者第一轮到 \(t\) 距离更小,此时我们只要让第二条路径多折返一次就行。

可以证明总用时 \(\le 2n\)。

不过如果某次我们找到的最靠外的匹配边是 \(s\) 上的,那么考虑时光倒流,从 \(t\) 走到 \(s\),也就是在 \(s\) 上不停折返,在最后时刻才从 \(s\) 走到 \(t\),下一条路径就提前两轮出发,最后在 \(t\) 上折返一次,以此类推。

此时同种路径内不交,如果 \(s\to t\) 和 \(t\to s\) 的两个路径相遇,那么两个起点的距离 \(\ge 2n-1\),如果他们并非各自类型的首条路径,考虑每当他们等待一轮都可以删掉至少两个不在路径上的点,最后还是能证明这样的情况不会发生。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4025,MAXE=2e5+5,inf=1e9;
namespace F {
struct Edge { int v,f,e; } G[MAXE];
int S,T,ec=1,h[MAXN],c[MAXN],d[MAXN],Q[MAXN];
void init() { ec=1,memset(h,0,sizeof(h)); }
void adde(int u,int v,int w) { G[++ec]={v,w,h[u]},h[u]=ec; }
void link(int u,int v,int w) { adde(u,v,w),adde(v,u,0); }
bool bfs() {
	memcpy(c,h,sizeof(c)),memset(d,-1,sizeof(d)),Q[1]=S,d[S]=0;
	for(int l=1,r=1,u;l<=r;++l) for(int i=h[u=Q[l]];i;i=G[i].e) if(G[i].f&&d[G[i].v]<0) d[G[i].v]=d[u]+1,Q[++r]=G[i].v;
	return ~d[T];
}
int dfs(int u,int f) {
	if(u==T) return f;
	int r=f;
	for(int &i=c[u],v;i;i=G[i].e) if(G[i].f&&d[v=G[i].v]==d[u]+1) {
		int g=dfs(v,min(r,G[i].f));
		if(!g) d[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;
}
}
int n,m,a[MAXN],b[MAXN],dsu[MAXN],dfn[MAXN];
bitset <MAXN> vis,cl;
vector <int> G[MAXN];
string si,ti;
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
int d[MAXN][MAXN],fa[MAXN][MAXN],z[MAXN][MAXN];
void solve() {
	cin>>n>>m>>si>>ti,si=" "+si,ti=" "+ti;
	for(int i=0;i<=2*n;++i) G[i].clear(),a[i]=b[i]=0;
	vector <array<int,2>> E;
	for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u),E.push_back({u,v});
	if(si==ti) {
		cout<<"YES\n0\n";
		for(int i=1;i<=n;++i) if(si[i]=='1') cout<<i<<" ";
		cout<<"\n";
		return ;
	}
	vis.reset(),cl.reset();
	int op=3;
	for(int i=1;i<=n;++i) if(!vis[i]) {
		int sc[2]={0,0},tc[2]={0,0},ok=1;
		function<void(int)>dfs1=[&](int x) {
			vis[x]=1,sc[cl[x]]+=si[x]-'0',tc[cl[x]]+=ti[x]-'0';
			for(int y:G[x]) {
				if(!vis[y]) cl[y]=cl[x]^1,dfs1(y);
				else ok&=cl[y]!=cl[x];
			}
		};
		dfs1(i);
		if(!ok) continue;
		if(sc[0]!=tc[0]||sc[1]!=tc[1]) op&=2;
		if(sc[0]!=tc[1]||sc[1]!=tc[0]) op&=1;
	}
	auto work=[&](string&in,int*f) {
		int o=0;
		F::init(),F::S=2*n+1,F::T=2*n+2;
		for(int i=1;i<=n;++i) {
			if(in[i]=='1') ++o,F::link(F::S,i,1);
			F::link(i+n,F::T,1);
			for(int j:G[i]) F::link(i,j+n,1);
		}
		if(F::dinic()<o) return 1;
		for(int i=1;i<=n;++i) if(in[i]=='1') {
			for(int j=F::h[i];j;j=F::G[j].e) if(!F::G[j].f) f[f[i]=F::G[j].v]=i;
		}
		return 0;
	};
	if(!op||work(si,a)||work(ti,b)) return cout<<"NO\n",void();
	op=(op==2),cout<<"YES\n"<<2*n-op<<"\n";
	if(op) {
		for(int i=1;i<=n;++i) swap(b[i],b[i+n]),b[i]=b[i]?b[i]+n:0,b[i+n]=b[i+n]?b[i+n]-n:0;
	}
	vis.reset();
	for(int i=1;i<=2*n;++i) if(!vis[i]&&(!a[i]||!b[i])) {
		for(int x=i,o=0;x;vis[x]=1,x=o?a[x]:b[x],o^=1);
		for(int x=i,o=0;x;vis[x]=1,x=o?b[x]:a[x],o^=1);
	}
	for(int i=1;i<=2*n;++i) if(!vis[i]) {
		vector <int> cy;
		for(int x=i,o=0;!vis[x];vis[x]=1,cy.push_back(x),x=o?b[x]:a[x],o^=1);
		for(int j=1;j<(int)cy.size();j+=2) b[b[cy[j]]=cy[j-1]]=cy[j];
	}
	for(int i=1;i<=2*n;++i) G[i].clear(),dsu[i]=i;
	auto link=[&](int x,int y) {
		if(x&&y&&find(x)!=find(y)) G[x].push_back(y),G[y].push_back(x),dsu[find(x)]=find(y);
	};
	for(int i=1;i<=n;++i) link(i,a[i]),link(i,b[i]);
	for(auto e:E) link(e[0],e[1]+n),link(e[1],e[0]+n);
	vis.reset();
	for(int i=1,dc=0;i<=2*n;++i) {
		function<void(int)>dfs2=[&](int u) {
			for(int v:G[u]) if(v!=fa[i][u]) fa[i][v]=u,d[i][v]=d[i][u]+1,dfs2(v);
		};
		function<void(int)>dfs3=[&](int u) {
			vis[u]=1;
			for(int v:G[u]) if(!vis[v]) dfs3(v);
			dfn[u]=++dc;
		};
		memset(d[i],0x3f,sizeof(d[i])),d[i][i]=fa[i][i]=0,dfs2(i);
		if(!vis[i]) dfs3(i);
	}
	vector<array<int,4>>f;
	for(int i=1;i<=n;++i) {
		if(si[i]=='1') f.push_back({max(dfn[i],dfn[a[i]]),0,i,a[i]});
		if(ti[i]=='1') {
			if(op) f.push_back({max(dfn[i+n],dfn[b[i+n]]),1,i+n,b[i+n]});
			else f.push_back({max(dfn[i],dfn[b[i]]),1,i,b[i]});
		}
	}
	sort(f.begin(),f.end()),vis.reset();
	int L=0,R=2*n-op,q=0;
	for(int i=0;i<(int)f.size();++i) if(!vis[i]) {
		int o=0,w=inf;
		for(int j=i+1;j<(int)f.size();++j) if(!vis[j]&&f[j][1]!=f[i][1]) {
			int v=inf;
			for(int l:{2,3}) for(int r:{2,3}) v=min(v,d[f[i][l]][f[j][r]]);
			if(v<w) w=v,o=j;
		}
		int x=f[i][2],y=f[o][2],*h=z[++q];
		if(f[i][1]) {
			for(int j=1;j<L;j+=2) h[j-1]=y,h[j]=a[y];
			int t=L; L+=2;
			for(int u=y;u;u=fa[x][u]) h[t++]=u;
			for(int j=t;j<2*n-op;j+=2) h[j]=b[x],h[j+1]=x;
		} else {
			for(int j=2*n-op-1;j>R;j-=2) h[j+1]=y,h[j]=b[y];
			int t=R; R-=2;
			for(int u=y;u;u=fa[x][u]) h[t--]=u;
			for(int j=t;j>0;j-=2) h[j]=a[x],h[j-1]=x;
		}
		vis[i]=vis[o]=1;
	}
	for(int i=0;i<=2*n-op;++i) for(int j=1;j<=q;++j) cout<<(z[j][i]>n?z[j][i]-n:z[j][i])<<" \n"[j==q];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



W. [CF2053I2] Affectionate Arrays (4)

Problem Link

首先观察题目的限制,猜测最大子段和就是 \(m=\sum a\),构造只需要把任何时候的前缀和控制在 \([0,m]\) 范围内即可,显然这个条件就是最大子段和等于 \(m\) 的充要条件。

\(f_{i,s}\) 表示前缀和为 \(s\) 的最小代价,每次要么转移到 \(s+a_i\),要么以 \(f_{i,s}+1\) 转移到任意位置。

显然 \(f_i\) 之间的值极差 \(\le 1\),且取到最小值的 \(s\) 构成区间 \([l,r]\),转移讨论 \([l+a_i,r+a_i]\cup [0,m]\) 是否为空即可。

然后考虑计算方案数 \(g\),每次转移时先把 \(g_{i,s}\) 平移到 \(g_{i+1,s+a_i}\),然后把所有 \(s\not\in[l,r]\) 的 \(g_{i+1,s}\) 加上 \(\sum_{t=l}^r g_{i,t}\)。

如果 \([l+a_i,r+a_i]\cup [0,m]=\varnothing\) 只要更新 \(l,r\),\(g\) 的转移不变。

使用 std::deque 维护 \(g\) 的连续段,记录 \(\sum_{t=0}^m g_t,\sum_{t=l}^r g_t\),以及全局加标记和 \(g_{l}\sim g_r\) 的减法标记。

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

代码:

#include<bits/stdc++.h>
#define ll long long
#define S f.front()
#define T f.back()
using namespace std;
const int MAXN=3e6+5,MOD=998244353;
ll n,a[MAXN],m;
struct info { ll l,r,z; };
void solve() {
	cin>>n,m=0;
	for(int i=1;i<=n;++i) cin>>a[i],m+=a[i];
	deque <info> f;
	ll l=0,r=0,c=1,x=0,d=0,y=0,s=1;
	f.push_back({0,0,1});
	if(m>0) f.push_back({1,m,0});
	for(int i=1;i<=n;++i) {
		ll L=max(0ll,l+a[i]),R=min(m,r+a[i]),v=c;
		if(a[i]>0) {
			for(;T.l+d+a[i]>m;f.pop_back()) {
				if(l<=T.l+d&&T.r+d<=r) c=(c+(T.r-T.l+1)%MOD*(MOD*2-T.z-x+y))%MOD;
				s=(s+(T.r-T.l+1)%MOD*(MOD*2-T.z-x))%MOD;
			}
			if(l<=T.l+d&&T.r+d<=r) c=(c+(T.r+d-(m-a[i]))%MOD*(MOD*2-T.z-x+y))%MOD;
			s=(s+(T.r+d-(m-a[i]))%MOD*(MOD*2-T.z-x))%MOD;
			T.r=m-a[i]-d;
		} else {
			for(;S.r+d+a[i]<0;f.pop_front()) {
				if(l<=S.l+d&&S.r+d<=r) c=(c+(S.r-S.l+1)%MOD*(MOD*2-S.z-x+y))%MOD;
				s=(s+(S.r-S.l+1)%MOD*(MOD*2-S.z-x))%MOD;
			}
			if(l<=S.l+d&&S.r+d<=r) c=(c+(-a[i]-S.l-d)%MOD*(MOD*2-S.z-x+y))%MOD;
			s=(s+(-a[i]-S.l-d)%MOD*(MOD*2-S.z-x))%MOD;
			S.l=-a[i]-d;
		}
		if(L>R) {
			l=max(0ll,a[i]),r=min(m,m+a[i]),x=(x+v)%MOD,c=s=(s+(r-l+1)%MOD*v)%MOD,y=0,d+=a[i];
		} else {
			d+=a[i],x=(x+v)%MOD,y=(y+v)%MOD,l=L,r=R,s=(s+(T.r-S.l+1)%MOD*v)%MOD;
		}
		if(a[i]>0) f.push_front({-d,a[i]-1-d,MOD-x});
		if(a[i]<0) f.push_back({m+a[i]+1-d,m-d,MOD-x});
	}
	if(r==m) cout<<(f.back().z+x+MOD-y)%MOD<<"\n";
	else cout<<(f.back().z+x+c)%MOD<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



X. [CF1740H2] Game of AI (5)

Problem Link

考虑如何判定 \((a,b)\) 是否合法,首先 \(b_u\in u\cup\mathrm{son}_u\),其次没有相邻的两个 \(b_i=i\),且环上至少有一个点的 \(b\) 不为其环上前驱,可以证明满足这三个条件一定能构造 \(p\)。

如果按 \(i\to b_i\) 形成的若干链结构去计算 \(a\) 容易做到 \(\mathcal O(n^2)\),但难以优化。

考虑从 \(a\) 的结构开始算 \(b\),\([x^n]F,[x^n]G\) 表示 \(n\) 个点的有根树 \((a,b)\) 数量,以及 \(a_{rt}=b_{rt}\) 的 \((a,b)\) 数量。

显然 \(G=x\exp(F-G)\),而 \(F=G+x\sum_{k\ge 1}\dfrac{kF^k}{k!}=G+xF\exp F\)。

所以 \(G=x\exp(xF\exp F),F=x\exp (xF\exp F)+xF\exp F\),可以牛顿迭代算出 \(F\)。

然后对于环上的结构分讨一下:

  • 如果 \(b_i\) 是非环上儿子则方案数 \(F-G\)。
  • 如果 \(b_i\) 是环上前驱则方案数 \(x\exp F\)。
  • 如果 \(b_i=a_i\) 则和前驱两个点合并起来,方案数是 \(G\times (F-G+x\exp F)\)。

算出总和 \(S\) 后答案为 \(\ln 1-S\),去掉所有 \(b_i\) 都指前驱的 \(\ln 1-x\exp F\),最后 $\exp $ 一下合并连通块即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1<<18;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,q,r; uLL m;
inline void init(const ull &B) { b=B,m=(uLL(1)<<64)/B; }
inline ull mod(const ull &a) {
	r=a-((m*a)>>64)*b;
	return r>=b?r-b:r;
}
};
#define o(x) FastMod::mod(x)
int MOD,G;
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) s=1ll*s*a%MOD; return s; }
namespace P {
const int N=1<<18;
int rev[N+5],inv[N+5],fac[N+5],ifac[N+5],w[N*2+5];
void poly_init() {
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
	for(int k=1;k<=N;k<<=1) {
		int x=ksm(G,(MOD-1)/k); w[k]=1;
		for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
	}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y;  }
void ntt(int *f,bool idft,int n) {
	for(int i=0;i<n;++i) {
		rev[i]=(rev[i>>1]>>1);
		if(i&1) rev[i]|=n>>1;
	}
	for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
	for(int k=2,x,y;k<=n;k<<=1) {
		for(int i=0;i<n;i+=k) {
			for(int j=i;j<i+k/2;++j) {
				x=f[j],y=o(1ll*f[j+k/2]*w[k+j-i]);
				f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
			}
		}
	}
	if(idft) {
		reverse(f+1,f+n);
		for(int i=0,x=ksm(n);i<n;++i) f[i]=o(1ll*f[i]*x);
	}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
	static int a[N+5],b[N+5];
	for(int i=0;i<n;++i) a[i]=f[i];
	for(int i=0;i<m;++i) b[i]=g[i];
	int len=plen(n+m-1);
	ntt(a,0,len),ntt(b,0,len);
	for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
	ntt(h,1,len);
	memset(a,0,sizeof(int)*len);
	memset(b,0,sizeof(int)*len);
}
void poly_inv(const int *f,int *g,int n) {
	static int a[N+5];
	g[0]=ksm(f[0]);
	int k=2;
	for(;k<(n<<1);k<<=1) {
		for(int i=0;i<k;++i) a[i]=f[i];
		ntt(g,0,k<<1),ntt(a,0,k<<1);
		for(int i=0;i<(k<<1);++i) {
			g[i]=o(o(2+1ll*(MOD-a[i])*g[i])*g[i]);
			if(g[i]<0) g[i]+=MOD;
		}
		ntt(g,1,k<<1);
		memset(g+k,0,sizeof(int)*k);
	}
	memset(g+n,0,sizeof(int)*(k-n));
	memset(a,0,sizeof(int)*k);
}
void poly_ln(const int *f,int *g,int n) {
	static int a[N+5],b[N+5];
	poly_inv(f,a,n);
	for(int i=1;i<n;++i) b[i-1]=1ll*i*f[i]%MOD;
	int m=plen(n<<1);
	ntt(a,0,m),ntt(b,0,m);
	for(int i=0;i<m;++i) a[i]=1ll*a[i]*b[i]%MOD;
	ntt(a,1,m);
	g[0]=0;
	for(int i=1;i<n;++i) g[i]=1ll*a[i-1]*inv[i]%MOD;
	memset(g+n,0,sizeof(int)*(m-n));
	memset(a,0,sizeof(int)*m);
	memset(b,0,sizeof(int)*m);
}
void poly_exp(const int *f,int *g,int n) {
	static int a[N+5];
	g[0]=1;
	int k=2;
	for(;k<(n<<1);k<<=1) {
		poly_ln(g,a,k);
		for(int i=0;i<k;++i) a[i]=o(f[i]+MOD-a[i]);
		++a[0];
		ntt(a,0,k<<1),ntt(g,0,k<<1);
		for(int i=0;i<(k<<1);++i) g[i]=o(1ll*g[i]*a[i]);
		ntt(g,1,k<<1);
		memset(g+k,0,sizeof(int)*k);
	}
	memset(g+n,0,sizeof(int)*(k-n));
	memset(a,0,sizeof(int)*k);
}
}
int rt() {
	vector <int> fc;
	int x=MOD-1;
	for(int i=2;i*i<=x;++i) if(x%i==0) for(fc.push_back(i);x%i==0;x/=i);
	if(x>1) fc.push_back(x);
	for(int g=2;g<MOD;++g) {
		for(int p:fc) if(ksm(g,(MOD-1)/p)==1) goto nx;
		return g; nx:;
	}
	return -1;
}
int a[N+5],b[N+5],c[N+5],d[N+5],e[N+5],p[N+5],q[N+5],r[N+5],s[N+5],f[N+5],g[N+5];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,m=4;
	cin>>n>>MOD,FastMod::init(MOD),G=rt(),P::poly_init();
	f[0]=0,f[1]=1;
	for(;m/2<=n;m*=2) {
		auto clr=[&](int*o) { memset(o,0,m*8); };
		for(int i=0;i<m;++i) a[i]=f[i];
		P::poly_exp(a,b,m);
		for(int i=m-1;~i;--i) b[i]=i?b[i-1]:0;
		P::poly_mul(a,b,c,m,m);
		for(int i=0;i<m;++i) d[i]=(b[i]+c[i])%MOD;
		P::poly_exp(c,e,m);
		for(int i=m-1;~i;--i) e[i]=i?e[i-1]:0;
		P::poly_mul(e,d,p,m,m);
		for(int i=0;i<m;++i) p[i]=(p[i]+d[i])%MOD;
		p[0]=(p[0]+MOD-1)%MOD,P::poly_inv(p,q,m);
		for(int i=0;i<m;++i) r[i]=(0ll+e[i]+c[i]+MOD-a[i])%MOD;
		P::poly_mul(q,r,s,m,m);
		for(int i=0;i<m;++i) f[i]=(a[i]+MOD-s[i])%MOD;
		clr(a),clr(b),clr(c),clr(d),clr(e),clr(p),clr(q),clr(r),clr(s);
	}
	P::poly_exp(f,a,n+1);
	for(int i=n;~i;--i) a[i]=i?a[i-1]:0;
	P::poly_mul(f,a,b,n+1,n+1);
	P::poly_exp(b,g,n+1);
	for(int i=n;~i;--i) g[i]=i?g[i-1]:0;
	for(int i=0;i<=n;++i) c[i]=(a[i]+b[i])%MOD;
	P::poly_mul(c,g,d,n+1,n+1);
	for(int i=0;i<=n;++i) d[i]=(d[i]+c[i])%MOD,d[i]=MOD-d[i]+(!i);
	P::poly_ln(d,p,n+1);
	for(int i=0;i<=n;++i) a[i]=MOD-a[i]+(!i);
	P::poly_ln(a,q,n+1);
	for(int i=0;i<=n;++i) p[i]=(0ll+q[i]+MOD-p[i]+MOD-b[i])%MOD;
	P::poly_exp(p,s,n+1);
	for(int i=1;i<=n;++i) cout<<1ll*P::fac[i]*s[i]%MOD<<"\n";
	return 0;
}



Y. [CF2152H2] Victorious Coloring (4.5)

Problem Link

直接 dp 难以做到动态维护,观察一些题目性质。

首先显然选的点集是连通块,否则只要保留最小的一个连通块。

其次我们尝试调整,选一条连通块内部的边 \(e\),然后切掉其中一侧,那么对于一条连通块边界上的边,其权值如果 \(\ge w(e)\) 那么切掉这一侧严格不劣。

所以可能被选的连通块一定是内部边权大于边界的,也就是最大 Kruskal 重构树上的子树。

限制就是每个子树内 \(\sum x_i\ge L-w_u\),贪心记录子树内的 \(\sum x=f\),转移为 \(f_{u}=\max(L-w_u,f_{ls}+f_{rs})\)。

那么直接维护每个 \(L\) 的答案,显然具有凸性,并且直接用可并堆或者优先队列合并维护所有斜率 \(+1\) 的拐点即可,不过相同的拐点要压缩起来一起维护。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
int n,m,q,dsu[MAXN]; ll a[MAXN];
priority_queue<array<ll,2>,vector<array<ll,2>>,greater<>>f[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void solve() {
	cin>>n,m=n;
	for(int i=1;i<=2*n;++i) dsu[i]=i,a[i]=0;
	vector<array<int,3>>E(n-1);
	for(auto&e:E) cin>>e[1]>>e[2]>>e[0],a[e[1]]+=e[0],a[e[2]]+=e[0];
	sort(E.begin(),E.end(),greater<>());
	for(int i=1;i<=n;++i) f[i].push({a[i],1});
	for(auto e:E) {
		int x=find(e[1]),y=find(e[2]);
		++m,dsu[x]=dsu[y]=m,a[m]=a[x]+a[y]-2*e[0];
		if(f[x].size()<f[y].size()) swap(x,y);
		auto&g=f[m]; g.swap(f[x]);
		while(f[y].size()) g.push(f[y].top()),f[y].pop();
		if(g.top()[0]<=a[m]) continue;
		ll K=0,B=0;
		for(;g.size()&&g.top()[0]*(K-1)+B+a[m]<0;g.pop()) B-=g.top()[1]*g.top()[0],K+=g.top()[1];
		g.push({a[m],1});
		ll u=(-B-a[m]+K-2)/(K-1),v=K*u+B,t=u-1-a[m],c=K-1;
		if(v-t>1) g.push({u-1,v-t-1}),c-=v-t-1;
		if(c) g.push({u,c});
	}
	vector<array<ll,3>>z{{0,0,0}};
	for(ll K=0,B=0;f[m].size();z.push_back({f[m].top()[0],K,B}),f[m].pop()) {
		B-=f[m].top()[0]*f[m].top()[1],K+=f[m].top()[1];
	}
	cin>>q;
	for(ll x;q--;) {
		cin>>x;
		auto i=*--lower_bound(z.begin(),z.end(),array<ll,3>{x+1,0,0});
		cout<<x*i[1]+i[2]<<"\n";
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



Z. [CF2207F] Hanabi (3.5)

Problem Link

结构肯定是选择若干等级按顺序全部打了,然后每个人把自己的卡牌分成若干个能连续打出的区间,如果区间内存在至少一张没打出的牌就要花一个指令来打牌。

注意到某个人第 \(x\sim y\) 张牌可以一口气打出,当且在手牌顺序下这些牌都是前缀标号最大值,那么直接取出 \(\mathcal O(nm)\) 个区间然后线段树优化 dp 即可。

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

代码:

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN],f[MAXN],S;
bool at[MAXN],is[MAXN];
vector <int> h[MAXN];
struct Segt {
	int mx[1<<19|5],ad[1<<19|5];
	void adt(int p,int k) { mx[p]+=k,ad[p]+=k; }
	void psd(int p) { if(ad[p]) adt(p<<1,ad[p]),adt(p<<1|1,ad[p]),ad[p]=0; }
	void psu(int p) { mx[p]=max(mx[p<<1],mx[p<<1|1]); }
	void init(int l=0,int r=n,int p=1) {
		ad[p]=mx[p]=0;
		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=0,int r=n,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);
		psu(p);
	}
}	T;
void solve() {
	cin>>n>>m,S=n,T.init();
	for(int i=1;i<=n;++i) is[i]=1,h[i].clear();
	vector<vector<int>>b(m);
	for(int i=1;i<=n*m;++i) cin>>a[i];
	for(int i=1,x;i<=n*m;++i) cin>>x,b[x-1].push_back(a[i]);
	for(auto&c:b) {
		memset(at,0,n+2);
		for(int j=0,w=0;j<n;++j) if(w<c[j]) w=c[j],at[c[j]]=1;
		for(int j=1,p=0;j<=n+1;++j) if(!at[j]) {
			if(p<j-1) h[j-1].push_back(p),++S;
			is[j]=0,p=j;
		}
	}
	for(int i=1;i<=n;++i) {
		if(is[i]) f[i]=T.mx[1]+1,T.add(i,i,f[i]);
		for(int x:h[i]) T.add(0,x,1);
	}
	cout<<S-1-T.mx[1]<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



AA. [CF2211G] Rational Bubble Sort (3.5)

Problem Link

首先有限次操作内可以让序列几乎平均。

考虑观察 \((i,s_i)\) 构成的图,每次操作就是找一个 \(x\) 在 \(x-1,x+1\) 连线上方的点放到连线上,目标是把图形变成下凸的。

猜一些必要结论,例如至少有一个点 \(x\) 严格在 \((0,0),(n,s_n)\) 下方,否则不能有连续的两个点在 \((0,0),(n,s_n)\) 上方。

考虑把 \([0,x],[x,n]\) 用充分多次操作变成几乎一条直线的结构,然后从 \([x,x]\) 开始逐步操作使得序列下凸即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
int n;
ll a[MAXN];
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i],a[i]+=a[i-1];
	for(int i=1;i<=n;++i) a[i]=a[i]*n-a[n]*i;
	for(int i=1;i<n;++i) if(a[i]<0) return cout<<"YES\n",void();
	for(int i=2;i<n;++i) if(a[i]>0&&a[i-1]>0) return cout<<"NO\n",void();
	cout<<"YES\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*AB. [CF1863I] Redundant Routes (8)

Problem Link

尝试进行调整,把路径 \([u,v]\) 向 \(v\) 一端扩展一步变成 \([u,x]\),如果不合法说明存在 \(x\to (u,v]\) 的路径。

此时这条路径 \(x\to y\) 尝试向 \(u\) 方向拓展一步变成 \(x\to w\),由于 \(w\to [y,x)\) 的路径都被 \([u,v]\) 包含所以一一定能调整。

停止时 \(u,y\) 相邻,那么总结一下这个调整过程,如果我们取出所有路径长度总和最大的一组最优解,那么存在 \((u,v)\) 时必定存在 \((x,y)\)。

所以这样的路径对要么同选要么同不选,可以缩成等价类,注意到如果两个等价路径 \((u_1,v_1),(u_2,v_2)\) 分别包含两条路径 \((x_1,y_1),(x_2,y_2)\),则这两条路径也必定等价,构造就沿着 \((u_1,v_1)\to (u_2,v_2)\) 的路径走就行。

所以说对于一个长度为 \(d\) 的路径等价类,只有一个长度为 \(d-1\) 的路径等价类中有不能和该等价类同时选的路径,所以这些等价类构成树形关系,可以简单树形 dp。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3005,MAXM=5e6+5;
int n,m,b[MAXN][MAXN],fa[MAXM],dsu[MAXM],f[MAXM],sz[MAXM];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
vector <int> G[MAXN],E[MAXM];
void dfs(int u,int fz,int h,int o) {
	fa[b[o][u]]=b[h][u];
	for(int v:G[u]) if(v^fz) dsu[find(b[v][h])]=find(b[o][u]),dfs(v,u,h,o);
}
void dp(int u) {
	for(int v:E[u]) dp(v),f[u]+=f[v];
	f[u]=max(f[u],sz[u]);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,m=0;
	for(int i=1;i<=n;++i) for(int j=1;j<i;++j) b[i][j]=b[j][i]=++m,dsu[m]=m;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	for(int i=1;i<=n;++i) for(int j:G[i]) dfs(j,i,j,i);
	for(int i=1;i<=m;++i) {
		++sz[find(i)];
		if(find(i)==i) E[find(fa[i])].push_back(i);
	}
	dp(0),cout<<f[0]<<"\n";
	return 0;
}



AC. [CF1718F] Burenka, an Array and Queries (4)

Problem Link

分讨这个数是否有 \(>\sqrt C\) 的质因子,如果存在的话,那么剩下部分 \(\le \sqrt C\),我们只要枚举 \(1\sim \sqrt C\) 中的每个数不含 \(P\) 的所有 \(\le \sqrt C\) 的质因子,由于 \(Y=\pi(\sqrt C)\le 65\),可以压位,然后可以简单计算。

所以我们只要算 \(1\sim C\) 中有多少数不含 \(>\sqrt C\) 的质因子,且质因子集合和询问集合 \(P\) 无交。

进一步根号分治,记 \(X=\pi(\sqrt[3] C)=13\),那么每个数只有 \(\le 2\) 个 \((X,Y]\) 范围内的质因子,对 \(\mathcal O(Y^2)\) 种情况分别预处理 \(2^X\) 个集合的子集和,查询时枚举两个这样的质因子就能 \(\mathcal O(1)\) 查询。

为了快速处理 \(P\) 中 \(>\sqrt C\) 的质因子,可以简单莫队处理一下,总体常数不大。

正解采用容斥,直接对着 \(\le Y\) 的质因子爆搜 \(\sum_S(-1)^{|S|}\left\lfloor\dfrac{n}{\prod_{p\in S}p}\right\rfloor\),记忆化一下即可。

时间复杂度 \(\mathcal O(n\sqrt q+Y^2\times (X2^X+q))\)。

代码:

#include<bits/stdc++.h>
#define LL __int128
using namespace std;
const int MAXN=1e5+5,X=13,Y=65,B=320;
struct qy { int l,r,i; } Q[MAXN];
int n,m,K,q,a[MAXN],b[MAXN],c[MAXN],pr[MAXN],pc,Z[MAXN];
int ct,id[Y+5][Y+5],f[1455][1<<X|5],in[B+5],vs[MAXN];
bool ic[MAXN];
LL s[MAXN],st[17][MAXN];
LL un(int l,int r) {
	int k=__lg(r-l+1);
	return st[k][l]|st[k][r-(1<<k)+1];
}
void add(int x) { if(x>1&&!vs[x]++) --in[K/x]; }
void del(int x) { if(x>1&&!--vs[x]) ++in[K/x]; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>K>>q,m=2e4;
	for(int i=2;i<MAXN;++i) {
		if(!ic[i]) pr[pc++]=i;
		for(int j=0;j<pc&&i*pr[j]<MAXN;++j) {
			ic[i*pr[j]]=1;
			if(i%pr[j]==0) break;
		}
	}
	for(int i=1;i<MAXN;++i) {
		int &x=b[i]=i;
		for(int j=0;j<Y;++j) if(x%pr[j]==0) for(s[i]|=(LL)1<<j;x%pr[j]==0;x/=pr[j]);
	}
	for(int i=X;i<Y;++i) for(int j=i;j<Y;++j) id[i][j]=++ct;
	for(int i=1;i<=K;++i) if(b[i]==1) {
		int x=0,y=0;
		for(int j=X;j<Y;++j) if(s[i]>>j&1) x?y=j:x=y=j;
		++f[id[x][y]][s[i]&((1<<X)-1)];
	}
	for(int o=0;o<=ct;++o) for(int k=1;k<(1<<X);k<<=1) for(int i=0;i<(1<<X);++i) {
		if(i&k) f[o][i]+=f[o][i^k];
	}
	for(int i=1;i<=n;++i) cin>>c[i],st[0][i]=s[c[i]];
	for(int k=0;k<16;++k) for(int i=1;i+(2<<k)-1<=n;++i) {
		st[k+1][i]=st[k][i]|st[k][i+(1<<k)];
	}
	for(int i=Y;i<pc;++i) ++in[K/pr[i]];
	for(int i=1;i<=q;++i) cin>>Q[i].l>>Q[i].r,Q[i].i=i;
	sort(Q+1,Q+q+1,[&](auto x,auto y){
		return (x.l-1)/B==(y.l-1)/B?((x.l-1)/B&1?x.r<y.r:x.r>y.r):x.l<y.l;
	});
	for(int o=1,l=1,r=0;o<=q;++o) {
		while(r<Q[o].r) add(b[c[++r]]);
		while(l>Q[o].l) add(b[c[--l]]);
		while(l<Q[o].l) del(b[c[l++]]);
		while(r>Q[o].r) del(b[c[r--]]);
		int&z=Z[Q[o].i]=0; LL S=un(l,r);
		for(int i=K/pr[Y],sz=0;i>=1;--i) {
			sz+=in[i]; if(!(S&s[i])) z+=sz;
		}
		int U=~S&((1<<X)-1); z+=f[0][U];
		for(int i=X;i<Y;++i) if(!(S>>i&1)) for(int j=i;j<Y;++j) if(!(S>>j&1)) z+=f[id[i][j]][U];
	}
	for(int i=1;i<=q;++i) cout<<Z[i]<<"\n";
	return 0;
}



*AD. [CF1737F] Ela and Prime GCD (7)

Problem Link

简单研究一下发现大部分序列都无解,考虑如果存在 \(b\ge 4\),那么这一维上任意 \(\min(a_i,a_{i-1})\le 1\),显然 \(0,1\) 的个数不够分。

所以首先所有 \(b\in[1,3]\),其次分析 \(0\) 的个数得到 \((m-1)\lfloor \frac n2\rfloor\le \sum_i\dfrac{n}{b_i+1}\),简单搜索一下合法序列只有 \([],[2],[3],[2,2],[2,3],[2,2,2],[3,3]\) 加上若干个 \(1\) 可能合法,且最后三种序列几乎把上面这个较宽松的界卡满,简单爆搜一下可以猜测这几种情况无解。

对于前四种情况可以直接爆搜出解,只要构造加入一个 \(b_i=1\) 的方案就行。

考虑把 \(a_1\sim a_n\) 变成 \(0a_1,1a_2,0a_3,\dots,0a_n,1a_n,0a_{n-1},\dots,1a_1,1a_0\),其中 \(a_0\) 是 \(0\) 向量。

不过这要求 \(\sum_i a_{n,i}=1\),除了 \(b=[1,1,\dots,1,3]\) 都可以。

对于这种情况,每次变成 \(a_1\sim a_n,1a_0,a_1\sim a_n\),然后把 \(1a_0\) 和 \(0a_{mid}\) 互换一下,高位大致上间隔填 \(0,1\),在 \(a_{mid}\) 旁边稍微调整一下。

时间复杂度 \(\mathcal O(nm)\)。

代码:

#include<bits/stdc++.h>
using namespace std;
int m,a[17];
void solve() {
	cin>>m;
	vector<int>b;
	vector<array<int,2>>c;
	for(int i=0,x;i<m;++i) {
		cin>>x,c.push_back({x,i});
		if(x>1) b.push_back(x);
	}
	vector<vector<int>>Z;
	if(b==vector<int>{3}) {
		Z={{2},{1},{3}};
		for(int o=2;o<=m;++o) {
			int k=Z.size(),p=k/2;
			Z.push_back(vector<int>(o,0)),Z.back()[0]=1;
			for(int i=0;i<k;++i) {
				Z.push_back(Z[i]);
				if(i==p) Z[i].push_back(1),Z.back().push_back(1),Z.back()[0]=0;
				else Z[i].push_back(i%2==p%2),Z.back().push_back(i%2!=p%2);
			}
		}
	} else {
		if(b==vector<int>{2}) Z={{2},{1}};
		else if(b==vector<int>{2,2}) Z={{2,2},{0,1},{1,2},{2,0},{1,1},{0,2},{2,1},{1,0}};
		else if(b.size()) return cout<<"-1\n",void();
		for(int o=b.size()+1;o<=m;++o) {
			for(int i=Z.size()-1;~i;--i) Z.push_back(Z[i]);
			for(int i=0;i<(int)Z.size();++i) Z[i].push_back(i&1);
			Z.push_back(vector<int>(o,0)),Z.back().back()=1;
		}
	}
	sort(c.begin(),c.end(),greater<>());
	for(int i=0;i<m;++i) a[c[i][1]]=i;
	for(auto&o:Z) for(int i=0;i<m;++i) cout<<o[a[i]]<<" \n"[i==m-1];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*AE. [CF2115F2] Gellyfish and Lycoris Radiata (9)

Problem Link

首先由于我们有删除操作,因此肯定要维护一些和历史版本有关的信息,在时间轴上考虑所有的操作。

首先我们尝试描述一个集合位置的变化,发现单个操作可以表示成 \(\mathcal O(1)\) 个信息,每个信息形如标号 \([l,r]\) 的集合平移到 \([l+d,r+d]\),可能会翻转顺序。

此时 \(len\) 个操作可以用 \(\mathcal O(len)\) 个信息描述,可以考虑按时间轴建立线段树,维护子树内操作复合得到的信息,注意每个点只在子树内被填满的时候计算信息。

然后考虑如何查询最小值,朴素的想法就是在信息上维护集合,但是复杂度较难接受。

考虑我们实际上只要找到首个插入操作的位置,可以直接在时间轴线段树上二分,每个信息只要维护这些集合是否被插入了至少一个元素,这个信息只要在合并时 \(\mathrm{OR}\) 一下就行。

这样就很优秀了,然后要处理删除操作,此时我们只要把所有此时被删空的信息更新。

我们不能直接在线段树上直接重建每个子树的信息和,考虑信息之间的关系。

在我们合并操作的过程中,每个新的信息来自左子树和右子树的一个信息的复合,只有这两个子信息都删空时该信息才被删空。

那么我们可以用一张 DAG 维护信息不断向上合并的过程,该 DAG 点数边数 \(\mathcal O(q\log q)\),深度 \(\mathcal O(\log q)\),由于每个信息只会被删空一次,所以只要快速找被删空的节点,显然直接在 DAG 上 dfs 本轮被删掉的所有节点复杂度就是正确的。

分析一下发现我们根本不需要线段树上二分,我们只要显式维护这个信息合并的 DAG,然后在信息之间从上往下走,根据每个子树是否被删空判断向下方向,优先走左子树即可,只需要用二进制分组来维护所有操作就行。

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

代码:

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=3e5+5,MAXM=1e7+5;
struct info { int s,t,d; bool c,v; } a[MAXM];
int n,m,q,ec,ls[MAXM],rs[MAXM],hd[MAXM],pr[MAXM*2],to[MAXM*2],rk[25],h;
void link(int x,int y) { to[++ec]=y,pr[ec]=hd[x],hd[x]=ec; }
basic_string<int>f[25],g[25];
void mrg(int x,int y) {
	auto i=g[x].begin(),j=f[y].begin();
	while(i!=g[x].end()&&j!=f[y].end()) {
		const info&u=a[*i],&v=a[*j];
		int l=max(u.t,v.s),r=min(u.t+u.d,v.s+v.d);
		a[++m]={u.s+(u.c?u.t+u.d-r:l-u.t),v.t+(v.c?v.s+v.d-r:l-v.s),r-l,u.c!=v.c,u.v||v.v};
		link(ls[m]=*i,m),link(rs[m]=*j,m);
		if(u.t+u.d==r) ++i;
		if(v.s+v.d==r) ++j;
	}
	f[0].clear(),g[0].clear();
	for(int o:f[x]) {
		int t=f[0].size();
		for(int e=hd[o];e;e=pr[e]) f[0]+=to[e];
		if(!a[o].c) reverse(f[0].begin()+t,f[0].end());
	}
	for(int o:g[y]) {
		int t=g[0].size();
		for(int e=hd[o];e;e=pr[e]) g[0]+=to[e];
		if(!a[o].c) reverse(g[0].begin()+t,g[0].end());
	}
	f[x].swap(f[0]),g[x].swap(g[0]),f[y].clear(),g[y].clear();
}
void del(int x) {
	for(int e=hd[x],y;e;e=pr[e]) {
		y=to[e],a[y].v=a[ls[y]].v||a[rs[y]].v;
		if(!a[y].v) del(y);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q,m=q;
	for(int i=1,o,x,y,z=0;i<=q;++i) {
		cin>>o>>x>>y,x=(x+z-1)%(o==3?q:n)+1,y=(y+z-1)%n+1,z=0;
		if(o<3) {
			rk[++h]=1,a[i].v=1,a[++m]={1,1,x-1,o==2,o==1},f[h]=g[h]={m};
			if(o==1) link(ls[m]=i,m);
			if(x<n) a[++m]={x+1,x+1,n-x-1,0,0},f[h]+=m,g[h]+=m;
			for(;h>1&&rk[h-1]==rk[h];--h) ++rk[h-1],mrg(h-1,h);
		} else if(a[x].v) a[x].v=0,del(x);
		for(int u=h,j=0;u;--u,j=0) {
			for(int k=1<<__lg(g[u].size());k;k>>=1) if(j+k<(int)g[u].size()&&a[g[u][j+k]].t<=y) j+=k;
			if(a[j=g[u][j]].v) z=j;
			y=a[j].c?a[j].s+a[j].t+a[j].d-y:a[j].s+y-a[j].t;
		}
		if(z) while(z>q) z=a[ls[z]].v?ls[z]:rs[z];
		cout<<z<<"\n";
	}
	return 0;
}



AF. [CF1740G] Dangerous Laser Power (4)

Problem Link

考虑如何计算答案,按 \(s\) 升序排序,动态维护每个面被多少激光射入,并查集快速维护下一个前缀最值的位置即可。

然后构造答案,我们发现每个点当前消耗的能量 \(c\) 已经确定了,直接令 \(t_{i,j}=c\bmod 2\) 就能满足这个点的限制,而这个过程可以一直重复,这样我们就构造了一个所有点都合法的方案,显然是最优的。

时间复杂度 \(\mathcal O(nm\log nm)\)。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005,MAXM=4e6+5,dx[]={-1,0,1,0},dy[]={0,1,0,-1};
int n,m,q,a[MAXN][MAXN],id[MAXN][MAXN][4],s[MAXM],t[MAXM],dsu[MAXM];
bool b[MAXN][MAXN],z[MAXN][MAXN],f[MAXM];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,q=0;
	vector<array<int,3>>it;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		cin>>a[i][j],it.push_back({a[i][j],i,j});
		for(int&o:id[i][j]) f[o=++q]=1,s[o]=i,t[o]=j;
	}
	iota(dsu,dsu+q+1,0),sort(it.begin(),it.end());
	for(auto o:it) {
		int x=o[1],y=o[2]; z[x][y]=b[x][y];
		for(int c:{0,1,2,3}) {
			int d=(c+2+b[x][y])%4,v=find(id[x+dx[d]][y+dy[d]][d^2]);
			if(f[id[x][y][c]]) f[v]^=1,b[s[v]][t[v]]^=(a[s[v]][t[v]]-a[x][y])&1;
			dsu[id[x][y][c]]=v;
		}
	}
	for(int i=1;i<=n;++i,cout<<"\n") for(int j=1;j<=m;++j) cout<<z[i][j];
	return 0;
}



*AG. [CF1776K] Uniform Chemistry (7)

Problem Link

首先可以猜测轮数很少,考虑每个点的坐标期望减半,所以取大概 \(K=120\) 轮就行。

可以简单 dp \(f_{i,j}\) 表示 \(i\) 轮后把 \(j\) 移动到 \(0\) 上的概率,我们要对每个 \(a_i\) 算 \(f_{0,a_i}\sim f_{K,a_i}\)。

对于较小的 \(a_i\) 可以直接 dp,较大的 \(a_i\) 考虑用连续的情况近似,也就是等概率变为 \([0,a_i)\) 中实数,求 \(j\) 步后首次 \(\le 1\) 的概率 \(g_j(i)\),由于 \(a\) 很大的时候 \(f\) 相当均匀,所以可以采取这种近似。

那么 \(g_k(x)=\dfrac{\int g_{k-1}(x)}x\),\(g_1=\dfrac 1x,g_2=\dfrac{\ln x}x,g_3=\dfrac 1x\int \dfrac{\ln x}x\mathrm dx\),运用 \(\dfrac{\mathrm d\ln x}{\mathrm dx}=\dfrac 1x\),所以 \(g_3(x)=\dfrac 1x\int \ln x\mathrm{d}\ln x=\dfrac {\ln^2 x}{2x}\),归纳可得 \(g_k(x)=\dfrac{\ln^{k-1}x}{(k-1)!x}\)。

不过当 \(x\) 很小的时候估算不准确,设定阈值 \(B=2\times 10^6\),如果我们落到 \(\le B\) 的范围就直接用 dp 出来的结果 \(f\),相当于计算 \(g_k(\frac{a_i}B)\times \frac 1B\sum_tf_{t,j-k}\)。

时间复杂度 \(\mathcal O(BK+mK^2)\)。

代码:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int K=128,B=2e6;
ll n,m,a[105];
ld f[B+5],g[K+5],p[105][K+5],s[105][K+5];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;++i) cin>>a[i],a[i]=n-a[i];
	for(int t=1;t<=K;++t) {
		ld &w=g[t-1]=(t==1),o;
		for(int i=1;i<B;++i) o=w/i,w+=f[i],f[i]=o;
		for(int i=1;i<=m;++i) if(a[i]<B) p[i][t]=f[a[i]];
	}
	for(int i=1;i<=m;++i) if(a[i]>=B) for(int t=1;t<=K;++t) {
		ld o=logl((ld)a[i]/B),w=1;
		for(int j=1;j<=t;++j) p[i][t]+=w*g[t-j]/a[i],w*=o/j;
	}
	for(int i=1;i<=m;++i) for(int j=1;j<=K;++j) s[i][j]=s[i][j-1]+p[i][j];
	for(int i=1;i<=m;++i) {
		ld z=0;
		for(int j=1;j<=K;++j) {
			ld w=p[i][j];
			for(int t=1;t<=m;++t) if(i!=t) w*=1-s[t][j-1];
			z+=w;
		}
		cout<<fixed<<setprecision(20)<<z<<" \n"[i==m];
	}
	return 0;
}



AH. [CF2206G] Extra Transition (4)

Problem Link

手玩一下发现合法的图可以通过广义串并联图方法变成 \((1,n)\),且这是充分必要的。

然后考虑如何计算边数,可以发现 compress 两个簇的时候可以把两个簇之间的点连起来,如果 twist 两个簇则内部的边不能在后续 compress 时向外连边,也可以在过程中维护。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN];
map <int,int> E[MAXN];
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],E[i].clear();
	ll z=0;
	for(int i=1,u,v;i<=m;++i) cin>>u>>v,E[u][v]+=0,E[v][u]+=0,z-=a[u]*a[v];
	queue <int> Q;
	for(int i=2;i<n;++i) if(E[i].size()<=2) Q.push(i);
	while(Q.size()) {
		int u=Q.front(); Q.pop();
		if(E[u].empty()) continue;
		if(E[u].size()==1) return cout<<"bad\n",void();
		int x=E[u].begin()->first,y=E[u].rbegin()->first;
		int l=E[u].begin()->second,r=E[u].rbegin()->second;
		z+=1ll*l*r+1ll*l*a[y]+1ll*r*a[x]+a[u]*a[x]+a[u]*a[y];
		if(E[x].count(y)) E[x][y]=E[y][x]=0;
		else E[x][y]=E[y][x]+=l+r+a[u];
		E[x].erase(u),E[y].erase(u),E[u].clear();
		if(1<x&&x<n&&E[x].size()<=2) Q.push(x);
		if(1<y&&y<n&&E[y].size()<=2) Q.push(y);
	}
	for(int i=2;i<n;++i) if(E[i].size()) return cout<<"bad\n",void();
	cout<<z+a[1]*a[n]<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}
posted @ 2026-05-25 15:46  DaiRuiChen007  阅读(226)  评论(0)    收藏  举报