2026 NOI 做题记录(十八)



Contest Link

\(\text{By DaiRuiChen007}\)



A. [QOJ5099] 朝圣道 (1)

Problem Link

看成 \(2n\) 次走 \(\pm 0.5\),答案是 \(\sum_{i>n}\binom{2n}i(2i-n)\),其中 \(\sum\binom{2n}i=2^{2n-1}-\binom{2n}{n}\),可以用组合数表示答案,Ex-Lucas 计算。

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

代码:

#include<bits/stdc++.h>
#include "pilgrimage.h"
#define ll long long
using namespace std;
const int MAXN=1e6+5;
ll ksm(ll a,ll b,ll p) { ll s=1; for(;b;a=a*a%p,b>>=1) if(b&1) s=s*a%p; return s; }
int hd[20],m,c[20];
ll P,p[20],q[20],pw[20][20],h[20],s[MAXN*2],i2;
void init(int,int o) {
	int x=P=o; m=0,i2=(P+1)/2;
	for(int i=2;i*i<=x;++i) if(x%i==0) for(p[++m]=i,q[m]=pw[m][c[m]=0]=1;x%i==0;x/=i,pw[m][++c[m]]=q[m]*=i);
	if(x>1) ++m,p[m]=q[m]=pw[m][c[m]=1]=x,pw[m][0]=1;
	for(int i=1;i<=m;++i) {
		s[hd[i]]=1,hd[i+1]=hd[i]+q[i],h[i]=q[i]-q[i]/p[i];
		for(int j=1;j<q[i];++j) s[hd[i]+j]=s[hd[i]+j-1]*(j%p[i]?j:1)%q[i];
	}
}
ll F(ll n,int k) {
	ll z=1,w=0;
	for(;n;n/=p[k]) z=z*s[hd[k]+n%q[k]]%q[k],w+=n/q[k];
	return w&1?q[k]-z:z;
}
ll G(ll n,int k) { ll z=0; for(;n/=p[k];z+=n); return z; }
ll iv(ll x,int k) { return ksm(x%q[k],h[k]-1,q[k]); }
ll C(ll n) {
	ll z=0,t;
	for(int i=1;i<=m;++i) t=F(n,i),z=(z+F(2*n,i)*iv(P/q[i]*t*t,i)*pw[i][min(G(2*n,i)-2*G(n,i),(ll)c[i])]*(P/q[i]))%P;
 	return z;
}
int ask(ll n) { return n%P*C(n)*ksm(i2,2*n,P)%P; }



B. [QOJ5171] 理论出线 (4)

Problem Link

考虑如何判断一个超过 UFT 的选手集合 \(S\) 是否合法。

看成把每场比赛匹配一个选手,\(S\) 中选手可以匹配 \(\infty\) 个比赛,其他选手只能匹配 \(v-w_i\) 个。

考虑 Hall 定理,存在完美匹配当且仅当 \(\forall [l,r]\),内部被至少一个选手覆盖的比赛数量总和不超过所有经过 \([l,r]\) 的选手可匹配比赛数之和。

那么把 \(S\) 中元素按 \(l\) 排序,判定时只要检验所有相邻的 \([r_{s_i}+1,l_{s_{i+1}}-1]\) 内部的每个子区间即可,直接按顺序 dp 记录最后一个 \(S\) 中元素,容易做到 \(\mathcal O(n^2+m^2)\)。

检验 \([l,r]\) 合法最终能写成 \(f_l+g_r\le 0\) 的形式,因此判断所有子区间只要线段树维护区间最大子段和,而每个 \(f_i\) 能够转移的位置是一段区间 \(f[j,i-1]\),可以使用线段树维护信息和。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,N=1<<19,MOD=1e9+7;
struct info { int c,s; } f[MAXN];
info operator +(const info&u,const info&v) {
	return u.c^v.c?(u.c<v.c?u:v):info{u.c,(u.s+v.s)%MOD};
}
struct mss { ll l,r,x; };
mss operator +(const mss&a,const mss&b) {
	return {max(a.l,b.l),max(a.r,b.r),max({a.x,b.x,a.l+b.r})};
}
struct seg { int l,r,w; } a[MAXN];
ll s[MAXN];
int L[MAXN],R[MAXN],c[MAXN];
template<typename T>struct Segt {
	T tr[N*2+5];
	void upd(int x,const T&z) {
		for(tr[x+=N]=z,x>>=1;x;x>>=1) tr[x]=tr[x<<1]+tr[x<<1|1];
	}
	T qry(int l,int r) {
		if(l==r) return tr[l+N];
		T sl=tr[l+=N],sr=tr[r+=N];
		for(;l^r^1;l>>=1,r>>=1) {
			if(~l&1) sl=sl+tr[l^1];
			if(r&1) sr=tr[r^1]+sr;
		}
		return sl+sr;
	}
};
Segt<mss>T; Segt<info>F;
bool ok(int l,int r) { return l>r||T.qry(l,r).x<=0; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,m,v,_;
	cin>>n>>m>>v>>_;
	for(int i=1;i<=n;++i) cin>>a[i].w>>a[i].l>>a[i].r;
	sort(a+1,a+n+1,[&](auto x,auto y){ return x.l^y.l?x.l<y.l:x.r<y.r; });
	for(int i=1;i<=m+1;++i) L[i]=n+1;
	for(int i=n;i>=1;--i) L[a[i].r]=i;
	for(int i=1;i<=n;++i) R[a[i].l]=i,s[i]=s[i-1]+v-a[i].w;
	for(int i=m;i>=1;--i) L[i]=min(L[i],L[i+1]);
	for(int i=1;i<=m;++i) R[i]=max(R[i],R[i-1]),c[i]=c[i-1]+(L[i]<=R[i]);
	for(int i=1;i<=m;++i) T.upd(i,{s[L[i]-1]-c[i-1],c[i]-s[R[i]],c[i]-c[i-1]-s[R[i]]+s[L[i]-1]});
	f[0]={0,1},F.upd(0,f[0]),a[n+1]={m+1,m+1,0};
	for(int i=1,j=0;i<=n+1;++i) {
		for(;!ok(a[j].r+1,a[i].l-1);++j);
		f[i]=F.qry(j,i-1);
		if(i>n) break;
		++f[i].c,F.upd(i,f[i]);
		if(a[i].w>v) j=i;
	}
	if(_) cout<<n-f[n+1].c<<" "<<f[n+1].s<<"\n";
	else cout<<n-f[n+1].c<<"\n";
	return 0;
}



*C. [QOJ1868] The Struggle (7.5)

Problem Link

记 \(n\) 为 \(E\) 中点的 \(x,y\) 最大值。

首先考虑刻画这个结构,显然我们要考虑一些二进制状的东西来处理 \(x\oplus y\),那么分成 \(\mathcal O(n\log n)\) 个 \(x\times [y2^k,(y+1)2^k)\) 的结构是比较自然的,但这个做法很难做到 \(\mathcal O(n\log n)\)。

需要一些进一步的刻画,显然性质更优美的结构就是 \([x2^k,(x+1)2^k)\times [y2^k,(y+1)2^k)\),一个朴素的划分方式就是按 \(k\) 从 \(\log n\) 到 \(1\) 枚举,完全被 \(E\) 包含就直接划分,否则如果和 \(E\) 有交就令 \(k\gets k-1\) 并递归上下左右四个子矩形。

分析一下这样的矩形个数,可以发现无论是哪种双曲线都能按最上下左右的位置分成四条单调曲线下的区域,我们只考虑一个这样的区域,也就是 \(\{(x,y)\mid y\le h(x)\}\),其中 \(h\) 递增。

那么对于所有的 \(k\) 级矩形,其必然被一个 \(k+1\) 级矩形包含,而这个 \(k+1\) 矩形必定和 \(h(x)\) 函数图像有交,显然所有这样的矩形之间只能通过 \(x,y\) 增加得到,所以只有 \(\mathcal O(n/2^k)\) 个这样的矩形。

那么对于所有划分出的矩形的 \(\sum 2^k\) 就是 \(\mathcal O(n\log n)\) 级别。

考虑如何解决一个矩形内的询问,显然可以直接 XOR 卷积,对 \(x,y\) 的权值序列 \(X,Y\) FWT 后点乘并 IFWT,最后乘上 \(x\oplus y\) 的权值序列 \(Z\),即 \(\mathrm{IFWT}(\mathrm{FWT}(X)\cdot\mathrm{FWT(Y)})\cdot Z\)。

显然这个形式不够优美,转置后可以得到答案等于 \(\mathrm{IFWT}(Z)\cdot\mathrm{FWT}(X)\cdot\mathrm{FWT(Y)}\)。

那么我们对每个矩形 \([x2^k,(x+1)2^k)\times [y2^k,(y+1)2^k)\),只要把 \(X_i=(x+i)^{-2},Y_i=(y+i)^{-1},Z_i=(x\oplus y+i)^{33}\) 对应的 \(\mathrm{IFWT}(Z)\cdot\mathrm{FWT}(X)\cdot\mathrm{FWT(Y)}\) 算出来即可。

