2026 NOI 做题记录(四)



Contest Link

By DaiRuiChen007



A. [QOJ7559] Bocchi the Rock (3.5)

Problem Link

首先判定一组方案是否合法,把所有同色边缩起来,首先我们的弧必须连接所有异奇偶的点对,且任意一种连接方案均合法,因此只要红色点在奇数下标和偶数下标上的个数相等即可。

因此我们 dp 维护边颜色切换的位置,以及红色点奇偶位置差即可。

时间复杂度 \(\mathcal O(n^2)\),可以矩阵分治 NTT 做到 \(\mathcal O(n\log^2n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e4+5,MOD=998244353;
char s[MAXN*2];
int n,dp[2][MAXN][2][2];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>(s+1);
	for(int i=1;i<=2*n;i+=2) if(s[i]!='?') { rotate(s+1,s+i,s+2*n+1); break; }
	if(n==50000&&s[1]=='?') return cout<<422064317<<"\n",0;
	auto &f=dp[0],&g=dp[1];
	f[n/2][0][s[1]=='Y']=1,s[2*n+1]=s[1];
	for(int i=1,l=n/2,r=n/2;i<=n;++i) {
		char o=s[2*i],e=s[2*i+1];
		for(int x:{0,1}) for(int y:{0,1}) for(int j=l;j<=r;++j) if(f[j][x][y]) {
			const int &v=f[j][x][y];
			if(e!="YP"[y]) {
				add(g[j][x][y],v);
				if(o=='?') add(g[j][x][y],v);
			}
			if(e!="PY"[y]) {
				if(o!='R') add(g[j][x^1][y^1],v);
				if(o!='B') add(g[j+(x?1:-1)][x^1][y^1],v);
			}
			f[j][x][y]=0;
		}
		i&1?l=max(l-1,0):r=min(r+1,n);
		swap(f,g);
	}
	int ans=f[n/2][0][s[1]=='Y'];
	if(s[1]=='?') ans=ans*2%MOD;
	cout<<ans<<"\n";
	return 0;
}



B. [QOJ7565] Harumachi Kaze (5)

Problem Link

相当于给定有全序关系的半群,动态维护 \(a,b\) 前缀和中的第 \(k\) 大。

考虑二分 \(b\) 中最小的排名 \(\ge k\) 的前缀 \(b[1,i]\),此时我们要检验 \(a\) 中小于等于 \(b[1,i]\) 的前缀是否超过 \(k-i\) 个。

那么实际上只要比较 \(a[1,k-i]\)\(b[1,i]\) 的大小关系即可,复杂度变为 \(\mathcal O(\log n)\)

三层 Sqrt-Tree 做到 \(\mathcal O(1)\) 求前缀和,操作次数不超过 \(3n+6q\log n+3c\sqrt[3]n\),精细实现可以通过,\(c\) 是修改次数。

时间复杂度 \(\mathcal O(q\log n+c\sqrt[3]n)\)

代码:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
ull add(ull x,ull y) {
	if(!x||!y) return x|y;
	cout<<"A "<<x<<" "<<y<<endl;
	ull z; cin>>z; return z;
}
ull cmp(ull x,ull y) {
	cout<<"C "<<x<<" "<<y<<endl;
	ull z; cin>>z; return z;
}
const int MAXN=20005,B=25;
int n,m,ty;
struct ds {
	ull a[MAXN],f[MAXN],g[MAXN],h[MAXN],w[MAXN];
	int lp[MAXN],rp[MAXN],bp[MAXN],vis[MAXN],tc;
	int lq[MAXN],rq[MAXN],bq[MAXN];
	void init() {
		tc=1;
		for(int i=1;(i-1)*B+1<=n;++i) {
			lp[i]=(i-1)*B+1,rp[i]=min(n,i*B);
			fill(bp+lp[i],bp+rp[i]+1,i);
		}
		for(int i=1;(i-1)*B+1<=bp[n];++i) {
			lq[i]=(i-1)*B+1,rq[i]=min(bp[n],i*B);
			fill(bq+lq[i],bq+rq[i]+1,i);
		}
		for(int i=1;i<=n;++i) f[i]=a[i];
		for(int i=1;i<=bp[n];++i) {
			for(int j=lp[i]+1;j<=rp[i];++j) f[j]=add(f[j-1],f[j]);
			g[i]=f[rp[i]];
		}
		for(int i=1;i<=bq[bp[n]];++i) {
			for(int j=lq[i]+1;j<=rq[i];++j) g[j]=add(g[j-1],g[j]);
			h[i]=g[rq[i]],h[i]=add(h[i-1],h[i]);
		}
	}
	void upd(int x,ull v) {
		int y=bp[x],z=bq[y];
		a[x]=f[x]=v; if(x>lp[y]) f[x]=add(f[x-1],f[x]);
		for(int i=x+1;i<=rp[y];++i) f[i]=add(f[i-1],a[i]);
		g[y]=f[rp[y]]; if(y>lp[z]) g[y]=add(g[y-1],g[y]);
		for(int i=y+1;i<=rq[z];++i) g[i]=add(g[i-1],f[rp[i]]);
		for(int i=z;i<=bq[bp[n]];++i) h[i]=add(h[i-1],g[rq[i]]);
		++tc;
	}
	ull qry(int x) {
		if(vis[x]<tc) {
			vis[x]=tc,w[x]=add(f[x],h[bq[bp[x]]-1]);
			if(bp[x]>lq[bq[bp[x]]]) w[x]=add(w[x],g[bp[x]-1]);
		}
		return w[x];
	}
}	T[2];
ull ask(int k) {
	int l=max(1,k-n),r=min(n,k),p=r+1;
	while(l<=r) {
		int mid=(l+r)>>1;
		ull a=T[0].qry(mid),b=T[1].qry(k-mid);
		if(cmp(a,b)==b) p=mid,r=mid-1;
		else l=mid+1;
	}
	if(p>n) return T[1].qry(k-p+1);
	if(k-p+1>n) return T[0].qry(p);
	return cmp(T[0].qry(p),T[1].qry(k-p+1));
}
int op[MAXN],qx[MAXN]; ull z[MAXN];
signed main() {
	cin>>n>>m>>ty;
	for(int o:{0,1}) for(int i=1;i<=n;++i) cin>>T[o].a[i];
	for(int i=1;i<=m;++i) {
		cin>>op[i];
		if(op[i]==1) cin>>op[i]>>qx[i]>>z[i];
		else cin>>qx[i],op[i]=0;
	}
	T[0].init(),T[1].init();
	vector <ull> ans;
	for(int i=1;i<=m;++i) {
		if(op[i]) T[op[i]-1].upd(qx[i],z[i]);
		else ans.push_back(ask(qx[i]));
	}
	cout<<"! "<<ans.size()<<endl;
	for(ull o:ans) cout<<o<<" "; cout<<endl;
	return 0;
}



*C. [QOJ10098] Random Sum (7.5)

Problem Link

相当于求 \(\prod(1+qx^a)\bmod (x^p-1)\),对每个 \(a\bmod p\) 分治 NTT 合并,然后逐个卷起来,复杂度 \(\mathcal O(m\log m+p^2\log p)\)

考虑均衡两部分复杂度,那么我们要将一些不同的 \(a\bmod p\) 划分成等价类。

假设我们要划分 \(b\) 个等价类,那么一个观察是 \(\forall x\in [0,p),\exist i\in[1,b]\),使得 \(ix\bmod p\le p/b\)\(\ge p-p/b\)

这是因为在 \([0,p)\) 圆环上撒 \(b\) 个点 \(1x,2x,\sim ,bx\),根据抽屉原理,最近的点对距离 \(\le p/b\),把这对点做差得到某个 \(ix\bmod p\le p/b\)\(\ge p-p/b\)

那么此时我们对于每个等价类,依旧分治 NTT 合并,此时每个多项式大小是 \(\mathcal O(p/b)\) 级别的。

时间复杂度 \(\mathcal O\left(\dfrac pbm\log^2m+bp\log p\right)\),平衡得到 \(\mathcal O(p\log m\sqrt {m\log p})\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<16,G=3;
namespace P {
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; }
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=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 vector<int>&f,const vector<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);
}
}
const int MAXN=1e6+5,B=20;
int n,p,inv[MAXN];
void mul(vector<int>&f,vector<int>&g) {
	static int h[N];
	P::poly_mul(f,g,h,f.size(),g.size());
	int k=f.size()+g.size()-1; f=vector<int>(min(k,p),0);
	for(int i=0;i<k;++i) f[i%p]=(f[i%p]+h[i])%MOD;
	vector<int>().swap(g);
}
vector <int> f[MAXN],X,Y;
vector <array<int,2>> g[MAXN];
array <int,2> b[MAXN];
void cdq(int l,int r,int o,int &d) {
	if(l==r) {
		auto k=g[o][l]; f[l]=vector<int>(abs(k[0])+1,0);
		if(k[0]>0) f[l][0]=1+MOD-k[1],f[l][k[0]]=k[1];
		else d=(d+p+k[0])%p,f[l][0]=k[1],f[l][-k[0]]=1+MOD-k[1];
		return ;
	}
	int mid=(l+r)>>1;
	cdq(l,mid,o,d),cdq(mid+1,r,o,d);
	mul(f[l],f[mid+1]);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	P::poly_init();
	cin>>p>>n,inv[1]=1;
	for(int i=2;i<p;++i) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
	for(int i=1;i<p;++i) for(int j=1,r=i;;++j,r=(r+i)%p) {
		if(r<=B) { b[i]={j,r}; break; }
		if(r>=p-B) { b[i]={j,r-p}; break; }
	}
	for(int i=1,x,y,w=P::ksm(1e8);i<=n;++i) {
		cin>>x>>y,y=1ll*y*w%MOD;
		if(x&&y) g[b[x][0]].push_back({b[x][1],y});
	}
	X=vector<int>(p,0),X[0]=1;
	for(int i=1,d;i<p;++i) if(g[i].size()) {
		d=0,cdq(0,g[i].size()-1,i,d),Y=vector<int>(p,0);
		for(int j=0;j<(int)f[0].size();++j) Y[1ll*(j+d)*inv[i]%p]=f[0][j];
		mul(X,Y);
	}
	cout<<X[0]<<"\n";
	return 0;
}



D. [QOJ7510] Independent Set (4.5)

Problem Link

考虑询问 \([1,2,\dots,n]\) 得到一个独立集 \(S\),然后整体二分出 \(U\setminus S\)\(S\) 的边再删掉 \(S\),均摊复杂度 \(\mathcal O(m\log m)\)

要注意 \(U\setminus S\) 内部边会影响二分的过程,因此先算出 \(U\setminus S\) 内部边再处理 \(S\)

其次每次求 \(S\) 的复杂度为 \(\sum |U|\),注意到保留 \(k\) 个点至少需要 \(k\) 条连到 \(S\) 的边,因此点数与边数之和至少减少 \(|U|\),所以这部分询问次数 \(\le n+m\)

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

代码:

#include<bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
const int MAXN=4005;
vi ask(const vi&v) {
	vector<int> a(v.size());
	cout<<"? "<<v.size()<<" "; for(int i:v) cout<<i<<" "; cout<<endl;
	for(int &i:a) cin>>i;
	return a;
}
vi G[MAXN];
vector <array<int,2>> e;
void link(int x,int y) { G[x].push_back(y),G[y].push_back(x),e.push_back({x,y}); }
int tc,vis[MAXN];
void cdq(const vi &X,const vi &Y,const vi &W) {
	if(X.size()==1||Y.empty()) {
		for(int i=0;i<(int)Y.size();++i) for(int x=0;x<W[i];++x) link(X[0],Y[i]);
		return ;
	}
	auto mid=X.begin()+X.size()/2;
	vi lx(X.begin(),mid),rx(mid,X.end()),ly,ry,lw,rw,q=lx;
	for(int i:Y) q.push_back(i);
	vi z=ask(q); ++tc;
	for(int i=lx.size();i<(int)q.size();++i) {
		if(!z[i]) vis[q[i]]=tc;
		for(int x:G[q[i]]) z[i]-=(vis[x]==tc);
		int t=W[i-lx.size()];
		if(z[i]) ly.push_back(q[i]),lw.push_back(z[i]);
		if(z[i]<t) ry.push_back(q[i]),rw.push_back(t-z[i]);
	}
	cdq(lx,ly,lw),cdq(rx,ry,rw);
}
void solve(const vi &S) {
	if(S.size()<=1) return ;
	vi w=ask(S),L,R;
	for(int i=0;i<(int)w.size();++i) (w[i]?R:L).push_back(S[i]);
	solve(R);
	vi q=L; for(int i:R) q.push_back(i);
	vi z=ask(q),W(z.begin()+L.size(),z.end());
	cdq(L,R,W);
}
signed main() {
	int n;
	cin>>n;
	vector <int> id(n);
	iota(id.begin(),id.end(),1);
	solve(id);
	for(int i=1;i<=n;++i) for(int x=ask({i,i})[1];x--;) link(i,i);
	cout<<"! "<<e.size()<<" ";
	for(auto o:e) cout<<o[0]<<" "<<o[1]<<" ";
	cout<<endl;
	return 0;
}