但对每个矩形都 \(\mathcal O(k2^k)\) 计算无法接受,但注意到对更大的 \(k'\) 求 \(\mathrm{FWT}\) 时,只操作完前 \(k\) 位时的 \(X[x2^k,(x+1)2^k)\) 就是 \(\mathrm{FWT}(X)\),所以只要进行一次 FWT,然后把每个矩形按 \(k\) 离线下来统计答案即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1<<22|5,MOD=1e9+7;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n; ll a,b,c,d,e,w,f[MAXN],g[MAXN],h[MAXN];
basic_string<array<int,2>>F[23];
bool ok(ll A,ll B,ll C,ll L,ll R) { return B*B>=4*A*C&&L*2*A<=-B&&-B<=R*2*A; }
void dfs(int xl,int yl,int k) {
	int xr=xl+(1<<k)-1,yr=yl+(1<<k)-1,ic=0;
	for(int x:{xl-b,xr-b}) for(int y:{yl-d,yr-d}) ic+=a*x*x+c*y*y+e*x*y<=w;
	if(ic==4) return F[k].push_back({xl,yl}),n=max({n,xr,yr}),void();
	bool o=ic>0||(xl<=b&&b<=xr&&yl<=d&&d<=yr);
	for(int x:{xl-b,xr-b}) o|=ok(c,e*x,a*x*x-w,yl-d,yr-d);
	for(int y:{yl-d,yr-d}) o|=ok(a,e*y,c*y*y-w,xl-b,xr-b);
	if(o&&k) for(int x:{0,1}) for(int y:{0,1}) dfs(xl+(x<<(k-1)),yl+(y<<(k-1)),k-1);
}
void fwt(ll&x,ll&y) { ll u=x+y>=MOD?x+y-MOD:x+y,v=x>=y?x-y:x+MOD-y; x=u,y=v; }
void solve() {
	cin>>a>>b>>c>>d>>e>>w;
	n=0,dfs(0,0,22),n=__lg(n)+1;
	for(int i=0;i<(1<<n);++i) f[i]=ksm(i,MOD-3),g[i]=ksm(i,MOD-2),h[i]=ksm(i,33);
	ll z=0;
	for(int k=0;k<n;F[k++].clear()) {
		for(auto o:F[k]) for(int s=0,x=o[0],y=o[1],p=x^y,iv=ksm((MOD+1)/2,k);s<(1<<k);++s) z=(z+f[x+s]*g[y+s]%MOD*h[p+s]%MOD*iv)%MOD;
		for(int s=0;s<(1<<n);++s) if(s>>k&1) for(auto&Z:{f,g,h}) fwt(Z[s^(1<<k)],Z[s]);
	}
	cout<<z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*D. [QOJ5176] 多控制反转 (7)

Problem Link

考虑 \(m\ge 2n+2\) 但初值未知时怎么做,显然要删点递归,设 \(f(X,c)\) 表示让 \(x_c\) 异或上 \(\mathrm{AND}_{i\in X}x_i\)。

取出 \(x_{n-1},x_m\),设 \(X\setminus\{n-1\}=X'\),则手玩发现只要把 \(x_n\) 异或上 \(x_{n-1}\operatorname{AND}x_m\),再执行 \(f(X',m)\) 并把 \(x_n\) 再次异或上 \(x_{n-1}\operatorname{AND}x_m\) 即可。

那么只有一个元素的时候,我们把 \([0,n)\) 分成两个大小相同的集合 \(X,Y\),互为对方的辅助位。

为了保证 \(f\) 过程辅助位不变,我们在最后操作一次 \(f(X',m)\) 即可还原。

那么合并 \(X,Y\) 的过程仿照上面的方式,操作 \(f(X,n+1)\),然后操作 \(f(Y\cup\{n+1\},n)\),再重复上过程一遍即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
vector<array<int,4>>Z;
void opr(int x,int y=0,int z=0) { Z.push_back({3-!y-!z,x,y,z}); }
void solve(vector<int>a,vector<int>b,int c,bool o=0) {
	if(a.size()==0) return opr(c);
	if(a.size()==1) return opr(a[0],c);
	if(a.size()==2) return opr(a[0],a[1],c);
	int x=a.back(),y=b.back(); a.pop_back(),b.pop_back();
	opr(x,y,c),solve(a,b,y),opr(x,y,c);
	if(o) solve(a,b,y);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,m,q,_; cin>>n>>m>>q>>_;
	if(!n) return cout<<"1\n1 0\n",0;
	if(n==1) return cout<<"1\n2 0 1\n",0;
	if(n==2) return cout<<"1\n3 0 1 2\n",0;
	vector<int>L,R,t;
	for(int i=0;i<n;++i) (i<n/2?L:R).push_back(i);
	t=L,t.push_back(n+1);
	for(int o:{0,1}) solve(R,L,n+1,1),solve(t,R,n,1);
	cout<<Z.size()<<"\n";
	for(auto&o:Z) for(int i=0;i<=o[0];++i) cout<<o[i]<<" \n"[i==o[0]];
	return 0;
}



*E. [QOJ17186] 哈夫曼树 II (7)

Problem Link

用 Package-Merge 算法解决单次询问。

设 \(w\) 单调递减,且 \(w_i\) 所在叶子深度为 \(d_i\),构造集合 \(S=\{(j,i)\mid 1\le j\le d_i\}\)。

那么树的权值就是 \(\sum_{(j,i)\in S}w_i\),而 \(\sum _{(j,i)\in S} 2^{-j}=\sum_i 1-2^{-d_i}=n-1\)。

接下来我们证明只要在所有 \(j\le \mathrm{d_lim}\) 的点中选出 \(\sum 2^{-j}=n-1\) 且 \(\sum w_i\) 最小的点集即为所求。

首先如果选了 \((j,i)\) 没选 \((j-1,i)\) 则换成 \((j-1,i)\),此时 \(\sum 2^{-j}=n-1+2^{-j}\),每次删掉 \(j\) 最大的元素,一定能调整成一个和更小的 \(\sum 2^{-j}=n-1\) 的点集。

而对于一个这样的 \((j,i)\in S\implies (j-1,i)\in S\) 的点集,我们就得到了一组 \(d_i\) 满足 \(\sum2^{-d_i}=1\),则按照求和的过程,每次把最大的 \(d_i\) 的两个叶子合并起来就得到一组合法的哈夫曼树构造。

所以只要变成一个背包问题,把 \(j=\mathrm{d_{lim}}\sim 1\) 的分别考虑,\(j+1\to j\) 时显然只要把所有元素从小到大排序,然后相邻两个重量为 \(2^{-j-1}\) 的元素变成一个重量为 \(2^{-j}\) 的元素,再加上 \(n\) 个原始权重 \(2^{-j}\) 的元素即可。

可以发现对于每个询问的 \(d\) 只要从 \(d-1\) 上得到的元素再合并一轮,而 \(d\) 较大的时候答案就是无限制哈夫曼树。

考虑分析无限制哈夫曼树的深度 \(k\) 的级别,把最长链以外的子树变成叶子,则存在 \(k+1\) 个数的哈夫曼树是链,构造发现这些数最小时是斐波那契数列,所以 \(k\) 是 \(\mathcal O(\log_{\varphi}(nV))\) 级别,只要对该范围内每个 \(d\) 预处理出答案即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1<<18|5;
int n,m,q; ll a[MAXN],f[MAXN*2],z[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	sort(a+1,a+n+1);
	for(int k=0;k<=70;++k) {
		m/=2;
		for(int i=1;i<=m;++i) f[i]=f[2*i-1]+f[2*i];
		if(m==n-1) z[k]=accumulate(f+1,f+m+1,0ll);
		for(int i=1;i<=n;++i) f[m+i]=a[i];
		inplace_merge(f+1,f+m+1,f+m+n+1),m+=n;
	}
	for(int i=1,d;i<=q;++i) cin>>d,cout<<z[min(d,70)]<<"\n";
	return 0;
}



F. [QOJ5013] Astral Birth (3.5)

Problem Link

每个子区间要么贡献 \(0\) 个数要么贡献 \(1\) 个数,然后选一个子区间前缀贡献 \(0\) 后缀贡献 \(1\)。

对 \(m\) 取前缀 \(\max\),则我们可以钦定贡献 \(0/1\) 个数的段是间隔出现的。

我们发现 \(m>1\) 时可以直接看成分成 \(m+1\) 段,每个段间隔贡献段内 \(0/1\) 个数,然后找相邻两个贡献 \((0,1)\) 的段合并起来即可。

那么只要对每个 \(m\) 求答案,\(m=2k+1\) 时,假设两侧的段贡献 \(0\),那么设 \(b_i=2a_i-1\),则答案就是选择 \(k\) 给不相交子区间,最大化内部的 \(\sum b\) 的结果,这是经典反悔贪心问题,当然也可以用线段树解决。

\(m=2k\) 时可以把开头或结尾的 \(b\) 变成 \(\infty\) 从而钦定这一段贡献 \(1\)。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,inf=1e9;
int n,m,a[MAXN],b[MAXN],f[MAXN],g[MAXN],l[MAXN],r[MAXN];
bool in[MAXN];
void solve() {
	m=0,memset(g,0,sizeof(g)),memset(in,0,sizeof(in));
	if(*max_element(a+1,a+n+1)<0) return ;
	for(int i=1;i<=n;++i) {
		if(!m||(a[i]>0)!=(b[m]>0)) b[++m]=a[i];
		else b[m]+=a[i];
	}
	if(b[m]<0) --m;
	if(b[1]<0) rotate(b+1,b+2,b+m+1),--m;
	int k=0,s=0; l[m+1]=m,r[0]=1;
	priority_queue<array<int,2>>Q;
	for(int i=1;i<=m;++i) k+=b[i]>0,s+=max(b[i],0),l[i]=i-1,r[i]=i+1,Q.push({-abs(b[i]),i}),in[i]=1;
	g[k]=s;
	auto del=[&](int x) { if(1<=x&&x<=m) l[r[x]]=l[x],r[l[x]]=r[x],in[x]=0; };
	for(int t=k-1,x;t>0;--t) {
		while(!in[Q.top()[1]]) Q.pop();
		g[t]=s+=Q.top()[0],x=Q.top()[1],Q.pop();
		if(l[x]<1||r[x]>m) del(x),del(l[x]),del(r[x]);
		else b[x]+=b[l[x]]+b[r[x]],del(l[x]),del(r[x]),Q.push({-abs(b[x]),x});
	}
	for(int i=1;i<=n;++i) g[i]=max(g[i],g[i-1]);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n; int s=0;
	for(int i=1;i<=n;++i) { char c; cin>>c,a[i]=(c=='1'?1:-1),s+=c=='0'; }
	int z0=s;
	for(int i=n,x=s,y=0;i>=1;--i) (a[i]>0?++y:--x),z0=max(z0,x+y);
	for(int o:{0,1}) {
		for(int i=1;i<=n;++i) a[i]*=-1;
		s=n-s,solve();
		for(int i=1;i<=n;i+=2) f[i]=max(f[i],g[i/2]+s);
	}
	a[1]+=inf,solve();
	for(int i=2;i<=n;i+=2) f[i]=max(f[i],g[i/2]-inf+s);
	a[1]-=inf,a[n]+=inf,solve();
	for(int i=2;i<=n;i+=2) f[i]=max(f[i],g[i/2]-inf+s);
	for(int i=1;i<=n+1;++i) f[i]=max(f[i],f[i-1]);
	cout<<z0<<" ";
	for(int i=2;i<=n;++i) cout<<f[i+1]<<" \n"[i==n];
	return 0;
}



*G. [QOJ5177] 摩斯电码 2.0 (7)

Problem Link

二分答案 \(K\),那么我们要最小化 \(\sum_{i=2}^{n} \left\lfloor\dfrac{v_iK-1}{v_1}\right\rfloor\)。

考虑如何分配 \(a_i\),显然 \(a_1,a_n\to v_1\),对于剩下的 \(a\),对 \(\sum v_iK-1\) 的贡献一定,所以只要最大化 \(\sum (v_iK-1)\bmod v_1\) 即可。

我们声称从左到右分配 \(a_i\) 时最大化 \(r_i=(v_iK-1)\bmod v_1\) 即可,这是因为如果 \(r_i\) 没有取到最大值 \(r_0\),则设差为 \(d\),则调整后 \(r_i\gets r_i+d,r_{i+1}\gets (r_{i+1}-d)\bmod v_1\),变化量 \(\in\{0,v_1\}\) 一定不劣。

所以我们只要计算等差数列取模最大值,即 \(\max_{0\le x\le n} (px+r)\bmod q\),直接万能欧几里得解决。

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

代码:

#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const ll inf=1e18;
struct info { ll x,w; };
info operator *(const info&L,const info&R) { return {L.x+R.x,max(L.w,L.x+R.w)}; }
info ksm(info a,int b) {
	info x{0,-inf};
	for(;b;a=a*a,b>>=1) if(b&1) x=x*a;
	return x;
}
info euclid(ll p,ll q,ll r,ll n,info X,info Y) {
	if((LL)n*p+r<q) return ksm(Y,n);
	if(p>=q) return euclid(p%q,q,r,n,X,ksm(X,p/q)*Y);
	ll m=((LL)n*p+r)/q; swap(X,Y);
	return ksm(X,(q-r-1)/p)*Y*euclid(q,p,(q-r-1)%p,m-1,X,Y)*ksm(X,n-((LL)q*m-r-1)/p);
}
ll f(ll p,ll q,ll r,ll n) { //max(px+r)%q
	info z={r,r};
	z=z*euclid(p,q,r,n,{-q,-inf},{p,p});
	return z.w;
}
const int MAXN=1e5+5;
int n,m; ll a[MAXN],b[MAXN];
bool chk(ll k) {
	LL s=0,p=b[1],r=0;
	for(int i=2;i<n;++i) {
		ll h=(b[i]*k+r+p-1)%p,o=f(k,p,h,a[i]);
		s+=k*a[i]-o+k*b[i]-1,r=(k*a[i]-o+h+p)%p;
	}
	s+=k*b[n]-(b[n]*k+r+p-1)%p;
	return k+s/p<=m;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>b[i];
	for(int i=1;i<=n;++i) cin>>a[i];
	int z=0; b[1]+=a[1]+a[n];
	for(int k=1<<30;k;k>>=1) if(z+k<=m&&chk(z+k)) z+=k;
	cout<<z<<"\n";
	return 0;
}



H. [QOJ1871] Typing Contest (3.5)

Problem Link

令 \(f_i\gets 100\times f_i\),枚举 \(s=\sum f_k\),然后 \(\mathcal O(ns)\) 暴力 dp。

考虑 \(s\) 较大时不一定合法,我们需要 \(10^4+f_i^2-f_is>0\) 的元素的 \(\sum f_i\ge s\)。

设这样的元素为 \(1\sim k\) 个,则根据 \(f_is-f_i^2<10^4\) 以及 \(f_i^2s-f_i^3<10^4f_i\),累加可以得到 \(10^4\times k>s^2-\sum f_i^2,10^4\times s>s\sum f_i^2-\sum f_i^3\)。

所以 \(s^2<10^4\times k+\sum f_i^2<10^4\times(k+1)+\dfrac{\sum f_i^3}{s}\le 10^4\times(k+2)\),其中 \(\dfrac{\sum f_i^3}s\le 10^4\) 是因为任意 \(\dfrac{f_i^3}{f_i}\le 10^4\),所以只要考虑 \(s\le 100\sqrt{n+2}\) 的范围。

时间复杂度 \(\mathcal O(n^2C)\),其中 \(C=10^4\)。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,b[105];
ll a[105],f[10005],z;
void solve() {
	scanf("%d",&n),z=0;
	for(int i=1;i<=n;++i) { double r; scanf("%lld%lf",&a[i],&r),b[i]=round(r*100); }
	for(int h=0;;++h) {
		int c=0;
		for(int i=1;i<=n;++i) if(b[i]*(b[i]-h)+10000>0) c+=b[i];
		if(c<h) break;
		memset(f,0,(h+1)*8);
		for(int i=1;i<=n;++i) for(int j=h;j>=b[i];--j) f[j]=max(f[j],f[j-b[i]]+(10000+b[i]*(b[i]-h))*a[i]);
		z=max(z,f[h]);
	}
	printf("%lld.%04lld00000\n",z/10000,z%10000);
}
signed main() {
	int _; scanf("%d",&_) ;
	while(_--) solve();
	return 0;
}



*I. [QOJ1866] Decomposition (7)

Problem Link

考虑构造一条欧拉路径满足同一个点距离 \(\ge n-2\)。

取一个点在中间,然后剩下的 \(2k\) 个点在弧上构造 \(k\) 条旋转得到的路径,要求每种差只在一条路径中出现 \(2\) 次。

考虑交错序列,每个点到上一个距离为 \(+1,-2,+3,\dots\),可以证明该路径合法。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int a[MAXN],n,m,q;
void solve() {
	cin>>n>>q,a[m=1]=--n;
	for(int i=0;i<n/2;++i) {
		for(int j=0,x=i,y=i;j<n;++j) {
			if(j&1) a[++m]=x,x=(x+1)%n;
			else y=(y+n-1)%n,a[++m]=y;
		}
		a[++m]=n;
	}
	for(int h=1,d;q--;) {
		cin>>d,h+=d;
		for(int i=h-d;i<=h;++i) cout<<a[i]+1<<" \n"[i==h];
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	for(int i=1;i<=_;++i) cout<<"Case #"<<i<<":\n",solve();
	return 0;
}



J. [QOJ5170] 加速度 (5)

Problem Link

考虑手玩决策,发现我们肯定是以最大速度移动,直到某个 \(s_i\) 到达时刻小于 \(l_i\)。

此时最优的决策是在起点上等待一段时间,这样不会影响最终速度,如果无法调整说明我们到达了某个 \((s_j,r_j)\)。

此时我们必须在位置 \(s_j\) 上做出调整,先考虑加速度是否会变化,对于一段时间 \(t\),设出末速度为 \(v_0,v_t\),画出 \(t-v\) 图像 \(f\),则位移 \(x\) 是曲线下面积。

考虑过 \((0,v_0),(t,v_t)\) 的斜率为 \(a\) 的直线 \(l_0,l_t\),发现任意 \(l_t(p)\le f(p)\le l_0(p)\),那么我们把直线从 \(l_0\to l_t\) 移动时一定经过某个 \(l\) 满足其直线下面积为 \(x\),按照该线段来位移,则初速度 \(\le v_0\) 末速度 \(\ge v_t\),显然更优秀。

则 \((s_j,r_j)\) 处的决策肯定是先减速度,速度为 \(0\) 再增加等待时间。

所以答案结构一定是不断进行加速度为 \(a\) 的运动,只会在某些 \((s_i,r_i)\) 处调整速度,记录到这些点的最大速度即可。

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

代码:

#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=5005;
const ld eps=1e-10;
int n,a,d[MAXN],l[MAXN],r[MAXN];
ld f[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>a;
	for(int i=0;i<=n;++i) cin>>d[i];
	for(int i=0;i<=n;++i) cin>>l[i]>>r[i];
	ld z=2e9; r[0]=0;
	for(int i=0;i<n;++i) {
		ld lv=0,rv=f[i],lw=0,rw=2e9;
		for(int j=i+1;j<=n&&r[i]<r[j]&&lv<rv+eps;++j) {
			ld s=d[j]-d[i],t=r[j]-r[i],v=s/t-a*t/2;
			if(lv-eps<v&&v<rv+eps) f[j]=max(f[j],v+a*t);
			lv=max(lv,v);
			if(l[j]>r[i]) t=l[j]-r[i],rv=min(rv,s/t-a*t/2);
			if(j==n) z=min(z,r[i]+(sqrtl(rv*rv+2*a*s)-rv)/a);
		}
		for(int j=i+1;j<=n&&r[i]<r[j]&&lw<rw+eps;++j) {
			ld s=d[j]-d[i],t=sqrtl(2*s/a),w=r[j]-r[i]-t;
			if(lw-eps<w&&w<rw+eps) f[j]=max(f[j],a*t);
			rw=min(rw,w),lw=max(lw,l[j]-r[i]-t);
			if(j==n) z=min(z,r[i]+lw+t);
		}
	}
	cout<<fixed<<setprecision(18)<<z<<"\n";
	return 0;
}



*K. [QOJ1197] Draw in Straight Lines (7.5)

Problem Link

首先这种困难最优化考虑使用流模型,注意到每个点的决策几乎是 \(\{0,1\}\) 状物,不妨考虑最小割建模,\(x_u=0/1\) 表示该点和源点或汇点连通,从而用建边表示 \(x_u=0,x_v=1\) 时产生额外代价 \(w(u,v)\)。

那么对每个点记录 \(ax,ay,bx,by\) 表示是否被横向或纵向染成黑色或白色。

对于一个黑点,必定有 \(bx=by=0\),且 \(ax=ay=0\) 时产生 \(+c\) 贡献,\(ax=1\) 或 \(a_y=1\) 都产生 \(+a\) 贡献。

如果 \(ax=1\) 左边格子 \(ax=0\) 产生 \(+b\) 贡献,\(ay=1\) 同理,这样能正确处理 \(ax,ay\) 的贡献。

对于 \(bx,by\),处理方式类似,对于一个白格子,我们发现 \(ax=bx=1\) 是不优的,因为同一行中用白色染区间不如把左右黑色i区间分开染。

所以 \(ax=1\implies bx=0,ay=1\implies by=0\),且 \(ax,ay\) 不同时为 \(1\),这样就能满足每个点至多染两次。

此时 \(ax=1,by=0\) 或 \(ay=1,bx=0\) 产生 \(+c\) 贡献,最终只要把 \(ax,by\) 和源点连通设为 \(0\),\(ay,bx\) 和汇点连通设为 \(0\) 即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXV=7005,MAXE=2e6+5,inf=1e9;
namespace F {
struct Edge {
	int v,f,lst;
}	G[MAXE];
int S,T,tot=1,hd[MAXV],cur[MAXV],dep[MAXV];
void init() { tot=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int w) { G[++tot]={v,w,hd[u]},hd[u]=tot; }
void link(int u,int v,int w) { adde(u,v,w),adde(v,u,0); }
bool BFS() {
	memcpy(cur,hd,sizeof(cur)),memset(dep,-1,sizeof(dep));
	queue <int> Q;
	Q.push(S),dep[S]=0;
	while(!Q.empty()) {
		int u=Q.front(); Q.pop();
		for(int i=hd[u];i;i=G[i].lst) if(G[i].f&&dep[G[i].v]==-1) {
			dep[G[i].v]=dep[u]+1,Q.push(G[i].v);
		}
	}
	return ~dep[T];
}
int dfs(int u,int f) {
	if(u==T) return f;
	int r=f;
	for(int i=cur[u];i;i=G[i].lst) {
		int v=G[cur[u]=i].v;
		if(G[i].f&&dep[v]==dep[u]+1) {
			int g=dfs(v,min(r,G[i].f));
			if(!g) dep[v]=-1;
			G[i].f-=g,G[i^1].f+=g,r-=g;
		}
		if(!r) return f;
	}
	return f-r;
}
int Dinic() {
	int f=0;
	while(BFS()) f+=dfs(S,inf);
	return f;
}
}
using F::link;
int n,m,A,B,C,s=F::S=1,t=F::T=2,o=2,ax[45][45],ay[45][45],bx[45][45],by[45][45];
char S[45][45];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>A>>B>>C;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		cin>>S[i][j]; for(auto&M:{ax,ay,bx,by}) M[i][j]=++o;
	}
	for(int j=1;j<=m;++j) ax[0][j]=s,bx[0][j]=t;
	for(int i=0;i<=n;++i) ay[i][0]=t,by[i][0]=s;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		link(s,ax[i][j],A);
		link(ay[i][j],t,A);
		link(ax[i-1][j],ax[i][j],B);
		link(ay[i][j],ay[i][j-1],B);
		link(s,by[i][j],A);
		link(bx[i][j],t,A);
		link(by[i][j-1],by[i][j],B);
		link(bx[i][j],bx[i-1][j],B);
		if(S[i][j]=='#') {
			link(ax[i][j],ay[i][j],C);
			link(s,by[i][j],inf);
			link(bx[i][j],t,inf);
		} else {
			link(bx[i][j],ax[i][j],inf);
			link(ay[i][j],by[i][j],inf);
			link(by[i][j],ax[i][j],C);
			link(ay[i][j],bx[i][j],C);
			link(ay[i][j],ax[i][j],inf);
		}
	}
	cout<<F::Dinic()<<"\n";
	return 0;
}



*L. [QOJ5215] 多项式 (8)

Problem Link

首先枚举 \(f\) 每一项的出现次数,根据 Lucas 定理其必须是 \(m\) 的子集,那么设 \(m=\sum 2^{m_i}\),那么对每个 \(2^{m_i}\) 选一个 \(f\) 中对应的项加起来即可。

所以计算单项的值可以按二进制 dp,从下到上维护 \(\sum 2^{m_i}f_i\) 的加法过程的进位即可。

注意到进位 \(\le n\),所以可以考虑 dp of dp。

此时一个 \(k\) 的 \([x^k]F\) 可以看成自动机上从低到高经过 \(k\) 的每个二进制位走到的节点。

那么现在我们做的就是处理形如 \([0,2^k)+[2^k,2^{k+1})\) 这样的区间合并的过程。

但是此时的 dp of dp 是从低到高加位的,不过更高位的信息我们不知道,需要枚举后才能确定跨越 \(2^k\) 的串是否有贡献。

所以这提示我们转置 dp of dp 的过程,变成从高往低加入 \(k\) 的每个二进制位,可以通过转置内层 dp 实现。

那么我们只要记录加入更高位后得到的节点 \(u\),即 \(F(k,u)\) 表示从 \(u\) 出发任意走 \(k\) 步形成一个长为 \(2^k\) 的串 \([p_u2^k,(p_u+1)2^k)\),然后变成 \(F(k-1,\mathrm{trans}(u,0))+F(k-1,\mathrm{trans}(u,1))\) 这样的信息合并。

考虑 \(F\) 需要什么信息,显然只要内部 \(t\) 数量,以及头尾 \(|t|\) 个字符即可,为了加速处理,我们可以直接维护 \(F\) 内部的每个前缀和后缀是否能匹配 \(t\) 上对应的后缀或前缀,二进制压位可以 \(\mathcal O(1)\) 合并。

实现的时候只处理 \(k\ge 5\) 的串,因为不会有 \(t\) 跨过它们匹配,拆分 \([L,R]\) 的二进制区间时保留中间若干 \(k\ge 5\) 的区间,剩余 \(k<5\) 的区间一定是前缀和后缀,暴力计算后合并起来。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int U=1<<18;
int n,k,T,e[U+5][2][2]; ll m,L,R; string N,E;
struct info { ll x; int L,R; } f[2][U+5],ans[205];
info operator +(const info&u,const info&v) {
	return {u.x+v.x+__builtin_popcount(u.R&v.L),u.L,v.R};
}
int cl,cr,q; ll sl,sr;
vector <array<int,2>> qy[67];
int jb=0;
void dfs(int d,int u,bool il,bool ir) {
	if(!il&&!ir&&d>=4) return qy[d].push_back({u,++q});
	if(d<0) {
		if(!q) sl|=(u&1ll)<<(cl++);
		else sr|=(u&1ll)<<(cr++);
		return ;
	}
	for(int c=(il?L>>d&1:0);c<=(ir?R>>d&1:1);++c) {
		dfs(d-1,e[u][m>>d&1][c],il&&c==(L>>d&1),ir&&c==(R>>d&1));
	}
}
info init(int d,ll str) {
	info z={0,0,0};
	for(int i=0;i+k-1<d;++i) z.x+=((str>>i)&((1<<k)-1))==T;
	for(int i=1;i<k&&i<=d;++i) z.L|=((str&((1<<i)-1))==(T>>(k-i)))<<(k-i),z.R|=((str>>(d-i))==(T&((1<<i)-1)))<<i;
	return z;
}
void solve() {
	cin>>n>>m>>k>>L>>R>>N>>E,T=0;
	reverse(N.begin(),N.end());
	for(int i=0;i<k;++i) T|=(E[k-1-i]-'0')<<i;
	swap(L,R),L=n*m+1-L,R=n*m+1-R;
	for(int s=0;s<U;++s) for(int c:{0,1}) {
		ll t=0,v=0;
		for(int j=0;j<18;++j) if(s>>j&1) t|=1ll<<(2*j+c);
		e[s][0][c]=t&(U-1);
		for(int j=0;j<=n;++j) if(N[j]=='1') v^=t>>j;
		e[s][1][c]=v&(U-1);
	}
	q=cl=cr=sl=sr=0;
	for(int d=0;d<60;++d) qy[d].clear();
	dfs(59,1,1,1);
	ans[0]=init(cl,sl),ans[q+1]=init(cr,sr);
	for(int s=0;s<U;++s) {
		static int st[67]; st[1]=s;
		for(int i=1;i<=5;++i) for(int j=(1<<i);j<(2<<i);++j) st[j]=e[st[j>>1]][m>>(5-i)&1][j&1];
		ll str=0;
		for(int i=0;i<32;++i) str|=(st[32+i]&1ll)<<i;
		f[0][s]=init(32,str);
	}
	for(auto&o:qy[4]) ans[o[1]]=f[0][o[0]];
	for(int h=5;h<60;++h) {
		for(int s=0;s<U;++s) f[h&1][s]=f[(h+1)&1][e[s][m>>h&1][0]]+f[(h+1)&1][e[s][m>>h&1][1]];
		for(auto&o:qy[h]) ans[o[1]]=f[h&1][o[0]];
	}
	for(int i=1;i<=q+1;++i) ans[0]=ans[0]+ans[i];
	cout<<ans[0].x<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*M. [QOJ5166] 回文匹配 (8)

Problem Link

首先注意到回文匹配具有传递性、且两个回文匹配串同位置的子串也相等。

进一步刻画回文匹配 \(S\approx T\) 的结构,考虑增量确定 \(S_{n},T_n\),发现我们只要记录 \(S[1,n],T[1,n]\) 的最长回文后缀,合法当且仅其长度相等,此时我们能确定该字符。

所以可以用类似 kmp 等朴素字符串匹配算法维护两个串之间的匹配,对于原问题,显然要考虑建立 AC 自动机状物,建立之后变成 Trie 上 \(S_j\) 路径和 Fail 树上 \(S_i\) 子树的公共部分,按 \(j\) 离线并在 Trie 上 dfs 维护路径上的点,BIT 统计即可。

问题变成如何构造 AC 自动机,那么先在原始的 Trie 上 dfs,得到回文匹配意义下的等价类,由于 \(S\approx T\) 只要比较每个前缀的最长回文后缀,因此我们也可以对这些等价类建 Trie,其中 \(S+c\approx T\),则把 \(S\to T\) 转移边的权值设为 \(S+c\) 的最长回文后缀长度,表示 \(S\to T\) 的转移加入的字符当且仅当满足该条件时合法。

所以我们在 Trie 上 dfs 时就可以划分等价类,\(x\approx y\) 当且仅当 \(fa(x)\approx fa(y)\) 且 \(fa(x)\to x,fa(y)\to y\) 的权值(最长回文后缀)相同,实现时先确定 \(fa(x)\) 所属的等价类,然后用 std::map 维护 Trie 上的出边,则 \(fa(x)\to x,fa(y)\to y\) 会对应到同一个点的同一条边上,建立时就会得到 \(x\approx y\)。

然后要求出 Fail 指针,即对于 \(y=x+c\),要从 \(\mathrm{Fail}^k(x)\) 中找到一个 \(z\) 满足存在字符 \(d\) 使得 \(z+d\) 回文匹配 \(y\) 的后缀。

判定的方式还是一样,首先 \(z\) 肯定回文匹配 \(x\) 的后缀 \(x[|x|-|z|+1,|x|]\),那么只要检验 \(z+d\) 的最长回文后缀是否和 \(y[|x|-|z|+1,|y|]\) 的最长回文后缀相等即可。

然后我们求出 \(y[|x|-|z|+1,|y|]\) 的最长回文后缀 \(t\),然后找 \(\mathrm{Trans}(z,t)\) 是否存在即可,如果不存在,那么递归 \(\mathrm{Fail}(z)\) 继续找,并把答案记忆化到 \(\mathrm{Trans}(z,t)\) 上。

我们来证明一下复杂度,首先 Trie 树上每个点的度数是 \(\le |\Sigma|\) 的,这是因为对于任何一个等价类中的串,其回文树结构相同、PAM 上的形态也相同。

考虑归纳证明,假设 \(S\approx T,|S|=n\),但是加入一个字符后 PAM 结构变化,那么应该存在回文后缀长度 \(x>y\),满足 \(S_{n-x}=S_{n-y},T_{n-x}\ne T_{n-y}\),但是此时 \(S[n-x,n-x+y+1]\) 回文,因为 \(S_{n-x+y+1}=S_{n-y}=S_{n-x}\),而 \(T\) 上的这个区间不回文,从而 \(S\approx T\) 不成立。

那么本质可能的出边只有 \(|\Sigma|\) 种,同理 AC 自动机上对 Fail 指针的记忆化本质上也是记录等价类上的某个后继,因为 \(z\approx x[|x|-|z|+1,|x|]\),且 \(\mathrm{Trans}(z,t)\) 是 \(x[|x|-|z|+1,|x|]+c\) 生成的串所属的等价类。

因此总的信息量依旧是 \(L|\Sigma|\) 的,其中 \(L\) 是 Trie 树大小。

实现的时候我们要快速对 Trie 树建立 PAM,类比序列建 PAM 的过程,从父亲处转移,但是不能均摊向上找匹配位置,而是对每个可能的新字符 \(c\) 提前维护跳 Fail 指针跳到的点,查询子串最长回文子序列依然可以类似地倍增。

时间复杂度 \(\mathcal O(L|\Sigma|\log|\Sigma|+(L+q)\log L)\)。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int _,n,q,ch[MAXN][26],m;
struct PAM {
	int tr[MAXN][26],ex[MAXN][26],fa[MAXN],up[20][MAXN],q,len[MAXN],s[MAXN],id[MAXN];
	void dfs(int u,int p,int k) {
		if(s[k]!=s[k-len[p]-1]) p=ex[p][s[k]];
		if(!tr[p][s[k]]) {
			len[++q]=len[p]+2,fa[q]=tr[ex[p][s[k]]][s[k]],tr[p][s[k]]=q;
			memcpy(ex[q],ex[fa[q]],sizeof(ex[q])),ex[q][s[k-len[fa[q]]]]=fa[q];
		}
		id[u]=tr[p][s[k]];
		for(int c=0;c<26;++c) if(ch[u][c]) s[k+1]=c,dfs(ch[u][c],id[u],k+1);
	}
	void build() {
		q=1,len[1]=-1,fa[0]=1,fa[1]=0,s[0]=-1;
		for(int c=0;c<26;++c) ex[0][c]=ex[1][c]=1;
		for(int c=0;c<26;++c) if(ch[0][c]) s[1]=c,dfs(ch[0][c],0,1);
		for(int i=0;i<=q;++i) up[0][i]=fa[i];
		for(int k=1;k<20;++k) for(int i=0;i<=q;++i) up[k][i]=up[k-1][up[k-1][i]];
	}
	int qry(int u,int d) {
		if(len[u]<=d) return len[u];
		for(int k=19;~k;--k) if(len[up[k][u]]>d) u=up[k][u];
		return len[up[0][u]];
	}
}	P;
int vc,ed[MAXN],id[MAXN],in[MAXN],dep[MAXN],fa[MAXN];
map <int,int> tr[MAXN],ac[MAXN];
void dfs1(int u,int k) {
	for(int c=0;c<26;++c) if(ch[u][c]) {
		int v=P.len[P.id[ch[u][c]]];
		if(!tr[id[u]].count(v)) tr[id[u]][v]=++vc;
		id[ch[u][c]]=tr[id[u]][v],in[id[ch[u][c]]]=P.id[ch[u][c]],dep[id[ch[u][c]]]=k+1;
		dfs1(ch[u][c],k+1);
	}
}
int qfa(int u,int x) {
	if(u<0) return 0;
	int v=P.qry(x,dep[u]+1);
	if(ac[u].count(v)) return ac[u][v];
	return ac[u][v]=qfa(fa[u],x);
}
void build() {
	queue<int>Q; ac[0]=tr[0];
	for(auto&e:tr[0]) Q.push(e.second);
	while(Q.size()) {
		int u=Q.front(); Q.pop(),ac[u]=tr[u];
		for(auto&e:tr[u]) fa[e.second]=qfa(fa[u],in[e.second]),Q.push(e.second);
	}
}
int dfn[MAXN],efn[MAXN],dc,ans[MAXN];
vector <int> G[MAXN];
vector <array<int,2>> qy[MAXN];
void dfs2(int u) { dfn[u]=++dc; for(int v:G[u]) dfs2(v); efn[u]=dc; }
struct BIT {
	int tr[MAXN],s;
	void add(int x,int v) { for(;x<=dc;x+=x&-x) tr[x]+=v; }
	int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
}	T;
void dfs3(int u) {
	T.add(dfn[u],1);
	for(auto o:qy[u]) ans[o[1]]=T.qry(efn[o[0]])-T.qry(dfn[o[0]]-1);
	for(auto&e:tr[u]) dfs3(e.second);
	T.add(dfn[u],-1);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>_>>n>>q;
	if(!_) {
		for(int i=1,u;i<=n;ed[i++]=u) {
			string s; cin>>s,u=0;
			for(auto c:s) {
				if(!ch[u][c-'a']) ch[u][c-'a']=++m;
				u=ch[u][c-'a'];
			}
		}
	} else {
		for(int i=1,x;i<=n;++i) {
			char c; cin>>x>>c;
			if(!ch[ed[x]][c-'a']) ch[ed[x]][c-'a']=++m;
			ed[i]=ch[ed[x]][c-'a'];
		}
	}
	P.build(),dfs1(0,0),build();
	for(int i=1,x,y;i<=q;++i) cin>>x>>y,qy[id[ed[y]]].push_back({id[ed[x]],i});
	for(int i=1;i<=vc;++i) G[fa[i]].push_back(i);
	dfs2(0),dfs3(0);
	for(int i=1;i<=q;++i) cout<<ans[i]<<"\n";
	return 0;
}



N. [QOJ5095] 九王唱 (3.5)

Problem Link

从后往前模拟,倒数第二个人无论怎么选择都没法让这三首歌中最后一个人最不喜欢的一首留下来,而剩下两首歌中较喜欢的一首一定能留下来。

而倒数第三个人的肯定也没法让最后一个人最不喜欢的一首留下来,对于剩下三首,倒数第二个人最不喜欢的肯定留不下来,而剩下的两个中较优的一个总是能留下来。

所以我们猜测方案就是从后往前每个人删除当前自己最不喜欢的一首歌,证明考虑所有人都不会删除最后一个人最不喜欢的歌,因为这首歌无论如何都会被删除,那么自己删除的话没法控制最后一个人的决策,还不如自己选一首歌删除自由度更高。

那么暴力求解是 \(\mathcal O(n^3)\) 的,不过我们本质上是对每个人求按喜欢程度降序排列后首个未删除的歌,而我们求 答案 \(z_1\to z_2\) 时,除第一个人以外,每个人面对的剩余歌曲集合都是求 \(z_1\) 时剩余歌曲的子集,因为我们只是在最开始删掉了第一个人最不喜欢的歌,所以可以直接用指针记忆化上次的结果扫描。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005;
int n,_,a[MAXN][MAXN],b[MAXN],z[MAXN],h[MAXN];
bool in[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>_,fill(h,h+n,1); mt19937 rnd(_);
	for(int i=0;i<n;++i) {
		for(int j=1;j<=n+1;++j) {
			if(_) b[j]=j,swap(b[j],b[rnd()%j+1]);
			else cin>>b[j];
		}
		for(int j=1;j<=n+1;++j) a[i][b[j]]=j;
	}
	for(int p=0;p<n;++p) {
		fill(in+1,in+n+2,1),h[(p+n-1)%n]=1;
		for(int i=(p+n-1)%n;;i=(i+n-1)%n) {
			for(int &j=h[i];j<=n+1;++j) if(in[a[i][j]]) { in[a[i][j]]=0; break; }
			if(i==p) break;
		}
		for(int i=1;i<=n+1;++i) if(in[i]) cout<<i<<" \n"[p==n-1];
	}
	return 0;
}



O. [QOJ14017] Circular Matching (2)

Problem Link

首先要求的就是环上最优的括号匹配方式,显然通过调整法能证明所有弦不相交。

同样我们也能证明存在一个不被任何一组括号匹配经过的边,比如从 \([l,r]\) 开始,\((l-1,l)(r,r+1)\) 这两条边只能被 \(x\le l\le r\le y\) 的括号经过,而不能被 \(l\le x\le y\le r\) 的括号经过,因此 \((l,r)\gets (x,y)\) 不断操作一定能找到一个不被任何括号匹配经过的边。

枚举这条边断掉后就变成了朴素的序列括号匹配问题,考虑每条边的贡献,显然是前缀中两种元素的差。

那么记 \(s\) 为原序列前缀和,断环为链的位置是 \(x\),则答案就是 \(\sum |s_i-s_x|\),显然 \(s_x\) 取中位数最优,那么直接主席树简单维护即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,d,tot,a[MAXN],rt[MAXN],ct[MAXN*22],ls[MAXN*22],rs[MAXN*22];
ll su[MAXN*22]; string S;
void ins(int x,int l,int r,int q,int &p) {
	ct[p=++tot]=ct[q]+1,su[p]=su[q]+x;
	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];
}
ll qry(int k,ll sl,ll sr,int l,int r,int q,int p) {
	if(l==r) return sr-1ll*(2*k-ct[p]+ct[q])*l-sl;
	int mid=(l+r)>>1,t=ct[ls[p]]-ct[ls[q]];
	if(k<=t) return qry(k,sl,sr+su[rs[p]]-su[rs[q]],l,mid,ls[q],ls[p]);
	else return qry(k-t,sl+su[ls[p]]-su[ls[q]],sr,mid+1,r,rs[q],rs[p]);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>S;
	for(int i=0;i<n;++i) a[i+1]=a[i]+(S[i]=='0'?-1:1);
	for(int i=1;i<=n;++i) ins(a[i],-n,n,rt[i-1],rt[i]);
	for(int l,r;m--;) cin>>l>>r,d=r-l+1,cout<<qry(d/2,0,0,-n,n,rt[l-1],rt[r])<<"\n";
	return 0;
}



*P. [QOJ5100] 卡牌游戏 (7.5)

Problem Link

首先权值 \(w(x,y)=\lfloor\frac{S}{\mathrm{lcm}(x,y)}\rfloor\times \mathrm{lcm}(x,y)\) 等价于最大的 \(z\le S\) 满足 \(x\mid z,y\mid z\)。

那么转移的时候每个 \(a_i\) 从所有倍数 \(z\) 处求 \(\max h_z+z\),其中 \(h_z=\max_{a_j\mid z} f_j\) 即可。

对于 \(a_i\) 或 \(a_j\) 不超过 \(\sqrt S\) 的部分可以直接暴力,从而做到 \(\mathcal O(S\sqrt S)\) 处理整体答案。

然后考虑算出前后缀答案 \(f,g\),然后 \(f_x+g_y+w(a_x,a_y)\to ans[x+1,y-1]\),进一步分析题目限制。

发现如果 \(x,y\le \sqrt S\),则 \(w(x,y)\ge \dfrac S2\),因为任意 \(q\le S\) 都有 \(S\bmod q<\dfrac S2\)。

所以如果 \((x,y)\) 中间有 \(\ge 3\) 个 \(\le \sqrt S\) 的元素,那么直接全部选上不劣,用这个结论优化求 \(f\) 的过程,如果 \(a_i\le \sqrt S\) 则向前向后暴力枚举直到遇到 \(\ge 3\) 个 \(\le\sqrt S\) 的元素,均摊线性。

同理第二部分 \(\min(a_x,a_y)\le \sqrt S\) 也能处理,但这部分要找 \(\ge 4\) 个 \(\le \sqrt S\) 的元素再退出,因为区间可能删掉一个元素。

那么 \(a_x,a_y>\sqrt S\) 时枚举 \(z\),对于每个 \(a_x\mid z\) 的 \(x\),由于 \(f\) 递增(因为 \(f_i\ge f_{i-1}+w(a_{i-1},a_i)\)),所以先找到最大的一个 \(x\),对于更小的 \(x'\),只要 \(x\) 不被删除那么选上 \(x\) 更优,所以只要用次大的 \(x'\) 更新 \(ans_x\) 即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
void chk(ll &x,const ll&y) { x=y>x?y:x; }
int n,S,B,O,_,a[MAXN],b[MAXN][2],gc[715][715],vl[MAXN];
ll f[MAXN],g[MAXN],h[MAXN],mx[19][MAXN];
int qg(int x,int y) {
	for(;x&&y;x%=y,swap(x,y)) if(max(x,y)<=B) return gc[x][y];
	return x+y;
}
ll w(int x,int y) {
	if(max(a[x],a[y])>S) return 0;
	ll t=1ll*a[x]*a[y]/qg(a[x],a[y]);
	return t<=S?vl[t]:0;
}
void solve() {
	memset(f,0,sizeof(f)),memset(h,-0x3f,sizeof(h));
	for(int i=1,d=1;i<=n;++i) {
		if(i>1) chk(f[i],f[i-1]+w(i-1,i));
		for(;f[d]+S<=f[i];++d);
		if(a[i]<=B) {
			for(int j=i-1,c=0;j>=d&&c<=2;c+=a[j--]<=B) chk(f[i],f[j]+w(j,i));
			for(int j=i+1,c=0;j<=n&&c<=2;c+=a[j++]<=B) chk(f[j],f[i]+w(i,j));
		} else {
			if(d>=i-50) for(int j=i-2;j>=d;--j) chk(f[i],f[j]+w(j,i));
			else for(int k=a[i];k<=S;k+=a[i]) chk(f[i],h[k]+k);
			for(int k=a[i];k<=S;k+=a[i]) chk(h[k],f[i]);
		}
	}
}
void ins(int l,int r,ll z) { int k=__lg(r-l+1); chk(mx[k][l],z),chk(mx[k][r-(1<<k)+1],z); }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>S>>O>>_,B=sqrt(S);
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=S;++i) vl[i]=S/i*i;
	for(int i=0;i<=B;++i) for(int j=0;j<=B;++j) gc[i][j]=i&&j?gc[j%i][i]:i+j;
	for(int o:{0,1}) solve(),reverse(a+1,a+n+1),swap(f,g);
	reverse(g+1,g+n+1);
	if(!O) return cout<<f[n]<<"\n",0;
	for(int i=1;i<=n;++i) {
		if(a[i]<=B) {
			for(int j=i-1,c=0;j>=1&&c<=3;c+=a[j--]<=B) if(j<i-1) ins(j+1,i-1,f[j]+w(j,i)+g[i]);
			for(int j=i+1,c=0;j<=n&&c<=3;c+=a[j++]<=B) if(j>i+1) ins(i+1,j-1,f[i]+w(i,j)+g[j]);
		} else {
			for(int j=a[i];j<=S;j+=a[i]) {
				if(b[j][0]) {
					if(b[j][0]<i-1) ins(b[j][0]+1,i-1,f[b[j][0]]+j+g[i]);
					if(b[j][1]) chk(mx[0][b[j][0]],f[b[j][1]]+j+g[i]);
				}
				b[j][1]=b[j][0],b[j][0]=i;
			}
		}
	}
	for(int k=17;~k;--k) for(int i=1;i+(2<<k)-1<=n;++i) chk(mx[k][i],mx[k+1][i]),chk(mx[k][i+(1<<k)],mx[k+1][i]);
	ll Z=0;
	for(int i=1;i<=n;++i) Z^=i*max(mx[0][i],f[i-1]+g[i+1]);
	cout<<f[n]<<" "<<Z<<"\n";
	return 0;
}