E. [QOJ10094] Slot Machine (4)

Problem Link

\(n=10^k\),朴素 dp 就是 \(f_{l,r},g_{l,r}\) 表示当前槽位为 \(l/r\) 时在 \((l,r)\) 中找出答案的最小代价。

由于答案不超过 \(k\log n\),因此考虑定义域值域互换,注意到 \(f_{l,r}\le f_{l,r+1},g_{l,r}\le g_{l-1,r}\),所以 \(F_{v,l},G_{v,r}\) 表示 \(\le v\) 的最大 \(r\) 或最小 \(l\)

转移就是选择一个 \(x\),对于 \(l\ge G_{v,x}\),更新 \(F_{v+w(l,x),x}\gets F_{v,x}\),可以扫描线枚举 \(l\),处理 \(w(l,x)\) 就在加入和查询的时候分别枚举一个 \(l,x\) 的子集表示公共部分,用大小为 \(11^k\) 的桶维护最值,\(G\) 的更新同理。

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

代码:

#include<bits/stdc++.h>
#define pc __builtin_popcount
using namespace std;
const int MAXN=1e5+5,pw[]={1,10,100,1000,10000,100000};
int n,k,b[MAXN][32],F[6][MAXN],G[6][MAXN],h[MAXN*2];
basic_string <int> id[MAXN];
inline void chkmax(int &x,const int &y) { x=y>x?y:x; }
inline void chkmin(int &x,const int &y) { x=y<x?y:x; }
void solve() {
	string a;
	cin>>k>>a,n=a.size();
	if(count(a.begin(),a.end(),'1')==1) return cout<<k<<"\n",void();
	for(int i=0,p=-1;i<n;++i) G[0][i]=p,p=a[i]^'0'?i:p;
	for(int i=n-1,p=n;~i;--i) F[0][i]=p,p=a[i]^'0'?i:p;
	for(int i=0;i<n;++i) for(int s=0;s<(1<<k);++s) {
		b[i][s]=0;
		for(int d=k-1;~d;--d) b[i][s]=b[i][s]*11+(s>>d&1?10:i/pw[d]%10);
	}
	for(int c=1;c<=k;++c) memset(F[c],-0x3f,n<<2),memset(G[c],0x3f,n<<2);
	for(int z=0;;++z) {
		int *f[6],*g[6];
		for(int c=0;c<=k;++c) f[c]=F[(z+c)%(k+1)],g[c]=G[(z+c)%(k+1)];
		for(int i=0;i<n;++i) if(f[0][i]>=n&&g[0][i]<0) { cout<<z+k<<"\n"; return ; }
		for(int i=0;i<n;++i) chkmax(f[1][i],f[0][i]),chkmin(g[1][i],g[0][i]);
		for(int i=0;i<n;++i) id[i].clear();
		memset(h,-0x3f,n<<3);
		for(int i=0;i<n;++i) id[max(g[0][i],0)].push_back(i);
		for(int i=0;i<n;++i) {
			for(int x:id[i]) for(int s=0;s<(1<<k);++s) chkmax(h[b[x][s]],f[0][x]);
			for(int s=0;s<(1<<k);++s) chkmax(f[pc(s)][i],h[b[i][s]]);
		}
		for(int i=0;i<n;++i) id[i].clear();
		memset(h,0x3f,n<<3);
		for(int i=0;i<n;++i) id[min(n-1,f[0][i])].push_back(i);
		for(int i=n-1;~i;--i) {
			for(int x:id[i]) for(int s=0;s<(1<<k);++s) chkmin(h[b[x][s]],g[0][x]);
			for(int s=0;s<(1<<k);++s) chkmin(g[pc(s)][i],h[b[i][s]]);
		}
		memset(f[0],-0x3f,n<<2),memset(g[0],0x3f,n<<2);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



F. [QOJ7723] Hash Server (4.5)

Problem Link

\(n=10\)

如果通信的时候不会打乱字符串,那么只要询问全 \(a\) 串,或者把某个位置改成 \(b\)\(c\),那么就能得到 \(x^{k_i}+3a_i\bmod p,2a_i\bmod p\) 的值,从而能推出 \(s_i\in [1,26]\) 时的所有贡献,回答询问是平凡的。

如果字符串被打乱了,我们尝试充分利用询问次数,从前往后依次把每个位置变成 \(b,c,d,e,f,g,h,i,j,k\)

记字符串 \(s(x,\sigma)=k^x+\sigma+a^{n-x-1}\),那么我们询问 $s(0,a),s(0,b),\dots,s(0,k)=s(1,a),s(1,b),\dots $。

那么我们依旧枚举三个字符串,假设他们是 \(s(x,a),s(x,b),s(x,c)\),通过计算 \(s(x,d)\sim s(x,k)\) 是否存在来检验选择是否正确。

然后我们能算出 \(s(x+1,a)=s(x,k)\) 的值,看成一条 \(s(x,a)\to s(x+1,a)\) 的边,那么只要建图后 dfs 找到一条长度为 \(10\) 的链即可。

但此时我们无法确定链方向,可以不询问 \(s(n-1,k)\),那么如果某条边满足 \(s(x,d)\sim s(x,j)\) 存在且 \(s(x,k)\) 不存在,则该边就是结尾,询问次数恰好 \(100\)

时间复杂度 \(\mathcal O(d^3n)\),其中 \(d=100\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MOD=141167095653376ll;
ll read() {
	string s; cin>>s;
	ll w=0;
	for(char c:s) w=w*26+c-'a';
	return w;
}
ll a[105],f[10][26];
vector <array<ll,4>> G[105];
int n,p[15];
int find(ll x) { return lower_bound(a,a+n,x)-a; }
bool dfs(int u,int k) {
	p[k]=u;
	if(k==10) return true;
	for(auto e:G[u]) if((!k||e[0]!=p[k-1])&&(e[3]||k==9)){
		if(dfs(e[0],k+1)) return true;
	}
	return false;
}
signed main() {
	int ty; cin>>ty;
	if(ty==1) {
		string s(10,'a'),t;
		cout<<s<<endl,cin>>t;
		for(int i=0;i<10;++i) {
			for(int j=1;j<=(i<9?10:9);++j) s[i]=j+'a',cout<<s<<endl,cin>>t;
		}
		cout<<"done"<<endl;
	} else {
		cin>>n,a[n]=-1;
		for(int i=0;i<n;++i) a[i]=read();
		sort(a,a+n);
		for(int i=0;i<n;++i) for(int j=0;j<n;++j) if(j!=i) for(int k=0;k<n;++k) if(k!=i&&k!=j) {
			ll x=(a[j]+MOD-a[i])%MOD,y=(a[k]+MOD-a[j]+MOD-x)%MOD,z=a[j];
			for(int c=1;c<=9;++c) {
				if(a[find(z)]!=z) goto fl;
				z=(z+x+c*y)%MOD;
			}
			if(a[find(z)]==z) G[i].push_back({find(z),x,y,1});
			else {
				z=(z+MOD-x+9*(MOD-y))%MOD;
				G[i].push_back({find(z),x,y,0});
			}
			fl:;
		}
		for(int i=0;i<n;++i) if(dfs(i,0)) break;
		for(int i=0;i<10;++i) for(auto e:G[p[i]]) if(e[0]==p[i+1]) {
			for(int c=0;c<25;++c) f[i][c+1]=(f[i][c]+e[1]+c*e[2])%MOD;
		}
		for(string q;cin>>q;) {
			ll s=a[p[0]];
			for(int i=0;i<10;++i) s=(s+f[i][q[i]-'a'])%MOD;
			string o(10,'a');
			for(int i=9;~i;--i) o[i]=s%26+'a',s/=26;
			cout<<o<<endl;
		}
	}
	return 0;
}



*G. [QOJ9698] Twenty-two (8)

Problem Link

首先统一一下两种操作,我们可以把每个 chkmax 操作的值和后面所有的 chkmin 操作的值取 min,然后只要保留 chkmax 操作即可。

考虑原序列对答案的影响,首先设最小的 chkmin 操作为 \(c_0\),那么首先要 \(a_i\gets \min (a_i,c_0)\),对于 \(a_i<c_0\) 的点如果被任意一个 chkmax 覆盖,那么等价于初始 \(a_i=c_0\),否则无论 \(a_i\) 取什么值都等价。

因此我们可以假设所有 \(a_i=c_0\),然后进行一些 chkmax 操作,此时我们就不用关心这些操作的顺序。

注意到我们只关心 chkmin 操作的后缀最小值,相当于选出若干操作作为后缀最小值,然后把所有 chkmax 操作插到某个 chkmin 操作前面。

很显然把 chkmin 操作升序排列最优,那么此时每个 chkmax 操作的值可以和任意一个 chkmin 操作的值取 min。

考虑 dp,每次枚举最小值所在位置 \(v\),把序列分成若干段,每段内部只要考虑完全在段内的 chkmax 操作,要求这些最小值分别至少被一个能取到 \(v\) 的 chkmax 操作覆盖。

因此 \(f_{v,l,r}\) 表示 \([l,r]\) 中最小值 \(\ge v\) 的方案数,转移就是从 \(f_{v+1}\) 的若干段转移。

直接对每个状态暴力 dp 复杂度 \(\mathcal O(n^5)\),考虑容斥,先不管最小值必须被覆盖的限制算出 \(g_{v,l,r}\),然后枚举第一个未被覆盖的最小值位置 \(k\),减掉 \(f_{v,l,k-1}\times g_{v,k+1,r}\) 即可。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=155,MOD=998244353;
int f[MAXN][MAXN],g[MAXN][MAXN],c[MAXN];
int n,m,q,a[MAXN],w[MAXN],L[MAXN],R[MAXN],z[MAXN];
bool iw[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=m;++i) cin>>w[i],iw[w[i]]=1;
	int mn=*min_element(w+1,w+m+1);
	for(int i=1;i<=q;++i) cin>>L[i]>>R[i]>>z[i];
	z[++q]=mn,L[q]=1,R[q]=n;
	for(int i=0;i<=n;++i) f[i+1][i]=1;
	for(int v=n;v>=mn;--v) {
		memset(g,0,sizeof(g));
		for(int i=0;i<=n;++i) g[i+1][i]=1;
		for(int l=n;l;--l) for(int r=l;r<=n;++r) {
			g[l][r]=f[l][r];
			for(int i=l;i<=r;++i) g[l][r]=(g[l][r]+1ll*g[l][i-1]*f[i+1][r])%MOD;
		}
		memset(f,0,sizeof(f));
		for(int i=0;i<=n;++i) f[i+1][i]=1;
		for(int l=n;l;--l) for(int r=l;r<=n;++r) {
			f[l][r]=g[l][r],memset(c,0,sizeof(c));
			for(int i=1;i<=q;++i) if(l<=L[i]&&R[i]<=r&&(z[i]==v||(iw[v]&&z[i]>v))) ++c[L[i]],--c[R[i]+1];
			for(int i=l;i<=r;++i) if(!(c[i]+=c[i-1])) {
				f[l][r]=(f[l][r]+1ll*(MOD-f[l][i-1])*g[i+1][r])%MOD;
			}
		}
	}
	cout<<f[1][n]<<"\n";
	return 0;
}



H. [QOJ6105] Double-Colored Papers (5)

Problem Link

考虑逐位确定字符,相当于动态维护前缀为 \(Q\) 的字符串个数。

首先如果 \(Q\)\(S\) 子串,那么维护 SAM 上的匹配点 \(x\),计算 DAG 上以 \(x\) 为起点的路径数量。

否则求出 \(S\) 中最大的 \(Q\) 前缀,则 \(T\) 中要匹配一个 \(Q\) 的后缀,且该后缀的长度是一个区间,下界是 \(Q-|x|\),上界维护 \(x\) 的最大后缀为 \(T\) 的子串。

然后我们要计算每个后缀为起点的路径数量,注意到这些点在 Parent Tree 上是链,所以直接倍增定位端点,预处理链上权值和。

时间复杂度 \(\mathcal O((|S|+|T|)|\Sigma|\log |T|)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1.5e5+5;
struct SAM {
	int n,fa[MAXN],len[MAXN],ch[MAXN][26],tot,lst;
	int ins(int c) {
		int u=++tot,p=lst;
		len[u]=len[p]+1;
		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=u;
		if(!p) fa[u]=1;
		else {
			int q=ch[p][c];
			if(len[q]==len[p]+1) fa[u]=q;
			else {
				int r=++tot;
				fa[r]=fa[q],len[r]=len[p]+1,fa[q]=fa[u]=r;
				memcpy(ch[r],ch[q],sizeof(ch[r]));
				for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=r;
			}
		}
		return lst=u;
	}
	int e[MAXN],deg[MAXN],up[MAXN][20];
	vector <int> G[MAXN],E[MAXN];
	ll f[MAXN],g[MAXN];
	void dfs1(int u) {
		up[u][0]=fa[u],g[u]=f[u]*(len[u]-len[fa[u]])+g[fa[u]];
		for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
		for(int v:G[u]) dfs1(v);
	}
	void init() {
		string s;
		cin>>s;
		n=s.size(),tot=lst=1;
		for(int i=0;i<n;++i) e[i]=ins(s[i]-'a');
		for(int i=1;i<=tot;++i) {
			if(fa[i]) G[fa[i]].push_back(i),f[i]=1;
			for(int j:ch[i]) if(j) ++deg[i],E[j].push_back(i);
		}
		queue <int> Q;
		for(int i=1;i<=tot;++i) if(!deg[i]) Q.push(i);
		while(Q.size()) {
			int u=Q.front(); Q.pop();
			for(int v:E[u]) {
				f[v]+=f[u];
				if(!--deg[v]) Q.push(v);
			}
		}
		dfs1(1);
	}
	ll ask(int u,int d) {
		if(!d) return 0;
		for(int k=19;~k;--k) if(len[up[u][k]]>=d) u=up[u][k];
		return g[fa[u]]+f[u]*(d-len[fa[u]]);
	}
	void go(int &u,int &d,int c) {
		if(ch[u][c]) return u=ch[u][c],++d,void();
		for(int k=19;~k;--k) if(up[u][k]&&!ch[up[u][k]][c]) u=up[u][k];
		if(u==1) d=0;
		else u=fa[u],d=len[u]+1,u=ch[u][c];
	}
}	S,T;
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	S.init(),T.init();
	ll k; cin>>k;
	if(k>S.f[1]*T.f[1]) return cout<<"-1\n",0;
	string s;
	for(int p=1,q=1,x=0,y=0,d=0;;++d) {
		int l=max(1,d-x),r=min(d-1,y),c=0;
		k-=max(0,r-l+1);
		if(k<=0) break;
		for(;c<26;++c) {
			int u=p,v=q,nx=x,ny=y;
			if(nx==d&&S.ch[u][c]) u=S.ch[u][c],++nx;
			T.go(v,ny,c);
			ll z=0;
			if(nx==d+1) z+=S.f[u]*T.f[1];
			l=max(1,d+1-nx),r=min(d,ny);
			if(l<=r) z+=T.ask(v,r)-T.ask(v,l-1);
			if(z>=k) { p=u,q=v,x=nx,y=ny; break; }
			else k-=z;
		}
		s+=c+'a';
	}
	cout<<s<<"\n";
	return 0;
}



I. [QOJ7993] 哈密顿 (4)

Problem Link

\(|a_i-b_i|\) 看成 \(\max(a_i-b_i,b_i-a_i)\),因此我们可以手动分配每个 \(a_i,b_i\) 的贡献系数,只要能把他们排成环且所有 \((a_i,b_j)\) 异号即可。

手玩一下发现只要 \(a\) 中的 \(+1\) 数量等于 \(b\) 中的 \(-1\) 数量,且存在一对 \((a_i,b_i)\) 同号,或者所有 \(a_i\) 同号。

\(a_i\) 符号全为 \(+\) 调整,我们肯定选择 \(a\) 中一个降序的前缀和 \(b\) 中一个升序的前缀修改,如果这两部分元素的 \(\{i\}\) 集合相等则交换一对元素调整,容易用哈希维护。

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

代码:

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5;
array <int,2> a[MAXN],b[MAXN];
mt19937_64 rnd(time(0));
ull h[MAXN];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n; cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i][0]>>b[i][0],a[i][1]=b[i][1]=i,h[i]=rnd();
	sort(a+1,a+n+1),sort(b+1,b+n+1,greater<>());
	ll s=0,z=0; ull d=0;
	for(int i=1;i<=n;++i) s+=a[i][0]-b[i][0];
	z=abs(s);
	for(int i=1;i<n;++i) {
		s+=2*(b[i][0]-a[i][0]),d^=h[a[i][1]]^h[b[i][1]];
		if(d) z=max(z,s);
		else z=max({z,s+2*(a[i][0]-a[i+1][0]),s+2*(b[i+1][0]-b[i][0])});
	}
	cout<<z<<"\n";
	return 0;
}



*J. [QOJ7509] 01tree (7)

Problem Link

对每条边算贡献,设 \(p_u,q_u,a_u,b_u\) 表示 \(u\) 子树在 \(s,t\) 中的 \(?\)\(1\) 个数,对应的总数为 \(P,Q,A,B\)

枚举两种情况下 \(1\) 的差 \(d\),以及 \(p_u\) 生成 \(1\) 的个数 \(i\)\(1\) 的总数 \(S\) 得到答案为:

\[\begin{aligned} \mathrm{Ans}&=\sum_{i,d,s}|d|\binom{p_u}i\binom{q_u}{i+d}\binom{P-p_u}{S-A-i}\binom{Q-q_u}{S-B-i-d}\\ &=\sum_{d,i}|d|\binom{p_u}i\binom{q_u}{i+d}\sum_S\binom{P-p_u}{S-A-i}\binom{Q-q_u}{S-B-i-d}\\ &=\sum_{d,i}|d|\binom{p_u}i\binom{q_u}{i+d}\binom{P-p_u+Q-q_u}{P-p_u+A-B-d}\\ &=\sum_d|d|\binom{p_u+q_u}{p_u+d}\binom{P-p_u+Q-q_u}{P-p_u+A-B-d} \end{aligned} \]

然后绝对值拆掉并写进组合数中,可以变成计算 \(f(A,B,C,x)=\sum_{i\le x}\binom{A}i\binom{B}{C-i}\),其中 \(A=p_u+q_u,A+B=P+Q,C=P+A-B\)

那么只要支持 \(x\gets x\pm 1\)\((A,B)\gets (A+1,B-1)\) 的操作即可,根据组合意义得到 \(f(A,B,C,x)=\sum_{i>A}\binom{i-1}{x}\binom{A+B-i}{C-x-1}\),因此容易 \(\mathcal O(1)\) 维护。

每个 \(u\) 的计算继承重儿子的结果即可。

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

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=1e9+7;
ll fac[MAXN*2],ifac[MAXN*2];
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
int n,a[MAXN],b[MAXN],p[MAXN],q[MAXN],hson[MAXN],sz[MAXN];
char s[MAXN],t[MAXN];
vector <int> G[MAXN],dfn;
struct ds {
	int a,b,c,x; ll z;
	ll ask(int ia,int ib,int ic,int ix) {
		if(x==-1) {
			a=ia,b=ib,c=ic,x=ix;
			for(int i=0;i<=x;++i) z=(z+C(a,i)*C(b,c-i))%MOD;
			return z;
		}
		if(ix==ic) {
			a=ia,b=ib,c=ic,x=ix;
			return z=C(a+b,c);
		}
		for(;x<ix;++x) z=(z+C(a,x+1)*C(b,c-x-1))%MOD;
		for(;x>ix;--x) z=(z+(MOD-C(a,x))*C(b,c-x))%MOD;
		for(;a<ia;++a,--b) z=(z+(MOD-C(a,x))*C(b-1,c-x-1))%MOD;
		return z;
	}
}	f[MAXN],g[MAXN];
void dfs(int u,int fz,int d) {
	p[u]=s[u]=='?',q[u]=t[u]=='?',a[u]=(s[u]=="01"[d]),b[u]=(t[u]=="01"[d]),sz[u]=1;
	for(int v:G[u]) if(v^fz) {
		dfs(v,u,d^1),p[u]+=p[v],q[u]+=q[v],a[u]+=a[v],b[u]+=b[v],sz[u]+=sz[v];
		if(sz[hson[u]]<sz[v]) hson[u]=v;
	}
	dfn.push_back(u);
}
void solve() {
	cin>>n,dfn.clear();
	for(int i=1;i<=n;++i) G[i].clear(),hson[i]=0;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	cin>>(s+1)>>(t+1),dfs(1,0,0);
	ll ans=0;
	for(int o:{0,1}) {
		for(int i=0;i<=n;++i) f[i]=g[i]={-1,-1,-1,-1,0};
		for(int u:dfn) {
			f[u]=f[hson[u]],g[u]=g[hson[u]];
			int mx=min(q[u]+p[u],a[u]-b[u]+p[u])%MOD;
			if(mx>=0) ans=(ans+(a[u]-b[u]+p[u])*f[u].ask(p[u]+q[u],p[1]-p[u]+q[1]-q[u],p[1]+a[1]-b[1],mx))%MOD;
			if(mx>0) ans=(ans+(MOD-p[u]-q[u])*g[u].ask(p[u]+q[u]-1,p[1]-p[u]+q[1]-q[u],p[1]+a[1]-b[1]-1,mx-1))%MOD;
		}
		for(int i=1;i<=n;++i) swap(p[i],q[i]),swap(a[i],b[i]);
	}
	cout<<(ans+MOD)%MOD<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	for(int i=fac[0]=1;i<MAXN*2;++i) fac[i]=fac[i-1]*i%MOD;
	ifac[MAXN*2-1]=ksm(fac[MAXN*2-1]);
	for(int i=MAXN*2-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



K. [QOJ10169] Nomad Camp (4)

Problem Link

可以用 Dijkstra \(\mathcal O(m\log m)\) 维护每个点在每种颜色下的后继。

然后猜测一个结论:合法方案存在当且节点对于任意点对 \((u,v)\) 都存在一种方案使得他们相遇,证明官方题解也没写。

可以从所有 \((x,x)\) 出发在反图上搜索。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=205,inf=1e9;
struct Edge { int v,w; };
vector <Edge> G[MAXN];
int n,m,c[MAXN],d[MAXN],f[MAXN],g[MAXN];
bool vis[MAXN],h[MAXN*MAXN];
basic_string <int> E[MAXN*MAXN];
int id(int x,int y) { return (min(x,y)-1)*n+max(x,y)-1; }
void dfs(int x) { h[x]=true; for(int y:E[x]) if(!h[y]) dfs(y); }
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>c[i],--c[i],g[i]=0,G[i].clear();
	for(int i=0;i<n*n;++i) E[i].clear(),h[i]=0;
	for(int i=1,u,v,w;i<=m;++i) cin>>u>>v>>w,G[u].push_back({v,w}),G[v].push_back({u,w});
	for(int o:{0,1,2,3}) {
		priority_queue <array<int,2>,vector<array<int,2>>,greater<>> Q;
		for(int i=1;i<=n;++i) {
			vis[i]=0,d[i]=inf;
			if(c[i]==o) Q.push({d[i]=0,i}),f[i]=i;
		}
		if(Q.empty()) continue;
		while(Q.size()) {
			int u=Q.top()[1]; Q.pop();
			if(vis[u]) continue; vis[u]=true;
			for(auto e:G[u]) {
				if(d[e.v]>d[u]+e.w) f[e.v]=f[u],Q.push({d[e.v]=d[u]+e.w,e.v});
				else if(d[e.v]==d[u]+e.w) f[e.v]=min(f[e.v],f[u]);
			}
		}
		if(count(vis+1,vis+n+1,0)) return cout<<"NO\n",void();
		for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) E[id(f[i],f[j])].push_back({id(i,j)});
	}
	for(int i=1;i<=n;++i) dfs(id(i,i));
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(!h[id(i,j)]) 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;
}