Q. [QOJ1870] Command and Conquer: Red Alert 2 (4)

Problem Link

考虑二分答案 \(k\),然后刻画一种点的删除顺序是否合法。

显然 \(x_i+k<x_j-k\) 则 \(i\) 必然在 \(j\) 前面,\(y,z\) 同理,假如这张图无环,取出拓扑序,假如任何时候我们都保持三维坐标尽可能小,则删除一个前缀后,我们的位置一定是 \(x_i-k,y_i-k,z_i-k\) 的前缀最小值,对于一个新的点 \(j\),\(x_j+k,y_j+k,z_j+k\) 一定都大于当前点坐标,否则我们会对 \(j\) 做出拓扑序限制。

那么我们只要对这个图拓扑排序,先只看 \(x\) 一维,排序后每个点都会向一段后缀连边,所以任何时候加入拓扑排序队列的都是一段前缀,用指针就能 \(\mathcal O(n)\) 处理。

对于原问题,处理方式类似,对于 \(x,y,z\) 分别计算当前维上没有入边的前缀,对每个点记录是否在三维上都进入前缀中了,如果是的话就加入拓扑排序队列。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,a[3][MAXN],b[3][MAXN],c[MAXN],d[3][MAXN],h[3][MAXN],e[3],q[MAXN];
bool chk(int k) {
	int l=1,r=0;
	fill(c+1,c+n+1,3);
	for(int o:{0,1,2}) {
		int *A=a[o],*B=b[o]; e[o]=1,fill(d[o],d[o]+n+2,0);
		for(int i=1,j=1;i<=n;++i) {
			while(j<=n&&A[B[j]]<=A[B[i]]+2*k) ++j;
			h[o][B[i]]=j,++d[o][j];
		}
		for(;e[o]<=n&&!d[o][e[o]];++e[o]) if(!--c[B[e[o]]]) q[++r]=B[e[o]];
	}
	for(;l<=r&&r<n;++l) {
		int u=q[l];
		for(int o:{0,1,2}) {
			--d[o][h[o][u]];
			for(;e[o]<=n&&!d[o][e[o]];++e[o]) {
				if(!--c[b[o][e[o]]]) q[++r]=b[o][e[o]];
			}
		}
	}
	return r==n;
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[0][i]>>a[1][i]>>a[2][i],b[0][i]=b[1][i]=b[2][i]=i;
	for(int o:{0,1,2}) sort(b[o]+1,b[o]+n+1,[&](int x,int y){ return a[o][x]<a[o][y]; });
	int z=1e9;
	for(int k=1<<29;k;k>>=1) if(z>=k&&chk(z-k)) z-=k;
	cout<<z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



R. [QOJ14025] Bot Friends 2 (4)

Problem Link

首先如果 \((x,z)\) 在 \(y\) 上合并,\(y\) 去其他位置合并,一定不如 \((x,y)\) 在 \(y\) 上合并,\(z\) 去原本 \(y\) 对应的位置合并。

所以我们一定在 \(x\) 或 \(y\) 上合并 \((x,y)\),即操作变成选择未删除的 \(y\),以 \(\mathrm{dis}(x,y)+a_y\) 的代价删除 \(x\)。

那么 \(a\) 的贡献是 \(a_{rt}+\sum a_i(\deg_i-1)\),显然 \(a_{rt}=\min a_i\),加上 \(\sum a_i\) 后第二部分变成 \(\sum a_i\deg i\),即按 \(\mathrm{dis}(x,y)+a_x+a_y\) 建立 MST 的边权。

考虑找出 MST 上一些本质有效的边,对每个点维护 \(f_x=\min a_y+\mathrm{dis}(x,y)\),\(c_x\) 表示取到 \(f_x\) 的 \(y\)。

我们猜测 MST 中对于 \(c_x\ne x\) 的点一定是 \(c_x\) 下的叶子,且只要考虑 \(c=x,c=y\) 的点集相邻的 \((x,y)\) 之间的边,即对于每条 \(c_u\ne c_v\) 的边连接 \((c_u,c_v,f_u+f_v+w(u,v))\),然后求 MST。

证明考虑对于任意 \(x,y\),\(w=a_x+a_y+\mathrm{dis}(x,y)\),首先 \((x,c_x),(y,c_y)\) 之间的边权显然 \(\le w\),其次 \(x\to y\) 路径上任意相邻的两个点 \(e_i,e_{i+1}\) 都有 \((c_{e_i},c_{e_{i+1}})\) 的边权 \(\le w\),所以 \((x,y)\) 之间的边已经被连通。

多源 Dijkstra 求出 \(f,c\) 后直接维护 MST。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
const ll inf=1e18;
int n,m,dsu[MAXN],c[MAXN];
ll a[MAXN],d[MAXN];
bool vis[MAXN];
struct Edge { int v,w; };
vector <Edge> G[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],dsu[i]=i,G[i].clear();
	vector<array<ll,3>>E;
	for(int i=1,u,v,z;i<=m;++i) cin>>u>>v>>z,G[u].push_back({v,z}),G[v].push_back({u,z}),E.push_back({z+a[u]+a[v],u,v});
	priority_queue<array<ll,2>,vector<array<ll,2>>,greater<>> Q;
	for(int i=1;i<=n;++i) vis[i]=0,c[i]=i,Q.push({d[i]=a[i],i});
	while(Q.size()) {
		int u=Q.top()[1]; Q.pop(); if(vis[u]) continue; vis[u]=1;
		for(auto e:G[u]) if(d[e.v]>d[u]+e.w) c[e.v]=c[u],Q.push({d[e.v]=d[u]+e.w,e.v});
	}
	for(int u=1;u<=n;++u) {
		if(c[u]!=u) E.push_back({d[u]+a[u],c[u],u});
		for(auto e:G[u]) if(c[u]!=c[e.v]) E.push_back({d[u]+e.w+d[e.v],c[u],c[e.v]});
	}
	ll z=*min_element(a+1,a+n+1)-accumulate(a+1,a+n+1,0ll);
	sort(E.begin(),E.end());
	for(auto&e:E) if(find(e[1])!=find(e[2])) z+=e[0],dsu[find(e[1])]=find(e[2]);
	cout<<z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



S. [QOJ5097] 小 P 爱学习 (4.5)

Problem Link

考虑 \(\sum a_i\) 乘积看成每组选一个 \(a_i\) 乘起来再求和。

枚举 \(k\) 个被选的 \(a_i\),那么就要把剩余 \(nm-k\) 个元素分成 \(k\) 个 \(\bmod m=m-1\) 的组。

直接背包可以看成求 \([x^n]F^k\),其中 \(F=\sum_{i\ge 1}\dfrac{x^i}{(im-1)!}\)。

这种维护 \(F^0\sim F^n\) 的问题考虑根号分治,记 \(B=\sqrt n\),那么快速求出 \(F^{kB}\),以及 \(X\) 的每一项对 \([x^n]XF^r\) 的贡献系数即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1.5e5+5,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; }
ll a[MAXN],fc[MAXN],ic[MAXN],f[1505],g[1505],h[1505],w[41][1505],z;
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,m; cin>>n>>m;
	for(int i=1;i<=n*m;++i) cin>>a[i];
	for(int i=fc[0]=ic[0]=1;i<=n*m;++i) ic[i]=ksm(fc[i]=fc[i-1]*i%MOD);
	f[0]=g[0]=h[0]=w[0][n]=1;
	for(int i=1;i<=n*m;++i) for(int j=n;j;--j) g[j]=(g[j]+g[j-1]*a[i])%MOD;
	int B=sqrt(n);
	for(int t=1;t<=B;++t) {
		for(int i=0;i<=n;++i) for(int j=1;i+j<=n;++j) w[t][i]=(w[t][i]+w[t-1][i+j]*ic[j*m-1])%MOD;
		for(int i=n;~i;--i) {
			h[i]=0;
			for(int j=1;j<=i;++j) h[i]=(h[i]+h[i-j]*ic[j*m-1])%MOD;
		}
	}
	for(int x=0;x<n;x+=B) {
		for(int t=1;t<=B&&x+t<=n;++t) {
			ll s=0;
			for(int i=0;i<=n;++i) s=(s+w[t][i]%MOD*f[i])%MOD;
			z=(z+g[x+t]*fc[n*m-x-t]%MOD*s)%MOD;
		}
		for(int i=n;~i;--i) {
			f[i]=0;
			for(int j=B;j<=i;++j) f[i]=(f[i]+f[i-j]*h[j])%MOD;
		}
	}
	cout<<z<<"\n";
	return 0;
}