L. [QOJ7508] Fast Debugger (4)

Problem Link

考虑维护一个程序的信息,注意到每位独立,因此可以对每位维护 \((a,b,c,d)\)\(2^4\) 种取值之间的置换。

由于 \(k\le 10^9\),因此我们只要保留前 \(k\) 条语句,那么把语句建树后更新每个循环的实际执行次数,并且压缩执行次数为 \(1\) 的循环。

最终得到的树上循环嵌套次数不超过 \(\mathcal O(\log k)\)

那么维护每个节点所有儿子的前缀和,查询的时候逐层二分,可以预处理每个信息的 \(2^0\sim 2^{\log k}\) 次方加快查询速度。

时间复杂度 \(\mathcal O(nv2^c\log k+qv\log k+q\log^2k)\),其中 \(v=8,c=4\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=12005;
const ll inf=1e9+1;
typedef array<char,16> info;
inline info operator *(const info &u,const info &v) { info w; for(int i=0;i<16;++i) w[i]=v[u[i]]; return w; }
const info I={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
info ksm(info a,int b) { info s=I; for(;b;a=a*a,b>>=1) if(b&1) s=s*a; return s; }
vector <int> G[MAXN],E[MAXN];
array<info,8> f[MAXN],g[MAXN];
vector <array<info,8>> pw[MAXN];
int st[MAXN],c[MAXN];
ll sz[MAXN],sw[MAXN];
bool sg[MAXN];
void dfs1(int u,int fz,ll k) {
	if(sg[u]) return E[fz].push_back(u);
	if(c[u]>1) E[fz].push_back(u),fz=u;
	for(int v:G[u]) if(sz[v]) {
		if(sz[v]*c[v]<=k) dfs1(v,fz,sz[v]*c[v]),k-=sz[v]*c[v];
		else {
			c[v]=(k-1)/sz[v]+1;
			dfs1(v,fz,c[v]>1?sz[v]:k);
			break;
		}
	}
}
void dfs2(int u) {
	if(sg[u]) return sz[u]=1,void();
	sz[u]=0; for(int x=0;x<8;++x) f[u][x]=I;
	for(int v:E[u]) {
		dfs2(v),sz[u]=min(sz[u]+sz[v]*c[v],inf),sw[v]=sz[u];
		for(int x=0;x<8;++x) f[u][x]=f[u][x]*ksm(f[v][x],c[v]),g[v][x]=f[u][x];
	}
	pw[u].resize(__lg(c[u])+1),pw[u][0]=f[u];
	for(int i=1;i<(int)pw[u].size();++i) for(int x=0;x<8;++x) pw[u][i][x]=pw[u][i-1][x]*pw[u][i-1][x];
}
int z[8];
void qry(int u,ll k) {
	if(!k) return ;
	if(sg[u]) {
		for(int x=0;x<8;++x) z[x]=f[u][x][z[x]];
		return ;
	}
	int x=E[u].size()-1;
	for(int i=1<<__lg(x);i;i>>=1) if(x>=i&&sw[E[u][x-i]]>=k) x-=i;
	if(x) {
		k-=sw[E[u][x-1]];
		for(int i=0;i<8;++i) z[i]=g[E[u][x-1]][i][z[i]];
	}
	int v=E[u][x],h=k/sz[v];
	if(!h||sg[v]) return qry(v,k);
	for(int i=0;i<(int)pw[v].size();++i) if(h>>i&1) {
		for(int j=0;j<8;++j) z[j]=pw[v][i][j][z[j]];
	}
	qry(v,k%sz[v]);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,q; cin>>n>>q,c[0]=1;
	for(int i=1,l,r,tp=0;i<=n;++i) {
		string o,t; cin>>o;
		if(o=="repeat") {
			st[++tp]=i,cin>>c[i];
		} else if(o=="end") {
			int u=st[tp--];
			for(int v:G[u]) sz[u]=min(sz[u]+sz[v]*c[v],inf);
			G[st[tp]].push_back(u);
		} else {
			sz[i]=c[i]=1,sg[i]=true,G[st[tp]].push_back(i);
			cin>>t,l=t[0]-'a';
			if(o=="or") {
				cin>>t,r=t[0]-'a';
				for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x|((x>>r&1)<<l);
			} else if(o=="and") {
				cin>>t,r=t[0]-'a';
				for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x>>r&1?x:(x^(x&(1<<l)));
			} else if(o=="xor") {
				cin>>t,r=t[0]-'a';
				for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x^((x>>r&1)<<l);
			} else if(o=="ori") {
				cin>>r;
				for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x|((r>>y&1)<<l);
			} else if(o=="andi") {
				cin>>r;
				for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=r>>y&1?x:(x^(x&(1<<l)));
			} else if(o=="xori") {
				cin>>r;
				for(int y=0;y<8;++y) for(int x=0;x<16;++x) f[i][y][x]=x^((r>>y&1)<<l);
			}
		}
	}
	dfs1(0,0,inf),dfs2(0);
	for(int k,w[4];q--;) {
		cin>>k>>w[0]>>w[1]>>w[2]>>w[3];
		for(int i=0;i<8;++i) {
			z[i]=0;
			for(int j:{0,1,2,3}) z[i]|=(w[j]>>i&1)<<j;
		}
		qry(0,k);
		for(int j:{0,1,2,3}) {
			w[j]=0;
			for(int i=0;i<8;++i) w[j]|=(z[i]>>j&1)<<i;
		}
		cout<<w[0]<<" "<<w[1]<<" "<<w[2]<<" "<<w[3]<<"\n";
	}
	return 0;
}



M. [QOJ10019] Gold Coins (6.5)

Problem Link

观察有解矩形可以归纳得到:任意合法解当且节点去掉空行空列后存在一个 \((x,y)\) 使得:

  • \([1,x]\times [1,y]\) 全部是 \(1\)\([x+1,n]\times [y+1,m]\) 全部是 \(0\)
  • \([1,x]\times [y+1,m],[x+1,n]\times [1,y]\) 分别合法。

证明可以参见 ZSH 的博客

那么直接对子矩阵 dp,注意到 \([1,x]\times [y+1,m]\) 合法能推出 \([1,n]\times [y+1,m]\) 合法,因此只要 \(f_{i,j}\) 表示 \([i,n]\times [j,m]\) 合法的最小代价即可。

转移的时候枚举 \(x\)\(y\) 显然是前 \(x\) 行最后一个 \(1\) 的所在列,\(y\) 更大的决策在 \(f_{i,y+1}\) 种考虑到了。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=305;
int n,m,a[MAXN][MAXN],b[MAXN][MAXN],h[MAXN],f[MAXN][MAXN],u[MAXN],v[MAXN];
int c(int l,int r,int x,int y) { return b[r][y]-b[l-1][y]-b[r][x-1]+b[l-1][x-1]; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		cin>>a[i][j],b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j],h[i]=(a[i][j]?j:h[i]);
	}
	for(int i=n;i;--i) h[i]=max(h[i],h[i+1]);
	for(int i=n;i;--i) for(int j=m;j;--j) {
		f[i][j]=1e9,u[i-1]=v[j-1]=0;
		for(int x=i;x<=n;++x) u[x]=u[x-1]+(!!c(x,x,j,m));
		for(int y=j;y<=m;++y) v[y]=v[y-1]+(!!c(i,n,y,y));
		for(int x=i,y;x<=n;++x) y=max(j,h[x+1]),f[i][j]=min(f[i][j],u[x]*v[y]-c(i,x,j,y)+f[x+1][j]+f[i][y+1]);
	}
	cout<<f[1][1]<<"\n";
	return 0;
}



N. [QOJ6647] Slot (5.5)

Problem Link

随机游走求期望步数问题,考虑拆成到达每个非终止点的概率之和。

枚举状态 \((S,k)\) 表示 \(k\) 次操作后当前为 \(1\) 的集合是 \(S\),对应概率为 \(\sum_{S,k}\prod_{i\in S}(1-q_i^k)\prod_{i\not\in S}q_i^k\),其中 \(q_i=1-p_i\)

展开得到 \(\sum_{S}\sum_{T\subseteq S}(-1)^{|T|}\sum_k\prod_{i\in T\cup\overline{S}}q_i^k=\sum_{T\subseteq S}(-1)^T\dfrac{1}{1-\prod_{i\in{T\cup\overline S}}q_i}\)

求出 \(p_s=\dfrac{1}{1-\prod_{i\in s}q_i}\) 之后 FWT 可以得到每个 \(S\) 的贡献 \(f_S\)

然后我们要对每个可能的答案串 \(w\),计算所有 \(S\) 使得 \(S\cap w\) 唯一的 \(\sum f_S\),注意到这样的 \((w,S\cap w)\) 最多 \(\mathcal O(3^n)\) 对,因此 dfs 枚举 \(w\),对于每种 \(S\cap w\) 动态维护对应 \(w\) 是否唯一。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1<<15|5,MOD=998244353;
void sub(int &x,const int &y) { x=(x>=y)?x-y:x+MOD-y; }
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b>>=1) if(b&1) s=1ll*s*a%MOD; return s; }
int n,m,a[MAXN],b[16][MAXN],f[MAXN],g[MAXN];
void dfs(int s,int p,int e) {
	int *c=b[e];
	for(int t=s;;t=(t-1)&s) {
		if(c[t]>0) sub(g[c[t]],f[s]);
		if(!t) break;
	}
	if(!s) return ;
	int *d=b[e+1];
	for(int i=p;i<n;++i) if(s>>i&1) {
		for(int t=s;;t=(t-1)&s) {
			if(t>>i&1) d[t^(1<<i)]=c[t];
			else d[t]=(d[t]&&c[t])?-1:(d[t]|c[t]);
			if(!t) break;
		}
		dfs(s^(1<<i),i+1,e+1);
	}
}
void solve() {
	cin>>n>>m,memset(b,0,sizeof(b));
	for(int s=0;s<(1<<n);++s) f[s]=1;
	for(int i=0,w=ksm(10000),x;i<n;++i) {
		cin>>x,f[1<<i]=(1+1ll*(MOD-w)*x)%MOD;
	}
	for(int i=1;i<(1<<n);i<<=1) for(int s=0;s<(1<<n);++s) if(s&i) f[s]=1ll*f[s]*f[s^i]%MOD;
	for(int s=0;s<(1<<n);++s) f[s]=ksm(1+MOD-f[s]);
	for(int i=1;i<(1<<n);i<<=1) for(int s=0;s<(1<<n);++s) if(s&i) {
		swap(f[s],f[s^i]),sub(f[s],f[s^i]);
	}
	int w=0;
	for(int s=0;s<(1<<n);++s) w=(w+f[s])%MOD;
	for(int i=1;i<=m;++i) {
		string o; cin>>o,a[i]=0,g[i]=w;
		for(int j=0;j<n;++j) a[i]|=(o[j]-'0')<<j;
		b[0][a[i]]=i;
	}
	dfs((1<<n)-1,0,0);
	for(int i=1;i<=m;++i) cout<<g[i]<<"\n";
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _; cin>>_;
	while(_--) solve();
	return 0;
}



O. [QOJ6103] A+B Problem (3)

Problem Link

题意就是构造 Halin 图的树分解,对每个点 \(u\) 维护子树中编号最小的叶子 \(l_u\),以及编号最大的叶子的后继 \(r_u\)

每个 \(u\) 建立节点 \((u,fa_u,l_u,r_u)\),和儿子 \(v\) 合并时建立 \((u,l_u,r_u,r_v)\) 中转。