T. [QOJ14026] Traveling in Cells 3 (3)

Problem Link

假设 \(a>b\),那么 \(x\to y\) 的距离可以先算上 \(b\lfloor\frac{y-x}a\rfloor\),再加上和 \((y-x)\bmod a\) 有关的代价。

可以证明最终应该汇聚在 \(x\) 的中位数左右 \(a\) 个位置的范围内,每个点对每个位置的答案贡献可以写成 \(\mathcal O(1)\) 个区间加,简单分讨即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
int n,p;
ll a[MAXN],A,B;
void solve() {
	cin>>n>>A>>B,p=(n+1)/2;
	for(int i=1;i<=n;++i) cin>>a[i];
	sort(a+1,a+n+1);
	if(B>=A) {
		ll z=0;
		for(int i=1;i<=n;++i) z+=abs(a[i]-a[p]);
		return cout<<z<<"\n",void();
	}
	ll L=a[p]-A,R=a[p]+A,o=0,c=(A+B)/2;
	map <ll,array<ll,2>> f;
	auto add=[&](ll l,ll r,ll x,ll y) {
		l=max(l,L),r=min(r,R);
		if(l<=r) f[l][0]+=x,f[l][1]+=y,f[r+1][0]-=x,f[r+1][1]-=y;
	};
	for(int i=1;i<=n;++i) {
		ll x=a[i],y;
		if(x<L) o+=(L-x)/A*B,x+=(L-x)/A*A;
		if(x>R) o+=(x-R)/A*B,x-=(x-R)/A*A;
		for(int k=0;x+k*A<=R;++k) {
			y=x+k*A,add(y,y+c,k*B-y,1);
			add(y+c+1,y+A-1,y+A+(k+1)*B,-1);
		}
		for(int k=0;x-k*A>=L;++k) {
			y=x-k*A,add(y-c,y,k*B+y,-1);
			add(y-A+1,y-c-1,-y+A+(k+1)*B,1);
		}
	}
	ll x=0,y=0,z=1e18;
	for(auto i:f) {
		if(i.first>L) z=min(z,x-y+i.first*y);
		x+=i.second[0],y+=i.second[1];
		if(i.first<=R) z=min(z,x+i.first*y);
	}
	cout<<o+z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



*U. [QOJ14022] Ethanol (9)

Problem Link

考虑把该问题变成离散问题,要求任何时候移动的容量都是 \(\Delta\),最后求 \(\Delta\to 0\) 的极限答案即可。

此时先考虑第一个杯子,会分别注入和流出 \(\dfrac{X}\Delta\) 次,考虑对这 \(\dfrac{2X}{\Delta}\) 次操作排序,应该是浓度 \(p_0>p_1\) 时一直注入直到 \(p_0\le p_1\),此时先把里面的容量全部流出,然后注入一次流出一次不断重复。

那么直接维护这 \(\dfrac{X}{\Delta}\) 次流出的溶液浓度即可,考虑该问题的连续版本,那么维护 \(i-1\to i\) 的溶液浓度随着流出溶液体积的变化函数 \(f(t)\),第一部分可以二分出一个 \(p_{i-1}=p_{i}\) 的时间,然后对于后续流出一份注入一份的位置,我们考虑 \(i\to i+1\) 的浓度函数 \(g(t)\),则在极短的时间 \(\Delta\) 内,\(g\) 的变化量应该是 \(-g(t)\Delta+f(t)\Delta\)。

也就是 \(g(t+\Delta)-g(t)=(f(t)-g(t))\times \Delta\),即 \(g'(t)=f(t)-g(t)\),现在已知 \(f\),需要解微分方程得到 \(g\)。

考虑 \(f(t)=g'(t)+g(t)\),构造求导得到 \(g'+g\) 状结构的函数,考虑 \((e^tg(t))'=e^t(g(t)+g'(t))=e^tf(t)\)。

所以 \(g(t)=e^{-t}\int f(t)e^t\mathrm d t\),那么处理 \(f\) 的定积分之后用 \(p_{i-1}=p_i\) 的临界时间 \(t_0\) 处的 \((t_0,p_0)\) 带入计算来确定不定积分常数 \(C\)。

考虑 \(f\) 的结构,如果 \(f\) 是多项式,则 \(g(t)\) 应该是多项式,但是要加上 \(Ce^{-t}\),进行若干次积分后变成 \(e^{-t}\) 乘一个多项式,我们可以归纳证明任意 \(f(t)\) 都是 \(p(t)+q(t)e^{-t}\) 的结构,对上述操作和积分操作都是封闭的,则求 \(\int p(t),\int p(t)e^t,\int q(t),\int q(t)e^{-t}\) 都能看成各项系数之间的递推关系从而线性计算。

那么我们维护刚才所说的 \(f\),应该是一个分段函数,每段都是某个 \(p(t)+q(t)e^{-t}\),通过积分可以算出在段内某个时刻 \(i\) 容器的浓度,从而找到临界时间 \(t_0\)。

然后对于 \(\le t_0\) 的部分 \(g(t)=p_0\),后续部分则是直接从 \(e^{-t}\int f(t)e^t\) 插入每一段即可。

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

代码:

#include<bits/stdc++.h>
#define ld long double
#define sz(a) ((int)a.size())
using namespace std;
struct poly {
	vector <ld> f,g; //f(x)+g(x)*e^{-x}
	ld Z(ld x) {
		ld u=0,v=0;
		for(int i=sz(f)-1;~i;--i) u=u*x+f[i];
		for(int i=sz(g)-1;~i;--i) v=v*x+g[i];
		return u+expl(-x)*v;
	}
	poly Int() {
		vector <ld> a(sz(f)+1),b(sz(g));
		for(int i=1;i<=sz(f);++i) a[i]=f[i-1]/i;
		for(int i=sz(g)-1;~i;--i) b[i]=(i+1<sz(g)?(i+1)*b[i+1]:0)-g[i];
		return {a,b};
	}
	poly inp(ld t,ld w) { //a'=f-a
		vector <ld> a(sz(f)),b(sz(g)+1);
		for(int i=sz(f)-1;~i;--i) a[i]=f[i]-(i+1<sz(f)?(i+1)*a[i+1]:0);
		for(int i=1;i<=sz(g);++i) b[i]=g[i-1]/i;
		ld u=0,v=0;
		for(int i=sz(a)-1;~i;--i) u=u*t+a[i];
		for(int i=sz(b)-1;~i;--i) v=v*t+b[i];
		b[0]+=(w-u)*expl(t)-v;
		return {a,b};
	}
};
struct info {
	ld l,r; poly f;
	bool qry(ld&t,ld&w) {
		ld v=t+1,c=v*w; poly s=f.Int();
		if((c+s.Z(r)-s.Z(l))/(v+r-l)<f.Z(r)) return t=r,w=(c+s.Z(t)-s.Z(l))/(v+t-l),1;
		if(w>=f.Z(l)) return 0;
		ld L=l,R=r,m;
		for(int _=80;_--;) {
			m=(L+R)/2,((c+s.Z(m)-s.Z(l))/(v+m-l)<f.Z(m)?L=m:R=m);
		}
		t=L,w=(c+s.Z(t)-s.Z(l))/(v+t-l);
		return 0;
	}
};
void solve() {
	int n; ld X; cin>>n>>X,X/=1000;
	vector <info> f{{0,X,{{0},{}}}},g;
	for(int i=1,k;i<=n;++i,f.swap(g)) {
		ld t=0,w; cin>>w,w/=1000,g.clear();
		for(k=0;k<sz(f)&&f[k].qry(t,w);++k);
		g.push_back({0,t,{{w},{}}});
		for(;k<sz(f);++k) {
			f[k].l=max(f[k].l,t);
			g.push_back({f[k].l,f[k].r,f[k].f.inp(f[k].l,w)});
			w=g.back().f.Z(f[k].r);
		}
	}
	ld z=0;
	for(auto&o:f) z+=o.f.Int().Z(o.r)-o.f.Int().Z(o.l);
	cout<<fixed<<setprecision(20)<<z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



V. [QOJ5018] nth (4.5)

Problem Link

考虑一个比较优秀的二分方式,取 \(x+y=c\) 且尽可能接近,比较 \(a_x,b_y\),如果 \(a_x< b_y\) 那么说明 \(a[1,x]\) 可以删除。

每次朴素比较的复杂度是 \(\log c\log n\),考虑优化,我们知道此时还有答案 \(\le b_y\),而比较 \(a_x,b_y\) 时我们会询问 \(\mathrm{lcp}(a_x,b_y)+1\) 次,所以我们知道答案在前 \(\mathrm{lcp}(a_x,b_y)\) 位和 \(a_x,b_y\) 是一样的。

那么比较后面的 \(a_x,b_y\) 时只要跳过前 \(\mathrm{lcp}\) 位即可,如果这些位已经产生不同,那么按大小关系把低位设成全 \(0\) 或全 \(1\) 即可。

比较次数 \(2(\log c+\log n)\)。

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

代码:

#include<bits/stdc++.h>
#include "nth.h"
using namespace std;
namespace Alice {
int n,m,a[M+5],h,q,k,up,x,y,w;
void work() {
	while(h<n&&m) {
		x=max(1,q/2),y=q-q/2;
		if(n-h<x) m-=y,q-=y;
		else if(m<y) h+=x,q-=x;
		else return w=min(max(a[h+x],up),up+(2<<k)-1),void();
	}
	if(!m) report(a[h+q]);
}
void initA(bitset<M>A,unsigned S,unsigned c) {
	for(int i=0;i<M;++i) if(A[i]) a[++n]=i;
	m=S-n,h=0,q=c,k=20,up=0;
	work(),sendA(w>>k&1);
}
void receiveA(bool o) {
	bool t=w>>k&1;
	if(o==t) {
		up|=o<<k;
		if(!k) report(up);
		--k,sendA(w>>k&1);
	} else if(o>t) {
		if(q==1) report(a[h+1]);
		q-=x,h+=x,work();
		sendA(w>>k&1);
	} else {
		q-=y,m-=y,work();
		sendA(w>>k&1);
	}
}
}
namespace Bob {
int n,m,a[M+5],h,q,k,up,x,y,w;
void work() {
	while(h<n&&m) {
		x=q-q/2,y=max(1,q/2);
		if(n-h<x) m-=y,q-=y;
		else if(m<y) h+=x,q-=x;
		else return w=min(max(a[h+x],up),up+(2<<k)-1),void();
	}
	if(!m) report(a[h+q]);
}
void initB(bitset<M>A,unsigned S,unsigned c) {
	for(int i=0;i<M;++i) if(A[i]) a[++n]=i;
	m=S-n,h=0,q=c,k=20,up=0;
	work();
}
void receiveB(bool o) {
	bool t=w>>k&1;
	if(o==t) {
		up|=o<<k;
		if(!k) report(up);
		sendB(w>>k&1),--k;
	} else if(o>t) {
		if(q==1) report(a[h+1]);
		q-=x,h+=x,work();
		sendB(t);
	} else {
		if(q>1) q-=y,m-=y,work();
		sendB(t);
	}
}
}



*W. [QOJ5090] 妙妙题 (7.5)

Problem Link

首先我们考虑已知 \(x\) 时的一种经典做法,把所有人分成均匀的两部分,它们分别预测一个完全相反的事实,则至少有一半正确,一种构造是按 \(x\bmod 2\) 分组,然后分别预测白帽子人数个数是奇数或偶数即可。

那么一般情况下还是要找一种分组方法,一个很非平凡的构造是把 \(n\) 个点放到环上,即复平面上的 \(\omega^0\sim \omega ^{n-1}\),此时任何一条过圆点的直线都能将其均等划分成两个大小 \(\ge \frac{n-1}2\) 的组,因为其至多经过 \(2-n\bmod 2\) 个点,剩下部分平分。

取 \(v=\sum b_i\omega^i\),然后直线过 \((0,v)\),此时算与 \(v\) 的叉积即 \(\omega^{-i}v\) 的虚部是否 \(>0\),\(\omega ^{-i}v=\sum b_j\omega^{j-i}\),恰好符合 \(i\) 出发的循环移位信息,可以直接计算,对于 \(j=i\) 的项 \(\omega^0=1\) 对虚部无影响。

只要处理 \(v=0\) 的部分,此时如果 \(\sum_{j\ne i} b_j\omega^{j-i}=\omega^{-i}v-\omega^ib_i\) 非 \(0\) 说明 \(b_i=1\) 否则 \(b_i=0\),看实部是否非零即可。

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

代码:

#include<bits/stdc++.h>
#include "tmp.h"
using namespace std;
int n,c; double x,y,p,e=1e-8;
void init(int N,bool,int) { n=N,p=2*acos(-1)/n; }
bool guess(unsigned long long A,int) {
	x=y=0,c=0,A*=2;
	for(int i=1;i<n;++i) if(A>>i&1) x+=cos(p*i),y+=sin(p*i),c^=1;
	return abs(y)>e?c^(y>0):abs(x)>e;
}



X. [QOJ37] 蜀道易 (1.5)

Problem Link

维护 \(\sum siz_u,\sum siz_u^2\),乘一棵树对其的影响容易用矩阵表示,直接矩阵快速幂。

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

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=998244353;
void add(ll&x,const ll&y) { x=(x+y)%MOD; }
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 i=0;i<3;++i) for(int k=i;k<3;++k) for(int j=k;j<3;++j) add(w.f[i][j],u.f[i][k]*v.f[k][j]);
		return w;
	}
};
int n,K,S,T,sz[MAXN];
bool in[MAXN];
vector <int> G[MAXN];
void dfs(int u,int fz) {
	sz[u]=1,in[u]=u==T;
	for(int v:G[u]) if(v^fz) dfs(v,u),in[u]|=in[v],sz[u]+=sz[v];
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>K>>S>>T;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs(S,0); Mat Z,I; Z.f[0][0]=I.f[0][0]=n;
	for(int i=1;i<=n;++i) {
		add(Z.f[0][1],sz[i]);
		add(Z.f[0][2],1ll*sz[i]*sz[i]);
		if(in[i]) {
			ll x=(sz[i]+MOD-n)%MOD;
			add(I.f[0][1],x),add(I.f[1][1],n);
			add(I.f[0][2],x*x),add(I.f[1][2],2*n*x),add(I.f[2][2],1ll*n*n);
		} else {
			add(I.f[0][1],sz[i]);
			add(I.f[0][2],1ll*sz[i]*sz[i]);
		}
	}
	for(--K;K;K>>=1,I=I*I) if(K&1) Z=Z*I;
	cout<<(Z.f[0][0]*Z.f[0][1]+MOD-Z.f[0][2])%MOD<<"\n";
	return 0;
}



Y. [QOJ14023] International Olympiad in Fishing (4)

Problem Link

设每个点为 \(x_i,y_i\) 假设第一次操作按 \(x\) 排序,那么交替排序我们能得到按关键字 \((x_i,i),(y_i,x_i,i),(x_i,y_i,i)\) 排序后的结果 \(a,b,c\)。

我们的策略是对每一组本质不同的 \(a,b,c\) 询问其中的某种情况,所以只要计数可以生成的本质不同的 \((a,b,c)\)。

考虑如何判定 \((a,b,c)\),假如只有 \((b,c)\) 则任意的排列都是可以的,因为我们可以让 \(x,y\) 两两不同。

原问题假设 \(x\) 已知,则先考虑 \(a,c\),此时把 \(x\) 相同的元素分段,则每段内 \(a,c\) 值域相等,每段 $[l,r] $对 \(q\) 的限制就是 \(c[l,r]\) 是 \(b\) 的子序列,构造就是让 \(y_{q_i}=i\),可以证明不满足该条件则一定不合法。

那么 \(x\) 未知的时候根据上面的观察,肯定是尽可能地把每个 \(a[1,r]=c[1,r]\) 的前缀都切开,然后检验每个子区间。

可以 dp,对于一个切开的长度为 \(k\) 的区间,首先 \(\times k!^{-2}\) 表示在 \([1,n]\) 中给这些元素选值并且选择在 \(b\) 中的位置,再乘上 \(g_k\) 表示 \(c[1,k]\) 的排列数,即 \(1\sim k\) 排列不能再被切开的方案数。