可以证明点数 \(\le 2n\)

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,fa[MAXN*4],l[MAXN],r[MAXN],id[MAXN],nx[MAXN];
vector <int> G[MAXN],S[MAXN*4];
void dfs(int u,int fz) {
	if(G[u].empty()) {
		id[u]=++m,S[m]={u,fz,nx[u]},l[u]=u,r[u]=nx[u];
		return ;
	}
	for(int v:G[u]) {
		dfs(v,u);
		if(!id[u]) { id[u]=id[v],l[u]=l[v],r[u]=r[v]; continue; }
		S[++m]={u,l[u],r[u],r[v]};
		fa[id[u]]=fa[id[v]]=m,r[u]=r[v],id[u]=m;
	}
	if(fz) S[++m]={u,fz,l[u],r[u]},fa[id[u]]=m,id[u]=m;
}
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);
	vector <int> h;
	for(int i=1;i<=n;++i) if(G[i].empty()) h.push_back(i);
	h.push_back(h[0]);
	for(int i=0;i+1<(int)h.size();++i) nx[h[i]]=h[i+1];
	dfs(1,0);
	cout<<m<<"\n";
	for(int i=1;i<=m;++i) {
		sort(S[i].begin(),S[i].end());
		S[i].erase(unique(S[i].begin(),S[i].end()),S[i].end());
		cout<<S[i].size();
		for(int x:S[i]) cout<<" "<<x;
		cout<<"\n";
	}
	for(int i=1;i<=m;++i) if(fa[i]) cout<<fa[i]<<" "<<i<<"\n";
	return 0;
}



*P. [QOJ8005] Crossing the Border (7)

Problem Link

直接对 \(2^n\) 种状态做子集 Exp 难以接受,考虑折半。

\(c\) 降序排序,把物品集合拆成前一半和后一半两部分,状态为 \(f_{s,t}\)

转移的集合也分成 \(x,y\) 两半,要求 \(w_x+w_y\le W\),这是经典的双指针形式。

注意到集合 \((x,y)\) 的信息只和 \(x\)\(\max c\) 有关,因此可以转移 \(f_{s,t-y}\to f_{s+x,t}\),在枚举 \(x\) 的同时维护合法的 \(y\) 对应 \(f_{s,t-y}\) 之和。

提前给每个 \(s,t\) 的所有子集排序即可,记得预处理 \(x=\varnothing\) 的集合贡献。

时间复杂度 \(\mathcal O(n3^{n/2}+2^{n/2}\times 3^{n/2})\)

代码:

#include<bits/stdc++.h>
#define lb __builtin_ctz
using namespace std;
const int inf=1.1e9+7,MOD=998244353;
struct info {
	int v,f;
	info(int V=inf,int F=0): v(V),f(F) {}
	inline friend info operator *(const info &u,const info &v) {
		return info(u.v+v.v,1ll*u.f*v.f%MOD);
	}
	inline friend info operator +(const info &u,const info &v) {
		if(u.v!=v.v) return u.v<v.v?u:v;
		return {u.v,(u.f+v.f)%MOD};
	}
}	f[1<<11][1<<11];
struct item { int w,c; } a[22];
int n,m,l,r,sl[1<<11],sr[1<<11];
basic_string <array<int,2>> br[1<<11];
array<int,2> bl[1<<11];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m,l=n/2,r=n-n/2;
	for(int i=0;i<n;++i) cin>>a[i].w>>a[i].c;
	sort(a,a+n,[&](auto x,auto y){ return x.c>y.c; });
	for(int i=0;i<(1<<l);++i) for(int j=0;j<l;++j) if(i>>j&1) sl[i]+=a[j].w;
	for(int i=0;i<(1<<r);++i) for(int j=0;j<r;++j) if(i>>j&1) sr[i]+=a[j+l].w;
	info *g=f[0]; g[0]={0,1};
	for(int s=1;s<(1<<r);++s) for(int t=s,x=lb(s);t;t=(t-1)&s) if((t>>x&1)&&sr[t]<=m) {
		g[s]=g[s]+g[s^t]*info(a[x+l].c,1);
	}
	for(int s=0;s<(1<<r);++s) {
		for(int t=s;;t=(t-1)&s) { br[s].push_back({sr[t],t}); if(!t) break; }
		sort(br[s].begin(),br[s].end(),greater<>());
	}
	for(int s=1;s<(1<<l);++s) {
		int q=0,d=lb(s);
		for(int t=s;t;t=(t-1)&s) if(t>>d&1) bl[q++]={sl[t],t};
		sort(bl,bl+q);
		for(int t=0;t<(1<<r);++t) {
			int y=0; info w;
			for(auto x:br[(1<<r)-1-t]) {
				for(;y<q&&x[0]+bl[y][0]<=m;++y) w=w+f[s-bl[y][1]][t]*info(a[d].c,1);
				f[s][t+x[1]]=f[s][t+x[1]]+w;
			}
		}
	}
	auto z=f[(1<<l)-1][(1<<r)-1];
	cout<<z.v<<" "<<z.f<<"\n";
	return 0;
}



Q. [QOJ7563] Fun on Tree (2)

Problem Link

\(f_u\) 表示子树内最小的 \(dep_x-a_x\)\(g_u\) 表示 \(T_u\setminus T_{hson(u)}\) 中最小的 \(dep_x-a_x-2dep_u\),修改就是子树加,然后更新轻祖先的 \(g\)

查询时维护链上的 \(\min g_u\) 就得到了答案。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
const ll inf=1e18;
typedef array<ll,2> pii;
int n,m;
struct Segt1 {
	pii tr[1<<19]; ll tg[1<<19];
	void adt(int p,ll k) { tr[p][0]+=k,tg[p]+=k; }
	void psu(int p) { tr[p]=max(tr[p<<1],tr[p<<1|1]); }
	void psd(int p) { if(tg[p]) adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
	void add(int ul,int ur,ll k,int l=1,int r=n,int p=1) {
		if(ul<=l&&r<=ur) return adt(p,k);
		int mid=(l+r)>>1; 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 upd(int u,pii z,int l=1,int r=n,int p=1) {
		if(l==r) return tr[p]=z,void();
		int mid=(l+r)>>1; psd(p);
		u<=mid?upd(u,z,l,mid,p<<1):upd(u,z,mid+1,r,p<<1|1);
		psu(p);
	}
	pii qry(int ul,int ur,int l=1,int r=n,int p=1) {
		if(ul>ur) return {-inf,-inf};
		if(ul<=l&&r<=ur) return tr[p];
		int mid=(l+r)>>1; pii s={-inf,-inf}; psd(p);
		if(ul<=mid) s=max(s,qry(ul,ur,l,mid,p<<1));
		if(mid<ur) s=max(s,qry(ul,ur,mid+1,r,p<<1|1));
		return s;
	}
}	T,Q;
struct Edge { int v,w; };
vector <Edge> G[MAXN];
ll a[MAXN],d[MAXN];
int siz[MAXN],hson[MAXN],fa[MAXN],top[MAXN],dfn[MAXN],dcnt,efn[MAXN];
void dfs1(int u,int fz) {
	fa[u]=fz,siz[u]=1;
	for(auto e:G[u]) {
		d[e.v]=d[u]+e.w,dfs1(e.v,u),siz[u]+=siz[e.v];
		if(siz[e.v]>siz[hson[u]]) hson[u]=e.v;
	}
}
void upd(int u) {
	pii x=T.qry(dfn[u],dfn[u]);
	if(hson[u]) x=max(x,T.qry(efn[hson[u]]+1,efn[u]));
	Q.upd(dfn[u],{x[0]-2*d[u],x[1]});
}
void dfs2(int u,int h) {
	top[u]=h,dfn[u]=++dcnt,T.upd(dfn[u],{d[u]-a[u],-u});
	if(hson[u]) dfs2(hson[u],h);
	for(auto e:G[u]) if(e.v!=hson[u]) dfs2(e.v,e.v);
	efn[u]=dcnt,upd(u);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=2,x,y;i<=n;++i) cin>>x>>y,G[x].push_back({i,y});
	dfs1(1,0),dfs2(1,1);
	for(int x,y,z;m--;) {
		cin>>x>>y>>z;
		T.add(dfn[y],efn[y],-z),Q.add(dfn[y],efn[y],-z);
		for(int u=y;u;u=fa[top[u]]) upd(u);
		pii s=T.qry(dfn[x],efn[x]); s[0]-=2*d[x];
		for(int u=x;u;u=fa[u]) {
			s=max(s,Q.qry(dfn[top[u]],dfn[u]-1)),u=top[u];
			if(u>1) {
				pii o=max(T.qry(dfn[fa[u]],dfn[u]-1),T.qry(efn[u]+1,efn[fa[u]]));
				s=max(s,{o[0]-2*d[fa[u]],o[1]});
			}
		}
		cout<<-s[1]<<" "<<s[0]+d[x]<<"\n";
	}
	return 0;
}



R. [QOJ10174] Lost Table (2)

Problem Link

容斥相当于选一些 \(a_i,b_i\) 减一,然后维护 \(\prod_{i,j}\min(a_i,b_j)\),从小到大枚举每个值 \(v\),容易算出有多少 \(\min(a_i,b_j)=v\)

考虑给一些 \(a_i,b_i\) 减一不影响 \(v\) 之间的大小关系,对每个 \(v\) 内部选一些 \(a_i,b_j\) 减一,然后先算 \(v-1\) 的贡献再算 \(v\) 的贡献。

简单处理可以把 \(b_j\) 个数的枚举用二项式定理优化掉。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7;
ll fac[MAXN],ifac[MAXN];
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 C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	int n,m; cin>>n>>m;
	map <int,array<int,2>> a;
	for(int i=1,x;i<=n;++i) cin>>x,++a[x][0];
	for(int i=1,x;i<=m;++i) cin>>x,++a[x][1];
	ll z=1,s=n,t=m;
	for(auto it:a) {
		int v=it.first,x=it.second[0],y=it.second[1];
		ll w=0;
		for(int i=0;i<=x;++i) {
			ll b=(MOD-ksm(v-1,s-i))*ksm(v,MOD-1+i-s)%MOD;
			w=(w+(i&1?-1:1)*ksm(b+1,y)*C(x,i)%MOD*ksm(v-1,i*t)%MOD*ksm(v,(x-i)*t+y*(s-x)))%MOD;
		}
		z=z*(w+MOD)%MOD,s-=x,t-=y;
	}
	cout<<z<<"\n";
	return 0;
}



*S. [QOJ6653] 阴阳阵法 (7)

Problem Link

考虑 \(f_{x,y}\) 表示 \(x\) 个白点 \(y\) 个黑点的方案数,初始 \(f_{x,0}=(n+m)^x\),初始令 \(f_{x,y}=f_{x,y-1}\times n\),然后容斥掉加入最后一个黑点时产生环的方案数。

具体来说枚举环上黑白点个数 \(i,j\),然后组合数计算方案,复杂度 \(\mathcal O(n^4)\)

考虑优化,设 \(g_{x,y,0/1,0/1,0/1}\) 表示容斥过程中(算上环外节点)有 \(x\) 个白点 \(y\) 个黑点,两种点奇偶性为 \(0/1\),当前环为颜色为黑或白的方案数,每次加入一个环末节点即可。

容斥就是 \(f_{x,y}\) 减掉 \(g_{x,y-1,1,0,0}\),即插入最后一个黑点后成环的情况,注意我们要钦定 \(g\) 中计算的环首是白点。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int MOD,n,m,f[MAXN][MAXN],g[MAXN][MAXN][2][2][2];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>MOD;
	for(int i=0;i<=n;++i) {
		f[i][0]=i?1ll*f[i-1][0]*(n+m)%MOD:1;
		for(int j=1;j<=m;++j) {
			f[i][j]=(1ll*n*f[i][j-1]+MOD-g[i][j-1][1][0][0])%MOD;
		}
		for(int j=0;j<=m;++j) { 
			g[i+1][j][1][0][0]=(g[i+1][j][1][0][0]+1ll*(i+1)*f[i][j])%MOD;
			for(int x:{0,1}) for(int y:{0,1}) {
				g[i+1][j][x^1][y][0]=(g[i+1][j][x^1][y][0]+1ll*(i+1)*(g[i][j][x][y][0]+g[i][j][x][y][1]))%MOD;
				g[i][j+1][x][y^1][1]=(g[i][j+1][x][y^1][1]+1ll*(j+1)*g[i][j][x][y][0])%MOD;
			}
		}
	}
	cout<<f[n][m]<<"\n";
	return 0;
}



T. [QOJ6101] Ring Road (3.5)

Problem Link

Halin 图上最短路,建立树分解后点分治,对重心上的每个节点跑一遍 Dijkstra。

时间复杂度 \(\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,q,l[MAXN],r[MAXN],id[MAXN],nx[MAXN];
struct Edge { int v; ll w; };
vector <Edge> G[MAXN];
vector <int> S[MAXN],E[MAXN];
void link(int x,int y) { E[x].push_back(y),E[y].push_back(x); }
void dfs1(int u,int fz) {
	if(G[u].size()==1&&fz) {
		id[u]=++m,S[m]={u,fz,nx[u]},l[u]=u,r[u]=nx[u];
		return ;
	}
	for(auto e:G[u]) if(e.v^fz) {
		int v=e.v; dfs1(v,u);
		if(!id[u]) { id[u]=id[v],l[u]=l[v],r[u]=r[v]; continue; }
		S[++m]={u,l[u],r[u],r[v]};
		link(m,id[u]),link(m,id[v]),r[u]=r[v],id[u]=m;
	}
	if(fz) S[++m]={u,fz,l[u],r[u]},link(m,id[u]),id[u]=m;
}
bool vis[MAXN],inq[MAXN],tmp[MAXN];
int fa[MAXN],b[MAXN],siz[MAXN],cur[MAXN],st[MAXN],tp,ed[MAXN];
ll dis[100][MAXN];
void solve(int u) {
	function<void(int,int)> dfs4=[&](int x,int fz) {
		siz[x]=1;
		for(int o:S[x]) if(!tmp[o]&&!inq[o]) tmp[o]=true,st[++tp]=o;
		for(int y:E[x]) if(!vis[y]&&y!=fz) dfs4(y,x),siz[x]+=siz[y];
	};
	dfs4(u,0); int o=b[u]*4;
	for(int s:S[u]) if(tmp[s]) {
		ll *d=dis[o++];
		priority_queue<array<ll,2>,vector<array<ll,2>>,greater<>> Q;
		Q.push({d[s]=0,s});
		while(Q.size()) {
			ll z=Q.top()[0],x=Q.top()[1]; Q.pop();
			if(z!=d[x]) continue;
			vector <Edge> h;
			for(auto e:G[x]) if(tmp[e.v]) {
				h.push_back(e);
				if(d[e.v]>d[x]+e.w) Q.push({d[e.v]=d[x]+e.w,e.v});
			}
			G[x].swap(h);
		}
		tmp[s]=false,inq[s]=true,ed[s]=u;
	}
	while(tp) tmp[st[tp--]]=0;
}
void dfs2(int u) {
	solve(u),vis[u]=true;
	for(int v:E[u]) if(!vis[v]) {
		int rt=0;
		function<void(int,int)> dfs3=[&](int x,int fz) {
			cur[x]=siz[v]-siz[x];
			for(int y:E[x]) if(y!=fz&&!vis[y]) dfs3(y,x),cur[x]=max(cur[x],siz[y]);
			if(!rt||cur[rt]>cur[x]) rt=x;
		};
		dfs3(v,u),b[rt]=b[u]+1,fa[rt]=u,dfs2(rt);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n; ll z;
	for(int i=2,x;i<=n;++i) cin>>x>>z,G[x].push_back({i,z}),G[i].push_back({x,z});
	vector <int> h;
	for(int i=2;i<=n;++i) if(G[i].size()==1) h.push_back(i);
	h.push_back(h[0]);
	for(int i=0;i+1<(int)h.size();++i) nx[h[i]]=h[i+1];
	dfs1(1,0);
	for(int i=0;i+1<(int)h.size();++i) cin>>z,G[h[i]].push_back({h[i+1],z}),G[h[i+1]].push_back({h[i],z});
	for(int i=1;i<=m;++i) {
		sort(S[i].begin(),S[i].end());
		S[i].erase(unique(S[i].begin(),S[i].end()),S[i].end());
	}
	memset(dis,0x3f,sizeof(dis)),dfs2(1);
	cin>>q;
	for(int u,v,x,y;q--;) {
		cin>>u>>v,z=1e18;
		for(x=ed[u],y=ed[v];x^y;b[x]<b[y]?y=fa[y]:x=fa[x]);
		for(int i=b[x]*4+3;~i;--i) z=min(z,dis[i][u]+dis[i][v]);
		cout<<z<<"\n";
	}
	return 0;
}



U. [QOJ9699] Loving You in My Humble Way (6.5)

Problem Link

正宗 Ad-Hoc。

直接把一条边看成 \((x,y),(y,z),(z,x)\) 三条边,限制变成没有四元环。

考虑空间中的直线,如果两条直线垂直就连边,可以证明图上没有四元环。

那么取质数 \(p\),在 \(\bmod p\) 意义下取出所有不平行直线,垂直就连边,可以证明不存在四元环,可以通过把其中两条直线变成 \((1,0,0),(0,1,0)\) 证明。

此时点数 \(p^2+p+1\),取 \(p=43\),直接数图中的三元环个数发现恰好合法。

时间复杂度 \(\mathcal O\left(\dfrac{n^3}\omega\right)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int n=43,M=1893;
array<int,3> a[M];
bitset <M> f[M],g;
signed main() {
	int m=0; a[m++]={0,0,1};
	for(int i=0;i<n;++i) a[m++]={0,1,i};
	for(int i=0;i<n;++i) for(int j=0;j<n;++j) a[m++]={1,i,j};
	cout<<n*(n-1)*(n+1)/6<<"\n";
	for(int i=0;i<m;++i) for(int j=i+1;j<m;++j) if((a[i][0]*a[j][0]+a[i][1]*a[j][1]+a[i][2]*a[j][2])%n==0) f[i].set(j);
	for(int i=0;i<m;++i) for(int j=i+1;j<m;++j) if(f[i][j]) {
		g=f[i],g&=f[j];
		for(int k=g._Find_first();k<M;k=g._Find_next(k)) cout<<i+1<<" "<<j+1<<" "<<k+1<<"\n";
	}
	return 0;
}



V. [QOJ10167] Random Interactive MST Bot (4.5)

Problem Link

考虑稠密图上表现最优秀的 Prim 算法,注意到我们只要维护每个 \(u\) 到当前点集的最小值 \(d_u\),那么取出每次更新 \(d_u\) 的边权,\(d_u\) 的实际变化次数就是其中前缀最小值的期望个数,显然是 \(\mathcal O(\log n)\) 级别的。

用 zkw 线段树维护所有 \(d_u\),增加时自下而上更新区间 \(\min d_u\),如果更新不了可以直接返回。

加上朴素记忆化即可通过,交互次数在 \(5900\) 次左右。

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

代码:

#include<bits/stdc++.h> 
using namespace std;
typedef array<int,2> pii;
const int N=128,M=5005;
int L(pii x) { return min(x[0],x[1]); }
int R(pii x) { return max(x[0],x[1]); }
int n,m,id[N][N];
bitset <M> g[M],w[M];
bool ask(pii x,pii y) {
	if(!x[0]||!y[0]) return x[0];
	int u=id[L(x)][R(x)],v=id[L(y)][R(y)];
	if(w[u][v]) return g[u][v];
	cout<<"? "<<L(x)<<" "<<R(x)<<" "<<L(y)<<" "<<R(y)<<endl;
	int o; cin>>o;
	w[u][v]=w[v][u]=1,g[u][v]=o,g[v][u]=o^1;
	return o;
}
bool vis[N];
pii d[N],f[N<<1];
void upd(int x,pii z) {
	for(x+=N;x&&ask(z,f[x]);x>>=1) f[x]=z;
}
void del(int x) {
	for(f[x+=N]={0,0};x>1;x>>=1) f[x>>1]=(ask(f[x],f[x^1])?f[x]:f[x^1]);
}
signed main() {
	cin>>n;
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) id[i][j]=++m;
	vis[1]=true;
	for(int i=2;i<=n;++i) upd(i,d[i]={1,i});
	vector <pii> z;
	for(int t=1;t<n;++t) {
		int u=f[1][1];
		z.push_back({f[1][0],u});
		vis[u]=true,del(u);
		for(int i=1;i<=n;++i) if(!vis[i]) upd(i,{u,i});
	}
	cout<<"!"; for(auto t:z) cout<<" "<<L(t)<<" "<<R(t); cout<<endl;
	return 0;
}



*W. [QOJ8012] Jumping Lights (8)

Problem Link

考虑同时维护当前树以及进行二操作后的树。

对于一个翻转操作我们将该点记录下来,在进行二操作前更新这些点的贡献。

具体来说对每个点分成父亲、叶子儿子、非叶子儿子,父亲节点可以在翻转时记录,叶子儿子直接对每个点记录个数并维护覆盖标记,非叶子儿子用哈希表维护未标记的部分并暴力更新。

可以通过势能分析证明复杂度是线性的。

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

代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
const int MAXN=3e5+5;
vector <int> G[MAXN];
int n,q,fa[MAXN],nc[MAXN],lc[MAXN];
struct ds {
	bool a[MAXN],b[MAXN];
	basic_string <int> Q;
	gp_hash_table <int,int> E[MAXN];
	int sc[MAXN],vs[MAXN],vc,z;
	bool qcol(int u) { return vs[fa[u]]>vs[u]?b[fa[u]]:a[u]; }
	bool ncol(int u) { return a[fa[u]]||sc[u]||(int)E[u].size()<nc[u]; }
	void updL(int u,int c) {//leaf
		vs[u]=++vc,a[u]=c,z+=2*c-1,sc[fa[u]]+=2*c-1;
	}
	void updV(int u,int c) { //no leaf
		a[u]=c,z+=2*c-1;
		if(fa[u]) {
			if(c) E[fa[u]].erase(u);
			else E[fa[u]].insert({u,0});
		}
	}
	void updN(int u,int c) { //neighbour
		vs[u]=++vc,b[u]=c,z-=sc[u],sc[u]=c*lc[u],z+=sc[u];
	}
}	T[2];
void updL(int o,int u,int c) {
	if(T[o].qcol(u)==c) return ;
	T[0].Q+=fa[u],T[1].Q+=fa[u],T[o].updL(u,c);
}
void updV(int o,int u,int c) {
	if(T[o].a[u]==c) return ;
	T[0].Q+=u,T[1].Q+=u;
	if(fa[u]) T[o].Q+=fa[u];
	T[o].updV(u,c);
}
int vis[MAXN],vc=0;
void solve(int o) {
	basic_string<int>Q; Q.swap(T[o].Q),++vc;
	for(int u:Q) if(vis[u]<vc) {
		int x=T[o].a[u],y=T[o].ncol(u);
		updV(o^1,u,y),T[o^1].updN(u,x),vis[u]=vc;
		if(x) {
			gp_hash_table<int,int>E; E.swap(T[o^1].E[u]);
			if(fa[u]) updV(o^1,fa[u],1);
			for(auto i:E) updV(o^1,i.first,1);
		}
	}
}
bool il[MAXN];
void dfs(int u,int fz) {
	il[u]=(fz&&G[u].size()==1),fa[u]=fz;
	for(int v:G[u]) if(v^fz) {
		dfs(v,u),lc[u]+=il[v],nc[u]+=!il[v];
		if(!il[v]) for(int o:{0,1}) T[o].E[u].insert({v,0});
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
	dfs(1,0);
	for(int o=0,c,u;q--;) {
		cin>>c;
		if(c==2) solve(o),o^=1;
		else cin>>u,(il[u]?updL(o,u,c):updV(o,u,c));
		cout<<T[o].z<<" \n"[!q];
	}
	return 0;
}



*X. [QOJ6651] 喵了个喵 III (9)

Problem Link

考虑最后一对删除的元素 \(x\),那么序列被分成 \(AxBxC\)

那么操作过程一定形如 \([A][x]\to[][x,B]\to [x,C][x,B]\to [x][x]\),注意到 \(A\) 中未消除的元素必须在同一个栈中,因此 \(A\) 中剩余的元素只有恰好出现过一次的元素,并且按顺序排列之。

考虑 \(A,B,C\) 三部分过程分开,可以定义状态 \(f_{l,r}\) 表示 \(a[1,l-1]\) 中出现过一次的元素放在其中一个栈,另一个栈为空,能否操作 \(a[l,r]\) 使得最终 \(a[1,r]\) 中出现过一次的元素放入另一个栈。

考虑 \(f\) 的转移,我们还需要 \(g_{l,r}\) 表示最终把 \(a[1,r]\) 中出现过一次的元素放入同一个栈。

以及 \(h_{l,r}\) 表示 \([1,r]\) 内部元素两两匹配时能否完全消除。

分讨一下转移:

  • \(f\gets g+f\):记 \(x\)\([l,r]\) 中第一个匹配 \([r+1,n]\) 的元素,序列为 \(A+BxC\),那么过程为 \([A][]\to [A,B][x]\to [][x,C]\)

注意这里 \(g\) 操作要求 \(AB\) 中能匹配的点是 \(A\) 的一段栈顶,因此不能有 \(C\to i,B\to j\) 满足 \(j<i,i,j\in A\),否则 \(j\) 消不掉。

  • \(g\gets f+f\):同上定义 \(x,A,B,C\),过程为 \([A][]\to [A,x][B]\to [A,x,C][]\)

    这里要求 \(A,C\) 无边。

  • \(h\gets f+f+h\):枚举 \([l,r]\) 最后一对消除元素 \(x\),设序列为 \(A+BxCxD\),那么过程为:\([A][]\to [A,x][B]\to [A,x,C][B,x]\to [A,x][B,x]\)

    要求 \((A,C),(A,D),(B,D)\) 无边。

  • \(h\gets g+f+h\):同上定义 \(x,A,B,C,D\),过程为:\([A][]\to [A,B][x]\to [A,B,x][x,C]\to [A,B,x][x]\)

    要求 \((A,D),(B,D)\) 无边,并且类似情况一,\(C,D\) 中元素不占用 \(A\) 的栈顶。

  • \(h\gets g+h\):这种情况下依然枚举 \(x\),但与 \(x\) 匹配的元素 \(\in a[1,l-1]\),设序列为 \(A+BxC\),过程为:\([A][]\to [AB][x]\to [AB][xC]\to [A'x][x]\)

    这种情况下依然要求 \(C\) 不占用 \(A\) 的栈顶,且任何非 \(x\) 元素不能 \(\in A'\),直接取 \([l,r]\)\(p\) 的最小值为 \(x\)

注意到 \(h_{l,r}\) 本质是 \([l,r]\) 中没有向 \(>r\) 匹配元素时 \(f_{l,r}=g_{l,r}=h_{l,r}\),按照上述过程预处理判定条件然后暴力 dp,记录决策点然后模拟上述策略构造。

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1505;
int n,a[MAXN],t[MAXN],p[MAXN],ls[MAXN][MAXN],rp[MAXN][MAXN],mn[MAXN][MAXN],mx[MAXN][MAXN],cv[MAXN][MAXN];
int h[MAXN][MAXN],b[MAXN];
bool f[MAXN][MAXN],g[MAXN][MAXN];
void dp(int l,int r,bool o,bool c) {
	if(l>r) return ;
	if(ls[l][r]<=r) {
		int i=ls[l][r];
		if(o) b[i]=c,dp(l,i-1,0,c),dp(i+1,r,0,c^1);
		else b[i]=c^1,dp(l,i-1,1,c),dp(i+1,r,0,c);
		return ;
	}
	int i=h[l][r];
	if(i<=n) {
		dp(l,i-1,0,c),b[i]=c,dp(i+1,p[i]-1,0,c^1),b[p[i]]=c^1,dp(p[i]+1,r,0,c);
	} else if(i-n<=n) {
		i-=n,dp(l,i-1,1,c),b[i]=c^1,dp(i+1,p[i]-1,0,c),b[p[i]]=c,dp(p[i]+1,r,0,c^1);
	} else {
		i-=2*n,dp(l,i-1,1,c),b[i]=c^1,dp(i+1,r,0,c);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) {
		cin>>a[i];
		if(t[a[i]]) p[i]=t[a[i]],p[p[i]]=i;
		else t[a[i]]=i;
	}
	for(int i=0;i<=n;++i) f[i+1][i]=g[i+1][i]=true;
	memset(ls,0x3f,sizeof(ls));
	memset(mn,0x3f,sizeof(mn));
	memset(cv,0x3f,sizeof(cv));
	for(int l=1;l<=n;++l) {
		for(int r=l;r<=n;++r) {
			rp[l][r]=p[r]<l?r:rp[l][r-1];
			mn[l][r]=min(mn[l][r-1],p[r]);
			mx[l][r]=max(mx[l][r-1],p[r]);
		}
		memset(t,0x3f,sizeof(t));
		for(int r=n;r>=l;--r) {
			for(int x=mn[l][r];x<=n;x+=x&-x) cv[l][r]=min(cv[l][r],t[x]);
			if(p[r]<l) for(int x=p[r];x;x&=x-1) t[x]=r;
		}
	}
	for(int r=1;r<=n;++r) for(int l=r;l>=1;--l) {
		ls[l][r]=p[l]>r?l:ls[l+1][r];
	}
	for(int l=n;l;--l) for(int r=l;r<=n;++r) {
		if(ls[l][r]<=r) {
			int i=ls[l][r];
			if(rp[l][r]<i&&f[l][i-1]&&f[i+1][r]) g[l][r]=true;
			if(r<cv[l][i-1]&&g[l][i-1]&&f[i+1][r]) f[l][r]=true;
			continue;
		}
		for(int i=l;i<=r;++i) if(i<p[i]) {
			if(rp[l][r]<i&&mx[l][i-1]<=p[i]&&f[l][i-1]&&f[i+1][p[i]-1]&&f[p[i]+1][r]) f[l][r]=true,h[l][r]=i;
			if(mn[p[i]+1][r]>=i&&cv[l][i-1]>r&&g[l][i-1]&&f[i+1][p[i]-1]&&f[p[i]+1][r]) f[l][r]=true,h[l][r]=n+i;
			if(f[l][r]) goto sc;
		}
		if(mn[l][r]<l) {
			int i=p[mn[l][r]];
			if(cv[l][i-1]>r&&g[l][i-1]&&f[i+1][r]) f[l][r]=true,h[l][r]=2*n+i;
		}
		sc:g[l][r]=f[l][r];
	}
	if(!f[1][n]) return cout<<"No solution.\n",0;
	dp(1,n,0,0);
	cout<<"Cleared.\n"<<n/2*3<<"\n";
	vector <int> c[2];
	for(int i=1;i<=n;++i) {
		cout<<b[i]+1,c[b[i]].push_back(a[i]);
		for(;c[0].size()&&c[1].size()&&c[0].back()==c[1].back();c[0].pop_back(),c[1].pop_back()) cout<<0;
	}
	cout<<"\n";
	return 0;
}



Y. [QOJ7994] 勿蹖宠物 (3)

Problem Link

考虑从中间开始向两侧填字符串来维护回文串,每次转移长度较小的一侧,只需 dp 记录长度较长一侧剩余的字符串是哪个前缀或者哪个后缀。

预处理每个状态加上一个字符串后的转移即可。

时间复杂度 \(\mathcal O(nS^2+nmS)\),其中 \(S=\sum|s_i|\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int n,m,q,d[355],f[1005][1205],L[335][605],R[335][605],g[1205][355];
string s[355];
void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) {
		cin>>s[i],d[i]=s[i].size();
		for(int j=0;j<d[i];++j) L[i][j]=++q,R[i][j]=++q;
	}
	for(int i=1;i<=n;++i) g[0][i]=R[i][0];
	for(int i=1;i<=n;++i) for(int j=0;j<d[i];++j) {
		for(int k=1;k<=n;++k) {
			int &z=g[L[i][j]][k];
			for(int x=0;x<min(j+1,d[k]);++x) if(s[i][j-x]!=s[k][x]) { z=-1; break; }
			if(z<0) continue;
			if(j+1<d[k]) z=R[k][j+1];
			else if(j+1>d[k]) z=L[i][j-d[k]];
			else z=0;
		}
		for(int k=1;k<=n;++k) {
			int &z=g[R[i][j]][k];
			for(int x=0;x<min(d[i]-j,d[k]);++x) if(s[i][j+x]!=s[k][d[k]-1-x]) { z=-1; break; }
			if(z<0) continue;
			if(d[i]-j<d[k]) z=L[k][d[k]-(d[i]-j)-1];
			else if(d[i]-j>d[k]) z=R[i][j+d[k]];
			else z=0;
		}
	}
	if(m%2==0) {
		++f[m][0];
		for(int i=1;i<=n;++i) if(d[i]<=m) for(int j=0;j+1<d[i];++j) {
			for(int x=0;x<min(j+1,d[i]-1-j);++x) if(s[i][j-x]!=s[i][j+1+x]) goto n1;
			if(j+1<d[i]-1-j) ++f[m-d[i]][R[i][2*j+2]];
			else if(j+1>d[i]-1-j) ++f[m-d[i]][L[i][2*j-d[i]+1]];
			else ++f[m-d[i]][0];
			n1:;
		}
	} else {
		for(int i=1;i<=n;++i) if(d[i]<=m) for(int j=0;j<d[i];++j) {
			for(int x=0;x<min(j,d[i]-1-j);++x) if(s[i][j-1-x]!=s[i][j+1+x]) goto n2;
			if(j<d[i]-1-j) ++f[m-d[i]][R[i][2*j+1]];
			else if(j>d[i]-1-j) ++f[m-d[i]][L[i][2*j-d[i]]];
			else ++f[m-d[i]][0];
			n2:;
		}
	}
	for(int i=m;i;--i) for(int j=0;j<=q;++j) if(f[i][j]) {
		for(int k=1;k<=n;++k) if(i>=d[k]&&~g[j][k]) add(f[i-d[k]][g[j][k]],f[i][j]);
	}
	cout<<f[0][0]<<"\n";
	return 0;
}



Z. [QOJ6650] Freshman Dream (4)

Problem Link

解方程 \(\sum a_{i,k}b_{k,j}=a_{i,j}b_{i,j}\),容易发现每组 \(j\) 相同的 \(b\) 构成 \(n\)\(n\) 次方程组。

高斯消元之后暴力枚举每个自由元的取值,然后背包。

由于 \(a\) 随机,因此矩阵秩 \(\le n-k\) 的概率为 \(\mathcal O\left(\dfrac 1{k!}\right)\) 级别。

时间复杂度 \(\mathcal O\left(\dfrac{n^4}\omega\right)\)

代码:

#include<bits/stdc++.h>
using namespace std;
bitset <105> a[105],f[105],o;
vector <bitset<105>> g[105],h;
int dp[105][10005];
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,k; cin>>n>>k;
	for(int i=1,c;i<=n;++i) for(int j=1;j<=n;++j) cin>>c,a[i][j]=c;
	memset(dp,-1,sizeof(dp)),dp[0][0]=0;
	for(int t=1;t<=n;++t) {
		for(int i=1;i<=n;++i) f[i].reset();
		for(int i=1;i<=n;++i) {
			o=a[i],o[i]=o[i]^a[i][t];
			for(int j=1;j<=n;++j) if(o[j]) {
				if(!f[j][j]) { f[j]=o; break; }
				else o^=f[j];
			}
		}
		for(int i=n;i;--i) if(f[i].any()) {
			for(int j=i-1;j;--j) if(f[j][i]) f[j]^=f[i];
		}
		h.clear();
		for(int i=n;i;--i) if(f[i].none()) {
			for(int j=1;j<=n;++j) o[j]=f[j][i];
			o[i]=1,h.push_back(o);
		}
		for(int s=0,c=h.size();s<(1<<c);++s) {
			o.reset();
			for(int x=0;x<c;++x) if(s>>x&1) o^=h[x];
			g[t].push_back(o); int z=o.count();
			for(int i=z;i<=k;++i) if(~dp[t-1][i-z]) dp[t][i]=s;
		}
	}
	if(dp[n][k]<0) return cout<<"-1\n",0;
	for(int i=1;i<=n;++i) a[i].reset();
	for(int t=n;t;--t) {
		int s=dp[t][k];
		for(int i=1;i<=n;++i) a[i][t]=g[t][s][i];
		k-=g[t][s].count();
	}
	cout<<"1\n";
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cout<<a[i][j]<<" \n"[j==n];
	return 0;
}



AA. [QOJ7718] Coconuts (2)

Problem Link

\(f_{a}\) 表示攻击次数为 \([a_1,a_2,\dots,a_n]\) 时的答案,只用关心 \(a_i\) 的无序集,转移的时候计算一下可能的 \(d\) 序列个数即可。

时间复杂度 \(\mathcal O(\mathrm{Bell}(n)\mathrm{poly}(n))\)

代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#define ll long long
using namespace std;
int n,a[12],b[12],c[12],d[12],e[12];
int qry() {
	memset(d,0,sizeof(d)),memset(e,0,sizeof(e));
	for(int i=0;i<n;++i) ++e[a[i]],d[b[i]]+=!c[i];
	int s=1;
	for(int i=0;i<n;++i) if(c[i]) s*=e[b[i]]--;
	if(!s) return 0;
	for(int i=0,k=0;i<10;++i) for(k+=d[i];e[i+1]--;s*=k--);
	return s;
}
__gnu_pbds::gp_hash_table <ll,int> F;
int dfs(int q) {
	int o=qry();
	if(!o||!q) return count(c,c+n,1)*o;
	for(int i=0;i<n;++i) e[i]=b[i]<<1|c[i];
	sort(e,e+n); ll h=0;
	for(int i=0;i<n;++i) h=h*22+e[i];
	if(F.find(h)!=F.end()) return F[h];
	int z=0;
	for(int i=0;i<n;++i) if(!c[i]) {
		++b[i]; int t=dfs(q-1);
		c[i]=1,t+=dfs(q-1),c[i]=0,--b[i],z=max(z,t);
	}
	return F[h]=z;
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int k; cin>>n>>k;
	for(int i=0;i<n;++i) cin>>a[i];
	cout<<fixed<<setprecision(20)<<1.*dfs(k)/qry()<<"\n";
	return 0;
}



AB. [QOJ8007] Egg Drop Challenge (4.5)

Problem Link

考虑 \(i\to j\) 的转移,设速度为 \(v\),则 \(v\le u_j,v^2\le v_j-2(h_i-h_j)\),分讨 \(v\) 的具体取值,可以写成 \(f_i\gets z+\sqrt{x_i-k}\)\(f_i\gets z-\sqrt{k-x_i}\)

注意到这样的函数之间只有一个交点,因此可以用类似李超线段树的方式维护最小值,\(v\) 取值的限制可以 CDQ 分治去掉,注意 \(x\) 超出定义域的部分不能插入该函数。

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

#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
const int MAXN=3e5+5;
const ll inf=4e18;
struct info {
	ld z; ll k; int o;
	inline ld f(ll x) const {
		if(o<0) return inf;
		if(!o) return x>k?inf:z+sqrtl(k-x);
		else return k>x?inf:z-sqrtl(x-k);
	}
}	e[MAXN];
struct Segt {
	int ls[MAXN*20],rs[MAXN*20],tr[MAXN*20],tot,rt;
	vector <ll> Z;
	void ins(int x,int ul,int ur,int l,int r,int &p) {
		if(ul<=l&&r<=ur) {
			if(!p) return tr[p=++tot]=x,void();
			int mid=(l+r)>>1;
			if(e[x].f(Z[mid])<e[tr[p]].f(Z[mid]))swap(tr[p],x);
			if(l==r) return ;
			if(e[x].f(Z[l])<e[tr[p]].f(Z[l])) ins(x,ul,ur,l,mid,ls[p]);
			if(e[x].f(Z[r])<e[tr[p]].f(Z[r])) ins(x,ul,ur,mid+1,r,rs[p]);
			return ;
		}
		if(!p) p=++tot;
		int mid=(l+r)>>1;
		if(ul<=mid) ins(x,ul,ur,l,mid,ls[p]);
		if(mid<ur) ins(x,ul,ur,mid+1,r,rs[p]);
	}
	ld ask(int x,int l,int r,int p) {
		ld z=e[tr[p]].f(Z[x]);
		if(!p||l==r) return z;
		int mid=(l+r)>>1;
		return min(z,x<=mid?ask(x,l,mid,ls[p]):ask(x,mid+1,r,rs[p]));
	}
	void init(vector<ll>&z) {
		for(int i=1;i<=tot;++i) ls[i]=rs[i]=tr[i]=0;
		tot=rt=0,Z.swap(z),sort(Z.begin(),Z.end()),Z.erase(unique(Z.begin(),Z.end()),Z.end());
	}
	void add(int x) {
		if(!e[x].o) {
			int t=upper_bound(Z.begin(),Z.end(),e[x].k)-Z.begin()-1;
			if(t>=0) ins(x,0,t,0,Z.size()-1,rt);
		} else {
			int t=lower_bound(Z.begin(),Z.end(),e[x].k)-Z.begin();
			if(t<(int)Z.size()) ins(x,t,Z.size()-1,0,Z.size()-1,rt);
		}
	}
	ld qry(ll x) {
		return ask(lower_bound(Z.begin(),Z.end(),x)-Z.begin(),0,Z.size()-1,rt);
	}
}	T;
int n,rt;
ll h[MAXN],L[MAXN],R[MAXN];
ld f[MAXN];
array <ll,2> a[MAXN];
void cdq(int l,int r) {
	if(l==r) return void();
	int mid=(l+r)>>1;
	cdq(mid+1,r);
	vector <ll> z1,z2;
	for(int j=l;j<=mid;++j) a[j]={L[j]*L[j]+2*h[j],j},z1.push_back(2*h[j]),z2.push_back(a[j][0]);
	for(int i=mid+1;i<=r;++i) a[i]={R[i]*R[i]+2*h[i],i};
	sort(a+l,a+mid+1),sort(a+mid+1,a+r+1);
	T.init(z1),rt=0;
	for(int j=l,k=mid+1,i;j<=mid;++j) {
		for(;k<=r&&a[k][0]<=a[j][0];++k) i=a[k][1],e[i]={f[i]-R[i],a[k][0],0},T.add(i);
		i=a[j][1],f[i]=min(f[i],T.qry(2*h[i]));
	}
	T.init(z2),rt=0;
	for(int j=mid,k=r,i;j>=l;--j) {
		for(;k>mid&&a[k][0]>a[j][0];--k) i=a[k][1],e[i]={f[i],2*h[i],1},T.add(i);
		i=a[j][1],f[i]=min(f[i],T.qry(a[j][0])+L[i]);
	}
	cdq(l,mid);
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,e[0]={0,0,-1};
	for(int i=1;i<=n;++i) cin>>h[i]>>R[i]>>L[i];
	fill(f+1,f+n,inf),cdq(1,n);
	if(f[1]>=inf) cout<<"-1\n";
	else cout<<fixed<<setprecision(20)<<f[1]<<"\n";
	return 0;
}



AC. [QOJ6656] 先人类的人类选别 (4.5)

Problem Link

考虑一个前缀操作后的变化,如果 \(x\) 小于其中最小值那么无事发生,否则前缀最小值会被弹出。

因此一个前缀 \(a[1,k]\) 操作若干次后剩余的元素就是所有 \(x\) 加上原有元素的前 \(k\) 大。

主席树维护当前所有的 \(x\) 以及每个 \(a[1,k]\),简单二分即可。

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

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
struct Segt {
	ll su[MAXN*40];
	int ct[MAXN*40],ls[MAXN*40],rs[MAXN*40],tot;
	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,int l,int r,int q,int p) {
		if(l==r) return 1ll*k*l;
		int mid=(l+r)>>1,w=ct[rs[q]]+ct[rs[p]];
		if(k<=w) return qry(k,mid+1,r,rs[q],rs[p]);
		return qry(k-w,l,mid,ls[q],ls[p])+su[rs[q]]+su[rs[p]];
	}
}	T;
int n,m,rt[MAXN],o;
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,x;i<=n;++i) cin>>x,T.ins(x,1,n,rt[i-1],rt[i]);
	for(int x,l,r;m--;) {
		cin>>x>>l>>r,T.ins(x,1,n,o,o);
		cout<<T.qry(r,1,n,rt[r],o)-T.qry(l-1,1,n,rt[l-1],o)<<"\n";
	}
	return 0;
}



AD. [QOJ9691] Little, Cyan, Fish! (3.5)

Problem Link

先把操作线段处理成互不相交的,然后按行扫描线,维护每个元素在三种操作中是否被覆盖容易用 bitset 优化,然后我们要统计每 \(\omega\) 位的贡献,拆成 \(4\) 个大小为 \(\dfrac\omega 4\) 的部分预处理即可。

正解是行列同时分块后 FFT。

时间复杂度 \(\mathcal O\left(\dfrac{n^2+nq}{\omega}\right)\)

代码:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5,MAXQ=1675,MOD=998244353,B=60,P=(1<<15)-1;
struct seg { int x,y; };
int n,m,zx[MAXN],zy[MAXN],s[MAXQ*4][P+5];
vector <seg> f[MAXN],g[MAXN],h[MAXN*2],w,ig[MAXN],ih[MAXN];
ull a[MAXQ],b[MAXQ],c[MAXQ],pw[B+5];
void upd(vector<seg>&e) {
	sort(e.begin(),e.end(),[&](auto i,auto j){ return i.x<j.x; }),w.clear();
	for(auto i:e) {
		if(w.empty()||w.back().y<i.x-1) w.push_back(i);
		else w.back().y=max(w.back().y,i.y);
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=B;++i) pw[i]=pw[i-1]<<1|1;
	for(int i=1;i<=n;++i) cin>>zx[i];
	for(int i=1;i<=n;++i) cin>>zy[i];
	for(int lx,ly,rx,ry;m--;) {
		cin>>lx>>ly>>rx>>ry;
		if(lx>rx||(lx==rx&&ly>ry)) swap(lx,rx),swap(ly,ry);
		if(lx==rx) f[lx].push_back({ly,ry});
		else if(ly==ry) g[ly].push_back({lx,rx});
		else h[ly+n-lx].push_back({lx,rx});
	}
	for(int i=1;i<=n;++i) {
		upd(f[i]),f[i].swap(w),upd(g[i]);
		for(auto j:w) ig[j.x].push_back({i,1}),ig[j.y+1].push_back({i,0});
	}
	for(int i=-n;i<=n;++i) {
		upd(h[i+n]);
		for(auto j:w) ih[j.x].push_back({j.x+i,1}),ih[j.y+1].push_back({j.y+1+i,0});
	}
	for(int i=0;i<=n/15;++i) {
		for(int j=0;j<15;++j) s[i][1<<j]=zy[i*15+j];
		for(int j=1;j<=P;++j) s[i][j]=(s[i][j&-j]+s[i][j&(j-1)])%MOD;
	}
	int q=n/B,ans=0; const ull U=pw[B];
	for(int i=1;i<=n;++i) {
		memset(a,0,sizeof(a));
		for(auto j:f[i]) {
			int l=j.x/B,r=j.y/B;
			if(l==r) a[l]^=pw[j.y%B+1]^pw[j.x%B];
			else {
				a[l]^=U^pw[j.x%B],a[r]^=pw[j.y%B+1];
				if(r-l>=2) memset(a+l+1,0xff,(r-l-1)<<3);
			}
		}
		for(auto j:ig[i]) {
			if(j.y) b[j.x/B]|=1ll<<j.x%B;
			else b[j.x/B]&=~(1ll<<j.x%B);
		}
		for(auto j:ih[i]) {
			if(j.y) c[j.x/B]|=1ll<<j.x%B;
			else c[j.x/B]&=~(1ll<<j.x%B);
		}
		ull z=0,d;
		for(int x=0;x<=q;++x) if(d=a[x]&b[x]&c[x]) {
			z+=0ll+s[x<<2][d&P]+s[x<<2|1][d>>15&P]+s[x<<2|2][d>>30&P]+s[x<<2|3][d>>45&P];
		}
		ans=(ans+z%MOD*zx[i])%MOD;
		for(int x=q;~x;--x) c[x+1]|=c[x]>>59&1,c[x]=c[x]<<1&U;
	}
	cout<<ans<<"\n";
	return 0;
}
posted @ 2025-09-28 23:10  DaiRuiChen007  阅读(115)  评论(0)    收藏  举报