那么记 \(f_k=\dfrac{g_k}{k!^2}\),则答案为 \([x^n]\dfrac{1}{1-f}\),而求 \(g\) 可以运用 \(\sum x^kk!=\dfrac{1}{1-g}\) 计算。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<20;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
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 {
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_inv(const int *f,int *g,int n) {
	static int a[N];
	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]=(2-1ll*a[i]*g[i]%MOD)*g[i]%MOD;
			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);
}
}
int f[N],g[N];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0);
	int n; cin>>n,P::poly_init();
	P::poly_inv(fac,g,n+1);
	for(int i=1;i<=n;++i) g[i]=1ll*g[i]*ifac[i]%MOD*ifac[i]%MOD;
	P::poly_inv(g,f,n+1);
	cout<<1ll*f[n]*fac[n]%MOD*fac[n]%MOD*ksm(n,MOD-1-2*n)%MOD<<"\n";
	return 0;
}



Z. [QOJ5017] 相等树链 (4.5)

Problem Link

在第一棵树上点分治,维护 \(rt\to x,rt\to y\) 点集在第二棵树上的直径,用哈希刻画第二棵树上对应的点集,枚举真实直径端点在 \(rt\to x\) 还是 \(rt\to y\) 的直径端点中即可用哈希表维护。

问题是异或哈希无法得到 \(\mathrm{LCA}\) 的值,所以还要考虑第二棵树上的形态。

但注意到这些路径在第二棵树上也经过 \(rt\),所以以 \(rt\) 在第二棵树上搜索连通块并处理即可,限制变成两端点在 \(rt\) 的不同子树,这个容易处理。

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

代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
#define ll long long
#define ull unsigned long long
using namespace std;
const int MAXN=2e5+5;
mt19937_64 rnd(time(0));
vector <int> G[MAXN],E[MAXN];
int n,dfn[MAXN],st[18][MAXN],dc,dep[MAXN];
ull hs[MAXN],s1[MAXN],s2[MAXN];
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]);
}
int dis(int x,int y) { return dep[x]+dep[y]-2*dep[LCA(x,y)]; }
struct dia { int u,v,d; };
dia operator +(dia u,int x) {
	if(u.d<0) return u;
	if(dis(u.u,x)>u.d) swap(u.v,x),u.d=dis(u.u,u.v);
	if(dis(u.v,x)>u.d) swap(u.u,x),u.d=dis(u.u,u.v);
	if(dis(u.u,x)+dis(x,u.v)!=u.d) u={0,0,-1};
	return u;
}
void dfs1(int u,int fz) {
	hs[u]=rnd(),dep[u]=dep[fz]+1,dfn[u]=++dc,st[0][dc]=fz;
	for(int v:E[u]) if(v^fz) dfs1(v,u);
}
dia f[MAXN];
int siz[MAXN],cur[MAXN],in[MAXN],ok[MAXN],ty[MAXN];
bool vis[MAXN]; ll ans=0;
__gnu_pbds::gp_hash_table<ull,int> d[MAXN],f0,f1,f2;
void dfs2(int u) {
	vis[u]=1,f[u]={u,u,0},s1[u]=s2[u]=hs[u],in[u]=ok[u]=u,ty[u]=0;
	for(int v:G[u]) if(!vis[v]) {
		function<void(int,int)>dfs3=[&](int x,int fz) {
			s1[x]=s1[fz]^hs[x],siz[x]=1,f[x]=f[fz]+x;
			if(~f[x].d) in[x]=u;
			for(int y:G[x]) if(y!=fz&&!vis[y]) dfs3(y,x),siz[x]+=siz[y];
		};
		dfs3(v,u);
	}
	function<void(int,int)>dfs5=[&](int x,int fz) {
		s2[x]=s2[fz]^hs[x],ok[x]=u;
		for(int y:E[x]) if(y!=fz&&in[y]==u) ty[y]=ty[x],dfs5(y,x);
	};
	int q=0;
	for(int v:E[u]) if(in[v]==u) ty[v]=++q,dfs5(v,u);
	auto add=[&](int x) {
		++f0[s1[x]];
		if(ty[f[x].u]!=ty[f[x].v]) ++f2[s2[f[x].u]^s2[f[x].v]^s1[x]];
		for(int t:{f[x].u,f[x].v}) if(t!=u) ++f1[s2[t]^s1[x]],++d[ty[t]][s2[t]^s1[x]];
	};
	auto qry=[&](int x) {
		ans+=f2[s1[x]];
		if(ty[f[x].u]!=ty[f[x].v]) ans+=f0[s2[f[x].u]^s2[f[x].v]^s1[x]];
		for(int t:{f[x].u,f[x].v}) if(t!=u) ans+=f1[s2[t]^s1[x]]-d[ty[t]][s2[t]^s1[x]];
	};
	add(u);
	for(int v:G[u]) if(!vis[v]) {
		basic_string<int>id;
		function<void(int,int)>dfs6=[&](int x,int fz) {
			if(ok[x]!=u) return ; id+=x;
			for(int y:G[x]) if(y!=fz&&!vis[y]) dfs6(y,x);
		};
		dfs6(v,u);
		for(int x:id) qry(x);
		for(int x:id) add(x);
	}
	f0.clear(),f1.clear(),f2.clear();
	for(int i=1;i<=q;++i) d[i].clear();
	for(int v:G[u]) if(!vis[v]) {
		int rt=0;
		function<void(int,int)>dfs4=[&](int x,int fz) {
			cur[x]=siz[v]-siz[x];
			for(int y:G[x]) if(y!=fz&&!vis[y]) dfs4(y,x),cur[x]=max(cur[x],siz[y]);
			if(!rt||cur[x]<cur[rt]) rt=x;
		};
		dfs4(v,u),dfs2(rt);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=2,x;i<=n;++i) cin>>x,G[x].push_back(i),G[i].push_back(x);
	for(int i=2,x;i<=n;++i) cin>>x,E[x].push_back(i),E[i].push_back(x);
	dfs1(1,0);
	for(int k=0;k<17;++k) for(int i=1;i+(2<<k)-1<=n;++i) st[k+1][i]=cmp(st[k][i],st[k][i+(1<<k)]);
	dfs2(1);
	cout<<ans+n<<"\n";
	return 0;
}
posted @ 2026-03-30 11:07  DaiRuiChen007  阅读(205)  评论(0)    收藏  举报