2026.9 做题记录

9.2

【本家投稿】 TAK - 'numb numb' feat. Hatsune Miku, Kasane Teto

[APIO2017] 考拉的游戏

solution

subtask1

直接询问 \([1,0,0,0,\dots]\) 即可,这样对方只能放弃一个,放弃的就是最小的。

subtask2

维护可能是最大值的位置,在这些位置放尽可能多的相同的石子,放的数量应不超过 \(12\),其他位置不放,这样对方至少会在一个放有石子的位置放更多石子,因为用 \(13\) 个石子拿走 \(100\) 的价值一定不劣,将可能是最大值的位置与对方放有石子位置取交集,可以证明最大次数为 \(4\) 次。

subtask3

比较两个位置大小,发现在这两个位置放相同数量石子,若对方只在其中一个放更多石子则放更多石子的位置更大,如果两个位置都放了更多石子说明我们需要放更多石子才能判定,放置石子数量显然不能超过 \(12\),枚举检查一下发现 \(1,3,4,7,8\) 这五个数字中必然有一个使得两个位置对方只在其中一个放更多石子,那么第一次用 \(4\) 确定需要更多还是更少石子即可。

subtask4

发现要比较两个位置谁权值更大,只需要在这两个位置各放上 \(100\) 个石子即可,对方只可能在其中一个位置放更多石子,放的位置就是权值更大的位置,那么直接归并排序即可。

subtask5

根据 subtask2 的经验,我们可以在一些位置上全都放上相同的石子数量,对面对应地放上更多石子则说明更多石子的位置权值更大。

考虑分治,我们假设知道了一个值域区间 \([l,r]\) 的所有位置,那么如果能在这些位置都放上 \(d\) 个石子,其他位置不放,对方在其中一些位置放上了更多石子,一些位置没放石子,那么就可以分成两个值域区间分别求解,当 \(l=r\) 时直接确认,递归结束,解决长度为 \(n\) 的值域区间需要次数 \(T(n)=T(L)+T(R)+1=(L-1)+(R-1)+1=n-1\),所以只需要 \(99\) 次即可解决,找合法的 \(d\) 随便找一个看起来比较合理的基本都可以。

Code
//#include "koala.h"
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
void playRound(int*,int*);

int minValue(int _N, int _W) {
	static int A[100],B[100],vis[100];
	f(i,0,99) A[i]=1;
	playRound(A,B);
	f(i,0,99) vis[i]=0;
	f(i,0,99) if (B[i]>1) vis[i]=1;
	int flag=0;
	f(i,0,99) A[i]=0;
	f(i,0,99) if (vis[i]&&!flag) A[i]=flag=1;
	playRound(A,B);
	f(i,0,99) if (!B[i]) return i;
	return -1;
}

int maxValue(int _N, int _W) {
	static int A[100],B[100],vis[100];
	f(i,0,99) vis[i]=1;
	while (1) {
		int sum=0;
		f(i,0,99) sum+=vis[i];
		if (sum==1) {f(i,0,99) if (vis[i]) return i;}
		int h=min(12,_W/sum);
		f(i,0,99) {
			if (vis[i]) A[i]=h;
			else A[i]=0;
		}
		playRound(A,B);
		f(i,0,99) {
			if (B[i]>A[i]&&vis[i]) vis[i]=1;
			else vis[i]=0;
		}
	}
	return -1;
}

int greaterValue(int _N, int _W) {
	static int A[100],B[100],vis[100];
	A[0]=A[1]=4;
	f(i,2,99) A[i]=0;
	playRound(A,B);
	if ((B[0]>A[0])^(B[1]>A[1])) {
		if (B[0]>A[0]) return 0;
		else return 1;
	}
	if (B[0]<A[0]) {
		A[0]=A[1]=1;
		f(i,2,99) A[i]=0;
		playRound(A,B);
		if ((B[0]>A[0])^(B[1]>A[1])) {
			if (B[0]>A[0]) return 0;
			else return 1;
		}
		A[0]=A[1]=3;
		f(i,2,99) A[i]=0;
		playRound(A,B);
		if ((B[0]>A[0])^(B[1]>A[1])) {
			if (B[0]>A[0]) return 0;
			else return 1;
		}
	}
	else {
		A[0]=A[1]=7;
		f(i,2,99) A[i]=0;
		playRound(A,B);
		if ((B[0]>A[0])^(B[1]>A[1])) {
			if (B[0]>A[0]) return 0;
			else return 1;
		}
		A[0]=A[1]=8;
		f(i,2,99) A[i]=0;
		playRound(A,B);
		if ((B[0]>A[0])^(B[1]>A[1])) {
			if (B[0]>A[0]) return 0;
			else return 1;
		}
	}
	return -1;
}

inline bool cmp(int x,int y) {
	static int A[100],B[100];
	f(i,0,99) A[i]=0;
	A[x]=A[y]=100;
	playRound(A,B);
	return B[y]>A[y];
}

inline void solve(int l,int r,vector<int> id,int *ans) {
	if (l==r) return ans[id.back()]=l,void();
	static int A[100],B[100];
	f(i,0,99) A[i]=0;
	int d=1;
	if (l!=1) f(k,1,14) if ((k+1)*k/2>l) {d=k;break;}
	d=min(d,100/(r-l+1));
	for (int x:id) A[x]=d;
	playRound(A,B);
	vector<int> vL,vR;
	for (int x:id) {
		if (B[x]>A[x]) vR.push_back(x);
		else vL.push_back(x);
	}
	solve(l,l+vL.size()-1,vL,ans);
	solve(r-vR.size()+1,r,vR,ans);
}

void allValues(int _N, int _W, int *_P) {
	if (_W==200) {
		static int id[100];
		f(i,0,99) id[i]=i;
		stable_sort(id,id+100,cmp);
		f(i,0,99) _P[id[i]]=i+1;
	}
	else {
		vector<int> st;
		f(i,0,99) st.push_back(i);
		solve(1,100,st,_P);
	}
}

9.3

【本家投稿】分かっちゃいないね(还真是什么都不懂呢)/ monet feat.花隈千冬

代码源模拟赛。

T1: 第一个题

题意

给定 \(n,k\) 并给定递增序列 \(r_1,r_2,\dots,r_k\),令一个排列 \(p\) 的权值 \(f(p)=\prod\limits_{i=1}^{k}\min\limits_{r_{i-1}<j\le r_i} p_j\),即分成 \(k\) 段后每段最小值的乘积,认定 \(r_0=0\),保证 \(r_k=n\),求所有长度为 \(n\) 的排列权值和。

\(1\le k\le n\le 100\)

solution

不是题。这还是 CSP 模拟赛吗。

从小到大填数,我们只关心还未填入任何一个数的段的长度构成的集合,并且对长度相同的段只关心其数量,对这个集合状压,发现 \(n\le 100\) 时状态数最大大概只有 \(2\times 10^5\) 个,直接 dp 维护即可。

要用混合进制进行状压,赛时实现劣了一点没过。

T2: 第二个题

题意

给定一张 \(n\) 个点 \(m\) 条边的无向图,对于所有点,考虑所有 \(1\) 这个点的最短路,求出依次经过的边的编号构成的序列按字典序排序的次小字典序。

保证边权为正,无自环。

\(1\le n\le 2\times 10^5,1\le m\le 5\times 10^5\)

solution

好像可以在最短路图上 dfs 两遍通过。不管了我不会。

先把最短路图建出来,维护每个节点的最小字典序及次小字典序,用可持久化线段树维护哈希值进行快速比较和末尾添加元素,直接更新就行了。

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

Code
#include<cstdio>
#include<algorithm>
#include<queue>
#include<bitset>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int M=5e5+10;
constexpr int P=3e7+10;

constexpr int B1=131,M1=998244853;
constexpr int B2=13331,M2=1e9+97;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m;
int head[N],E_tot;
struct Edge{int next,to,w,id;}e[M<<1];

inline void add(int u,int v,int w,int id) {
	e[++E_tot]={head[u],v,w,id};
	head[u]=E_tot;
}

struct Segment_Tree {
	int tot;ll s[P];
	struct Node{int ls,rs,H1,H2,siz;}tr[P];
	
	#define ls(x) tr[x].ls
	#define rs(x) tr[x].rs
	
	ll fac1[M],fac2[M];
	inline void init_pw(int Len) {
		fac1[0]=fac2[0]=1;
		f(i,1,Len) {
			fac1[i]=fac1[i-1]*B1%M1;
			fac2[i]=fac2[i-1]*B2%M2;
		}
	}
	
	inline void push_up(int x) {
		s[x]=s[ls(x)]+s[rs(x)];
		tr[x].H1=(tr[ls(x)].H1*fac1[tr[ls(x)].siz]+tr[rs(x)].H1)%M1;
		tr[x].H2=(tr[ls(x)].H2*fac2[tr[ls(x)].siz]+tr[rs(x)].H2)%M2;
		tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz;
	}
	
	inline int newNode(int x) {
		tr[++tot]=tr[x];
		s[tot]=s[x];
		return tot;
	}
	
	inline void update(int x,int l,int r,int &now,int k) {
		now=newNode(now);
		if (l==r) {
			tr[now].H1=tr[now].H2=k;
			s[now]=k;tr[now].siz=1;
			return;
		}
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now),k);
		else update(x,mid+1,r,rs(now),k);
		push_up(now);
	}
	
	inline bool chkmin(int x,int y,int l,int r) {
		if (l==r) return s[x]<s[y];
		int mid=l+r>>1;
		int f1=(tr[ls(x)].H1==tr[ls(y)].H1),f2=(tr[ls(x)].H2==tr[ls(y)].H2),f3=((tr[ls(x)].siz==tr[ls(y)].siz));
		if (f1&&f2&&f3) return chkmin(rs(x),rs(y),mid+1,r);
		return chkmin(ls(x),ls(y),l,mid);
	}
	
	#undef ls
	#undef rs
}tr;

struct Uint {int v,len;}d1[N],d2[N];
inline bool operator <(const Uint &x,const Uint &y) {
	if (!x.v) return 0;
	if (!y.v) return 1;
	return tr.chkmin(x.v,y.v,1,m);
}

inline Uint add_E(Uint x,int id) {
	Uint y=x;y.len++;
	tr.update(y.len,1,m,y.v,id);
	return y;
}

ll dis[N];
struct Node {
	int id;ll val;
	inline bool operator <(const Node &x)const {return val>x.val;}
};

inline void dij1(int s) {
	priority_queue<Node> q;
	bitset<N> vis;
	f(i,1,n) dis[i]=1e18;
	q.push({s,dis[s]=0});
	while (!q.empty()) {
		int now=q.top().id;
		q.pop();
		if (vis[now]) continue;
		vis.set(now);
		for (int i=head[now];i;i=e[i].next) {
			int v=e[i].to,w=e[i].w;
			if (dis[v]<=dis[now]+w) continue;
			q.push({v,dis[v]=dis[now]+w});
		}
	}
}

inline void dij2(int s) {
	d1[s].v=tr.newNode(0);
	vector<int> idx;
	f(i,1,n) idx.push_back(i);
	sort(idx.begin(),idx.end(),[](int x,int y){return dis[x]<dis[y];});
	for (int now:idx) {
		for (int i=head[now];i;i=e[i].next) {
			int v=e[i].to,w=e[i].w,id=e[i].id;
			if (dis[v]!=dis[now]+w) continue;
			Uint ad1=add_E(d1[now],id);
			if (ad1<d1[v]) {
				d2[v]=d1[v];d1[v]=ad1;
				if (d2[now].v) d2[v]=min(d2[v],add_E(d2[now],id));
			}
			else if (ad1<d2[v]) d2[v]=ad1;
		}
	}
}

int main() {
	read(n);read(m);
	f(i,1,m) {
		int u,v,w;
		read(u);read(v);read(w);
		add(u,v,w,i);add(v,u,w,i);
	}
	tr.init_pw(m);
	dij1(1);dij2(1);
	f(i,1,n) {
		if (d2[i].v) printf("%lld",tr.s[d2[i].v]);
		else printf("-1");
		putchar((i==n)?10:32);
	}
	return 0;
}

Elections in Saransk (hard version)

solution

没啥好说的 dp。

对于 \(x\) 每个质因子,要求在每个位置选出不超过这个位置的限制的质因子个数,使得最后每个位置选出个数和减去选出的最大值恰好为 \(x\) 这个质因子个数。

那么枚举一下最大值,f_{i,j} 表示考虑到位置 \(i\),和为 \(j\) 的方案数,第二位上界不超过 \(x\) 的当前质因子个数加上最大值,前缀和优化一下,再将最大值没有达到当前最大值的部分容斥掉,即可做到 \(O(n\log^3 V)\)

对于非 \(x\) 质因子的质数,令 \(cnt_p\) 表示这个质数在序列中每个位置出现 \(p\) 个数和,那么还要乘上一个 \(\prod (cnt_p+1)\) 的系数,表示选或不选这个质数,如果要选只能在一个位置选若干个质因子,因为超过两个位置选就会带来这个质数的贡献。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
#include<map>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr int inf=1e9+10;

constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

vector<int> P[N];
inline void init(int n) {
	bitset<N> vis;
	f(i,2,n) {
		if (vis[i]) continue;
		for (int j=i;j<=n;j+=i) {
			int now=j;
			while (now%i==0) {
				P[j].push_back(i);
				now/=i;
			}
			vis.set(j);
		}
	}
}

int n,x;
int a[N],t[N],d[N];
int f[N],g[N],lst[N];
bitset<N> visP;

inline ll work(int k,int m) {
	int mx=0;
	f(i,1,n) {
		int val=a[i];
		for (int p:P[val]) t[i]+=(p==k);
		mx=max(mx,t[i]);
	}
	ll ret=0;
	f(c,1,mx) {
		int M=m+c+1;
		f(i,0,M) g[i]=1;
		f(i,1,n) {
			int mxt=min(c,t[i]);
			f(j,0,M) add(f[j],ad(g[j],mod-(j-mxt<=0?0:g[j-mxt-1])));
			g[0]=f[0];f[0]=0;
			if (i==n) continue;
			f(j,1,M) {
				add(g[j]=g[j-1],f[j]);
				f[j]=0;
			}
		}
		ret+=f[m+c]-lst[m+c];
		f(i,0,M) {
			lst[i]=f[i];
			f[i]=g[i]=0;
		}
	}
	f(i,0,m+mx+1) lst[i]=0;
	f(i,1,n) t[i]=0;
	return ((ret%mod+mod)%mod);
}

inline void solve() {
	read(n);read(x);
	f(i,1,n) read(a[i]);
	vector<int> tmp;
	for (int p:P[x]) d[p]++,tmp.push_back(p);
	sort(tmp.begin(),tmp.end());
	tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
	for (int p:tmp) visP.set(p);
	ll ret=1;
	map<int,int> mp;
	f(i,1,n) for (int p:P[a[i]]) if (!visP[p]) mp[p]++;
	for (pair<int,int> k:mp) ret=ret*(k.second+1)%mod;
	for (int k:tmp) ret=ret*work(k,d[k])%mod;
	printf("%lld\n",ret);
	for (int p:tmp) visP.reset(p);
	for (int p:P[x]) d[p]=0;
}

int main() {
	int T;init(N-1);
	read(T);
	while (T--) solve();
	return 0;
}

9.4

【初音未来】エリンジウム/Eryngium【香椎モイミ】

[PKUWC2018] 猎人杀

solution

钦定一个集合 \(S\)\(S\) 内的人都在 \(1\) 之后死亡,其他人不用管,令这个概率为 \(f(S)\)

答案为 \(\sum\limits_{S \subseteq [2,n]} (-1)^{\left\lvert S\right\rvert} f(S)\)

题意可以看作向所有人开枪直到打到一个活人为止,那么有 \(f(S)=\frac{w_1}{w_1+\sum\limits_{j\in S}w_j}\)

\(g_i\) 表示 \(\sum\limits_{j\in S}w_j =i\) 的容斥系数和,那么答案可以写作 \(w_1 \times \sum\limits_{i=0} \frac{g_i}{w_1+i}\)

直接背包是 \(O(n\sum w)\) 的,考虑多项式优化一下。

\(i\) 个人看作一个函数 \(1-x^{w_i}\),表示选或不选带给容斥系数的贡献,那么令 \(F(x)=\prod\limits_{i=2}^{n}(1-x^{w_i})\)\(g_i=[x^i]F(x)\)

直接分治 NTT,令 \(S=\sum w_i\),时间复杂度 \(O(S\log^2 S)\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

namespace Poly {
	constexpr int N=2e6+10;
	constexpr double pi=acos(-1.0);
	constexpr int mod=998244353,gen=3;
	
	inline ll qpow(ll a,int b) {
		ll res=1;
		while (b) {
			if (b&1) res=res*a%mod;
			a=a*a%mod;b>>=1;
		}
		return res;
	}
	
	static int len,rev[N];
	inline void initrev() {
		const int lt=log(len)/log(2)-1;
		f(i,1,len-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<lt));
	}
	
	static int g[30],flag_init_gen;
	inline void init_gen() {f(i,1,22) g[i]=qpow(gen,(mod-1)>>i);}
	inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
	inline void add(int &x,int y) {x=ad(x,y);}
	
	inline void NTT(int *a,int type) {
		if (!flag_init_gen) init_gen(),flag_init_gen=1;
		f(i,1,len-1) if (rev[i]<i) swap(a[i],a[rev[i]]);
		for (int mid=1,t=1;mid<len;mid<<=1,t++) {
			int Wn=g[t];
			for (int i=mid<<1,j=0;j<len;j+=i) {
				int w=1;
				for (int k=0;k<mid;k++,w=(ll)(w)*Wn%mod) {
					int x=a[j+k],y=(ll)(w)*a[j+k+mid]%mod;
					a[j+k]=ad(x,y);a[j+k+mid]=ad(x,mod-y);
				}
			}
		}
		if (type==-1) {
			reverse(a+1,a+len);
			const ll invn=qpow(len,mod-2);
			f(i,0,len-1) a[i]=invn*a[i]%mod;
		}
	}
	
	inline void poly_px(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=(ll)(f[i])*g[i]%mod;} 
	inline void poly_cpy(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=g[i];}
	inline void poly_clr(int *f,int l,int r) {f(i,l,r) f[i]=0;}
	
	inline void NTT_mul(int *f,int *g,int *ret,int lenf,int leng) {
		static int X0[N],Y0[N];
		for (len=1;len<lenf+leng;len<<=1);
		initrev();
		poly_cpy(X0,f,lenf);
		poly_cpy(Y0,g,leng);
		NTT(X0,1);NTT(Y0,1);poly_px(X0,Y0,len);NTT(X0,-1);
		f(i,0,lenf+leng) ret[i]=X0[i];
		f(i,0,len) X0[i]=Y0[i]=0;
	}
}

constexpr int N=3e5+10;
constexpr int mod=998244353;

int n;
int w[N];

inline vector<int> NTT(vector<int> X,vector<int> Y) {
	static int F[N],G[N],H[N];
	int lenf=X.size(),leng=Y.size(),lenh=lenf+leng-1;
	f(i,0,lenf-1) F[i]=X[i];
	f(i,0,leng-1) G[i]=Y[i];
	vector<int> ret;
	Poly::NTT_mul(F,G,H,lenf,leng);
	f(i,0,lenh-1) ret.push_back(H[i]);
	return ret;
}

inline vector<int> solve(int l,int r) {
	if (l==r) {
		vector<int> ret;
		ret.push_back(1);
		f(i,2,w[l]) ret.push_back(0);
		ret.push_back(mod-1);
		return ret;
	}
	int mid=l+r>>1;
	return NTT(solve(l,mid),solve(mid+1,r));
}

int main() {
	read(n);int sum=0;
	f(i,1,n) read(w[i]),sum+=(i>=2)*w[i];
	vector<int> f=solve(2,n);
	ll ans=0;
	f(i,0,sum) ans+=Poly::qpow(w[1]+i,mod-2)*f[i]%mod;
	printf("%d\n",(int)((ans%mod*w[1])%mod));
	return 0;
}

9.5

【补档】【歌愛ユキ】别走【想太】【2013.10.09】

第二类斯特林数·行

solution

\(f_i\)\(n\) 个两两不同的元素放进 \(i\) 个两两不同的集合中(可以为空)的方案数,令 \(g_i\) 表示将 \(n\) 个两两不同的元素恰好放进 \(i\) 个两两不同的集合使得每个集合都非空的方案数,那么有:

\[f_i = i^n \]

\[f_i=\sum_{j=0}^{i} \binom{i}{j} \times g_j \]

二项式反演可得:

\[g_i=\sum_{j=0}^{i} (-1)^{i-j} \times \binom{i}{j} \times f_j \]

由于题目要求集合全部相同,所以 \({n \brace m} = \frac{g_m}{m!}\)

拆开组合数可得:

\[{n \brace m} = \sum_{i=0}^{m} \frac{(-1)^{m-i} \times i^n}{i!\times (m-i)!} \]

发现这是一个卷积形式,令 \(F(x)=\sum\limits_{i=0} \frac{(-1)^{i}}{i!} x^i\)\(G(x)=\sum\limits_{i=0} \frac{i^n}{i!} x^i\),多项式卷积即可。

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

Code
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=4e5+10;
constexpr int inf=1e9+10;

constexpr int mod=167772161;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

ll fac[N],inv[N];
inline void init(int n) {
	fac[0]=1;
	f(i,1,n) fac[i]=fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2);
	F(i,n,1) inv[i-1]=inv[i]*i%mod;
}

namespace Poly {
	constexpr int N=4e6+10;
	constexpr double pi=acos(-1.0);
	constexpr int mod=167772161,gen=3;
	
	inline ll qpow(ll a,int b) {
		ll res=1;
		while (b) {
			if (b&1) res=res*a%mod;
			a=a*a%mod;b>>=1;
		}
		return res;
	}
	
	static int len,rev[N];
	inline void initrev() {
		const int lt=log(len)/log(2)-1;
		f(i,1,len-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<lt));
	}
	
	static int g[30],flag_init_gen;
	inline void init_gen() {f(i,1,22) g[i]=qpow(gen,(mod-1)>>i);}
	inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
	inline void add(int &x,int y) {x=ad(x,y);}
	
	inline void NTT(int *a,int type) {
		if (!flag_init_gen) init_gen(),flag_init_gen=1;
		f(i,1,len-1) if (rev[i]<i) swap(a[i],a[rev[i]]);
		for (int mid=1,t=1;mid<len;mid<<=1,t++) {
			int Wn=g[t];
			for (int i=mid<<1,j=0;j<len;j+=i) {
				int w=1;
				for (int k=0;k<mid;k++,w=(ll)(w)*Wn%mod) {
					int x=a[j+k],y=(ll)(w)*a[j+k+mid]%mod;
					a[j+k]=ad(x,y);a[j+k+mid]=ad(x,mod-y);
				}
			}
		}
		if (type==-1) {
			reverse(a+1,a+len);
			const ll invn=qpow(len,mod-2);
			f(i,0,len-1) a[i]=invn*a[i]%mod;
		}
	}
	
	inline void poly_px(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=(ll)(f[i])*g[i]%mod;} 
	inline void poly_cpy(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=g[i];}
	inline void poly_clr(int *f,int l,int r) {f(i,l,r) f[i]=0;}
	
	inline void NTT_mul(int *f,int *g,int *ret,int lenf,int leng) {
		static int X0[N],Y0[N];
		for (len=1;len<lenf+leng;len<<=1);
		initrev();
		poly_cpy(X0,f,lenf);
		poly_cpy(Y0,g,leng);
		NTT(X0,1);NTT(Y0,1);poly_px(X0,Y0,len);NTT(X0,-1);
		f(i,0,lenf+leng) ret[i]=X0[i];
		f(i,0,len) X0[i]=Y0[i]=0;
	}
}

int n;
int F[N],G[N],H[N];

int main() {
	read(n);init(n);
	f(i,0,n) F[i]=((i&1)?mod-inv[i]:inv[i]);
	f(i,0,n) G[i]=qpow(i,n)*inv[i]%mod;
	Poly::NTT_mul(F,G,H,n+1,n+1);
	f(i,0,n) printf("%d ",(int)(H[i]));
	return 0;
}

十二重计数法

solution

第一问

没啥好说的,答案为 \(m^n\)

第二问

答案为 \(m^{\underline n}\)

第三问

容斥一下,枚举钦定多少个盒子不满足条件,然后就变成第一问了。

第四问

就是第二类斯特林数求第 \(n\) 行前 \(m\) 项的和。

第五问

答案为 \([n\le m]\)

第六问

答案为 \(n\brace m\)

第七问

插板法,答案为 \(\binom{n+m-1}{m-1}\)

第八问

答案为 \(\binom{m}{n}\)

第九问

答案为 \(\binom{n-1}{m-1}\)

第十问

正片开始

首先有一个 \(n^2\) dp。

\(p_{i,j}\) 表示将 \(i\) 个球放入 \(j\) 个盒子的方案数,球之间互不区分,盒子之间互不区分。

那么有 \(p_{i,j}=p_{i-j,j}+p_{i,j-1}\)

这个转移方程表达的是,考虑将 \(i\) 个球放入 \(j\) 个盒子的一种方案,如果这种方案中,每个盒子至少有一个球,那么每个盒子拿走一个球,剩下的方案数与 \(i-j\) 个球,\(j\) 个盒子的方案数一一对应,如果这种方案中有一个盒子没有球,那么将没有球的盒子拿走,方案与 \(i\) 个球,\(j-1\) 个盒子的方案数一一对应。

考虑用一个生成函数表示这个式子,令 \(F_i(x)=\sum\limits_{j=0} p_{j,i} x^j\)

那么转移可以看作 \(F_i(x)=F_{i-1}(x) \times (1+x^i+x^{2i}+x^{3i}+\dots)\),即:

\[F_i(x) = \frac{F_{i-1}(x)}{1-x^i} \]

所以我们要求的实际上是:

\[F_m(x) = \prod_{i=1}^{m} \frac{1}{1-x^i} \]

这个可以对每个形如 \(\frac{1}{1-x^i}\) 的因子先求 \(\ln\),相加后再 \(\exp\) 回来。

考虑如何快速求 \(\ln\)

\[G(x) = \ln{\frac{1}{1-x^i}} \]

\[G'(x)= \frac{i x^{i-1}}{1-x^i} \]

\[G'(x)= (i x^{i-1}) \times \sum_{j=0}^{\infty} x^{ij} \]

\[G'(x)= \sum_{j=1}^{\infty } i x^{ij-1} \]

\[G(x)= \sum_{j=1}^{\infty} \frac{x^{ij}}{j} \]

在对 \(x^{n+1}\) 意义下求出把 \(i=1\sim m\) 对应的 \(G(x)\) 相加,再求 \(\exp\) 即可。

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

第十一问

答案为 \([n\le m]\)

第十二问

\(n<m\) 则为 \(0\),否则在每个盒子内放一个球,那么所有方案与球数减少 \(m\) 的第十问一一对应。

Code
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=4e5+10;
constexpr int inf=1e9+10;

namespace Poly {
	constexpr int N=4e6+10;
	constexpr double pi=acos(-1.0);
	constexpr int mod=998244353,gen=3;
	
	inline ll qpow(ll a,int b) {
		ll res=1;
		while (b) {
			if (b&1) res=res*a%mod;
			a=a*a%mod;b>>=1;
		}
		return res;
	}
	
	static int len,rev[N];
	inline void initrev() {
		const int lt=log(len)/log(2)-1;
		f(i,1,len-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<lt));
	}
	
	static int g[30],flag_init_gen;
	inline void init_gen() {f(i,1,22) g[i]=qpow(gen,(mod-1)>>i);}
	inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
	inline void add(int &x,int y) {x=ad(x,y);}
	
	inline void NTT(int *a,int type) {
		if (!flag_init_gen) init_gen(),flag_init_gen=1;
		f(i,1,len-1) if (rev[i]<i) swap(a[i],a[rev[i]]);
		for (int mid=1,t=1;mid<len;mid<<=1,t++) {
			int Wn=g[t];
			for (int i=mid<<1,j=0;j<len;j+=i) {
				int w=1;
				for (int k=0;k<mid;k++,w=(ll)(w)*Wn%mod) {
					int x=a[j+k],y=(ll)(w)*a[j+k+mid]%mod;
					a[j+k]=ad(x,y);a[j+k+mid]=ad(x,mod-y);
				}
			}
		}
		if (type==-1) {
			reverse(a+1,a+len);
			const ll invn=qpow(len,mod-2);
			f(i,0,len-1) a[i]=invn*a[i]%mod;
		}
	}
	
	inline void poly_px(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=(ll)(f[i])*g[i]%mod;} 
	inline void poly_cpy(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=g[i];}
	inline void poly_clr(int *f,int l,int r) {f(i,l,r) f[i]=0;}
	
	inline void NTT_mul(int *f,int *g,int *ret,int lenf,int leng) {
		static int X0[N],Y0[N];
		for (len=1;len<lenf+leng;len<<=1);
		initrev();
		poly_cpy(X0,f,lenf);
		poly_cpy(Y0,g,leng);
		NTT(X0,1);NTT(Y0,1);poly_px(X0,Y0,len);NTT(X0,-1);
		f(i,0,lenf+leng) ret[i]=X0[i];
		f(i,0,len) X0[i]=Y0[i]=0;
	}
	
	inline void poly_inv(int *F,int *G,int lenf) {
		static int X1[N],Y1[N];
		int Rlen=1;
		for (;Rlen<lenf;Rlen<<=1);
		f(i,0,Rlen) G[i]=0;
		G[0]=qpow(F[0],mod-2);
		for (int l=2;l<=Rlen;l<<=1) {
			poly_cpy(X1,F,l);poly_cpy(Y1,G,l);
			len=(l<<1);initrev();
			NTT(Y1,1);poly_px(Y1,Y1,len);
			NTT(X1,1);poly_px(Y1,X1,len);
			NTT(Y1,-1);poly_clr(Y1,l,len);
			for (int i=0;i<l;i++) add(G[i],ad(G[i],mod-Y1[i]));
		}
		poly_clr(G,lenf,Rlen);
		Rlen<<=1;
		f(i,0,Rlen) X1[i]=Y1[i]=0;
	}
	
	static int inv[N],flag_init_inv;
	inline void init_inv() {inv[1]=1;for(int i=2;i<N;i++)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;}
	inline void poly_dao(int *f,int *g,int len_mx) {
		g[len_mx-1]=0;
		for (int i=1;i<len_mx;i++) g[i-1]=1ll*f[i]*i%mod;
	}
	inline void poly_jif(int *f,int *g,int len_mx) {
		if (!flag_init_inv) init_inv(),flag_init_inv=1;g[0]=0;
		for (int i=1;i<len_mx;i++) g[i]=1ll*f[i-1]*inv[i]%mod;
	}
	
	inline void poly_ln(int *F,int *G,int lenf) {
		static int X3[N],Y3[N];
		poly_dao(F,X3,lenf);
		poly_inv(F,G,lenf);
		NTT_mul(X3,G,Y3,lenf,lenf);
		poly_jif(Y3,G,lenf);
		lenf<<=2;
		f(i,0,lenf) X3[i]=Y3[i]=0;
	}
	
	inline void poly_exp(int *F,int *G,int lenf) {
		static int X4[N],Y4[N];
		int Rlen=1;
		for (;Rlen<lenf;Rlen<<=1);
		f(i,0,Rlen) G[i]=0;
		G[0]=1;
		for (int l=2;l<=Rlen;l<<=1) {
			poly_cpy(X4,G,l>>1);
			poly_ln(X4,Y4,l);
			for (int i=0;i<l;i++) Y4[i]=ad(F[i],mod-Y4[i]);
			add(Y4[0],1);
			NTT_mul(X4,Y4,G,l,l);
			poly_clr(G,l,l<<1);
		}
		Rlen<<=1;
		f(i,0,Rlen) X4[i]=Y4[i]=0;
	}
}

constexpr int mod=998244353;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

ll fac[N],inv[N];
inline void init(int n) {
	fac[0]=1;
	f(i,1,n) fac[i]=fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2);
	F(i,n,1) inv[i-1]=inv[i]*i%mod;
}

inline ll C(int n,int m) {
	if (n<0||m<0||n<m) return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

inline ll A(int n,int m) {
	if (n<0||m<0||n<m) return 0;
	return fac[n]*inv[n-m]%mod;
}

inline int solve1(int n,int m) {return qpow(m,n);}
inline int solve2(int n,int m) {return A(m,n);}
inline int solve3(int n,int m) {
	ll ans=0;
	f(i,0,m) ans+=((i&1)?-1:1)*C(m,i)*qpow(m-i,n)%mod;
	return (ans%mod+mod)%mod;
}

inline int solve4(int n,int m) {
	static int F[N],G[N],H[N];
	f(i,0,m) F[i]=((i&1)?mod-inv[i]:inv[i]);
	f(i,0,m) G[i]=qpow(i,n)*inv[i]%mod;
	Poly::NTT_mul(F,G,H,m+1,m+1);
	ll ans=0;
	f(i,1,m) ans+=H[i];
	return ans%mod;
}
inline int solve5(int n,int m) {return n<=m;}
inline int solve6(int n,int m) {
	ll ans=0;
	f(i,0,m) ans+=(((m-i)&1)?-1:1)*C(m,i)*qpow(i,n)%mod;
	return (ans%mod+mod)%mod*inv[m]%mod;
}

inline int solve7(int n,int m) {return C(n+m-1,m-1);}
inline int solve8(int n,int m) {return C(m,n);}
inline int solve9(int n,int m) {return C(n-1,m-1);}

inline int solve10(int n,int m) {
	static int F[N],G[N],inv[N];
	inv[0]=inv[1]=1;
	f(i,2,n) inv[i]=1ll*(mod-(mod/i))*inv[mod%i]%mod;
	f(i,1,m) for (int j=1;i*j<=n;j++) add(G[i*j],inv[j]);
	Poly::poly_exp(G,F,n+1);
	int ans=F[n];
	f(i,0,n) F[i]=G[i]=0;
	return ans;
}
inline int solve11(int n,int m) {return n<=m;}
inline int solve12(int n,int m) {return n<m?0:solve10(n-m,m);}

int main() {
	int n,m;read(n);read(m);init(N-1);
	printf("%d\n%d\n%d\n",solve1(n,m),solve2(n,m),solve3(n,m));
	printf("%d\n%d\n%d\n",solve4(n,m),solve5(n,m),solve6(n,m));
	printf("%d\n%d\n%d\n",solve7(n,m),solve8(n,m),solve9(n,m));
	printf("%d\n%d\n%d\n",solve10(n,m),solve11(n,m),solve12(n,m));
	return 0;
}

付公主的背包

solution

和上题的第十问一样。

把 dp 式子写成一个生成函数的形式,每加入一个大小为 \(V\) 的物品等价于乘上 \(\frac{1}{1-x^V}\),取 \(\ln\) 相加后多项式 \(\exp\) 即可。

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

Code
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=4e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

namespace Poly {
	constexpr int N=1e6+10;
	constexpr double pi=acos(-1.0);
	constexpr int mod=998244353,gen=3;
	
	inline ll qpow(ll a,int b) {
		ll res=1;
		while (b) {
			if (b&1) res=res*a%mod;
			a=a*a%mod;b>>=1;
		}
		return res;
	}
	
	static int len,rev[N];
	inline void initrev() {
		const int lt=log(len)/log(2)-1;
		f(i,1,len-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<lt));
	}
	
	static int g[30],flag_init_gen;
	inline void init_gen() {f(i,1,22) g[i]=qpow(gen,(mod-1)>>i);}
	inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
	inline void add(int &x,int y) {x=ad(x,y);}
	
	inline void NTT(int *a,int type) {
		if (!flag_init_gen) init_gen(),flag_init_gen=1;
		f(i,1,len-1) if (rev[i]<i) swap(a[i],a[rev[i]]);
		for (int mid=1,t=1;mid<len;mid<<=1,t++) {
			int Wn=g[t];
			for (int i=mid<<1,j=0;j<len;j+=i) {
				int w=1;
				for (int k=0;k<mid;k++,w=(ll)(w)*Wn%mod) {
					int x=a[j+k],y=(ll)(w)*a[j+k+mid]%mod;
					a[j+k]=ad(x,y);a[j+k+mid]=ad(x,mod-y);
				}
			}
		}
		if (type==-1) {
			reverse(a+1,a+len);
			const ll invn=qpow(len,mod-2);
			f(i,0,len-1) a[i]=invn*a[i]%mod;
		}
	}
	
	inline void poly_px(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=(ll)(f[i])*g[i]%mod;} 
	inline void poly_cpy(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=g[i];}
	inline void poly_clr(int *f,int l,int r) {f(i,l,r) f[i]=0;}
	
	inline void NTT_mul(int *f,int *g,int *ret,int lenf,int leng) {
		static int X0[N],Y0[N];
		for (len=1;len<lenf+leng;len<<=1);
		initrev();
		poly_cpy(X0,f,lenf);
		poly_cpy(Y0,g,leng);
		NTT(X0,1);NTT(Y0,1);poly_px(X0,Y0,len);NTT(X0,-1);
		f(i,0,lenf+leng) ret[i]=X0[i];
		f(i,0,len) X0[i]=Y0[i]=0;
	}
	
	inline void poly_inv(int *F,int *G,int lenf) {
		static int X1[N],Y1[N];
		int Rlen=1;
		for (;Rlen<lenf;Rlen<<=1);
		f(i,0,Rlen) G[i]=0;
		G[0]=qpow(F[0],mod-2);
		for (int l=2;l<=Rlen;l<<=1) {
			poly_cpy(X1,F,l);poly_cpy(Y1,G,l);
			len=(l<<1);initrev();
			NTT(Y1,1);poly_px(Y1,Y1,len);
			NTT(X1,1);poly_px(Y1,X1,len);
			NTT(Y1,-1);poly_clr(Y1,l,len);
			for (int i=0;i<l;i++) add(G[i],ad(G[i],mod-Y1[i]));
		}
		poly_clr(G,lenf,Rlen);
		Rlen<<=1;
		f(i,0,Rlen) X1[i]=Y1[i]=0;
	}
	
	static int inv[N],flag_init_inv;
	inline void init_inv() {inv[1]=1;for(int i=2;i<N;i++)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;}
	inline void poly_dao(int *f,int *g,int len_mx) {
		g[len_mx-1]=0;
		for (int i=1;i<len_mx;i++) g[i-1]=1ll*f[i]*i%mod;
	}
	inline void poly_jif(int *f,int *g,int len_mx) {
		if (!flag_init_inv) init_inv(),flag_init_inv=1;g[0]=0;
		for (int i=1;i<len_mx;i++) g[i]=1ll*f[i-1]*inv[i]%mod;
	}
	
	inline void poly_ln(int *F,int *G,int lenf) {
		static int X3[N],Y3[N];
		poly_dao(F,X3,lenf);
		poly_inv(F,G,lenf);
		NTT_mul(X3,G,Y3,lenf,lenf);
		poly_jif(Y3,G,lenf);
		lenf<<=2;
		f(i,0,lenf) X3[i]=Y3[i]=0;
	}
	
	inline void poly_exp(int *F,int *G,int lenf) {
		static int X4[N],Y4[N];
		int Rlen=1;
		for (;Rlen<lenf;Rlen<<=1);
		f(i,0,Rlen) G[i]=0;
		G[0]=1;
		for (int l=2;l<=Rlen;l<<=1) {
			poly_cpy(X4,G,l>>1);
			poly_ln(X4,Y4,l);
			for (int i=0;i<l;i++) Y4[i]=ad(F[i],mod-Y4[i]);
			add(Y4[0],1);
			NTT_mul(X4,Y4,G,l,l);
			poly_clr(G,l,l<<1);
		}
		Rlen<<=1;
		f(i,0,Rlen) X4[i]=Y4[i]=0;
	}
}

constexpr int mod=998244353;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

int n,m;
int F[N],G[N],t[N],inv[N];

int main() {
	read(n);read(m);
	f(i,1,n) {int x;read(x);t[x]++;}
	inv[0]=inv[1]=1;
	f(i,2,m) inv[i]=1ll*(mod-(mod/i))*inv[mod%i]%mod;
	f(i,1,m) if (t[i]) {
		int x=t[i];
		for (int j=1;j*i<=m;j++) add(G[i*j],1ll*inv[j]*x%mod);
	}
	Poly::poly_exp(G,F,m+1);
	f(i,1,m) printf("%d\n",F[i]);
	return 0;
}

9.6

【东方/熟肉】Koishi Disco(小石Disco)- 东方冰之勇者记DLC OST(歌词)

补一下昨天 MX 的比赛。

\(100+100+100+100+100+0+20=520\),/xin。

【MX-X31-T1】「FAOI-R14」四元博弈

solution

诈骗题。

考虑根节点上有没有值。

若有值,则先手选择将根节点上的值清零,后手只能选非根节点的位置进行异或,那么就会使根节点变为有值,先手继续清零根节点的值即可,这样先手必胜。

若没有值,则先手只能选择非根节点的位置进行异或,,那么根节点就变成有值,后手清零根节点,像上文提到的进行操作,后手必胜。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

inline void solve() {
	int n,flag;read(n);read(flag);
	f(i,2,n) {int x;read(x);}
	f(i,2,n) {int x,y;read(x);read(y);}
	puts(flag?"Si":"Yuan");
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

【MX-X31-T2】「FAOI-R14」警察抓小偷

solution

神秘大分讨。

首先这是一个内向基环树森林,那么在叶子节点上一定需要放一个警察,不然小偷就待在叶子节点上不走了。

放完叶子节点上的警察后,我们还需要保证在一定的时间后环上全部被警察覆盖,不然小偷就可以在环上绕着走,所以需要在初始时放一些警察在其他节点上保证覆盖这个环,按照 \(0\) 时刻能够走到环上哪个位置分成若干个等价类,每个等价类取最小值即可,方案数即为最小值个数,注意特判 \(w_i=0\) 的情况。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N],w[N];
ll P=1;

int vis[N],in[N],col[N];
inline ll topu() {
	ll ret=0;
	queue<int> q;
	f(i,1,n) if (!in[i]) ret+=w[i],q.push(i),col[i]=1;
	while (!q.empty()) {
		int now=q.front(),v=a[now];
		vis[now]=1;
		q.pop();
		in[v]--;
		if (!in[v]) q.push(v);
	}
//	puts("topu:ok");
//	f(i,1,n) printf("%d ",vis[i]);
//	putchar(10);
	return ret;
}

int dfn[N];
vector<int> e[N];
vector<int> vec;

int Len,t[N];
vector<int> can[N];

inline void clear() {
	f(i,1,n) {
		a[i]=w[i]=dfn[i]=0;
		vis[i]=in[i]=col[i]=0;
		e[i].clear();
		can[i].clear();
	}
	vec.clear();P=1;
}

inline void dfs1(int now) {
//	printf("dfs1:%d\n",now);
	dfn[now]=1;
	vec.push_back(now);
	if (!dfn[a[now]]&&!vis[a[now]]) dfs1(a[now]);
}

inline void dfs2(int now,int val) {
//	printf("dfs2:%d\n",now);
	if (e[now].empty()&&vis[now]) t[val]++;
	else can[val].push_back(w[now]);
	val--;if (val<0) val=Len-1;
	for (int v:e[now]) if (vis[v]) dfs2(v,val);
}

constexpr int mod=998244353;
inline ll work(vector<int> tmp) {
	Len=tmp.size();
	int cnt=0;
	for (int x:tmp) {
		dfs2(x,cnt);
		cnt++;
	}
	ll ret=0;
	for (int i=0;i<Len;i++) {
		if (t[i]) {
			int c0=0;
			for (int x:can[i]) c0+=(x==0);
			f(i,1,c0) P=P*2%mod;
		}
		else {
			int mn=2e9,num=0;
			for (int x:can[i]) {
				if (x<mn) mn=x,num=1;
				else if (x==mn) num++;
			}
//			printf("num=%d\n",num);
			if (mn==0) {
				ll C=1;
				f(i,1,num) C=C*2%mod;
				C--;
				P=P*C%mod;
			}
			else P=P*num%mod;
			ret+=mn;
		}
		t[i]=0;
		can[i].clear();
	}
	return ret;
}

inline void solve() {
	read(n);
	f(i,1,n) read(w[i]);
	f(i,1,n) {
		read(a[i]),in[a[i]]++;
		e[a[i]].push_back(i);
	}
	ll ans=topu();
	f(i,1,n) if (!vis[i]&&!dfn[i]) {
		vec.clear();
		dfs1(i);
		ans+=work(vec);
	}
	printf("%lld %lld\n",ans,P);
	clear();
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

【MX-X31-T3】「FAOI-R14」数列计数 2025

solution

考虑对于固定的 \(m\) 怎么做。

\(m<n\) 则答案为 \(0\),考虑 \(m\ge n\) 的情况。

首先需要发现一些性质。

  • 所有元素一定不在 \([n+1,2n]\) 内,否则若出现元素 \(x\in[n+1,2n]\),则选择 \(i=n,j=x-n\),根据题意需要满足 \(a_i+a_j\) 在序列 \(a\) 内,而 \(a_n\) 是序列中最大的元素,那么 \(a_i+a_j>a_n\) 一定不在序列 \(a\) 内。
  • \(a_i\in [1,n]\),则必须满足 \(a_i=i\),否则考虑第一个 \(a_i>i\) 的位置 \(p\),由题目中第三条限制有 \(p>\left\lfloor \frac{n}{2} \right\rfloor\),选择 \(i=p,j=a_p-p\),那么得到 \(j\le\left\lfloor \frac{n}{2} \right\rfloor\),则有 \(a_i+a_j\le 2n\),若 \(a_i+a_j>n\),则不满足上述第一条性质,序列不满足条件,否则继续考虑值为 \(a_i+a_j\) 的位置,令这个位置为 \(x\),选择 \(i=x,j=a_x-x\),递归直到出现 \(a_i+a_j>n\) 时结束,因为 \(a_i+a_j\) 是递增的,所以一定能找到 \(i,j\) 使得 \(a_i+a_j>n\)
  • \(a_i>2n\),则只需要满足递增的限制即可,因为两个下标相加不可能大于 \(2n\)

所以我们得到了:

  • 这个序列由一段满足 \(a_i=i\) 的前缀和一段值域在 \([2n+1,m]\) 的后缀构成。

\(m\le 2n\) 则答案为 \(1\),所以考虑 \(m>2n\) 的情况。

令答案为 \(f_m\),枚举一下后缀的长度,根据插板法,可以得到:

\[f_m=\sum_{i=0}^{n} \binom{m-2n}{i} \]

\(n\ge m-2n\),那么这个式子的组合意义即为 \(m-2n\) 个物品随便选的方案数,答案即为 \(2^{m-2n}\)

否则根据杨辉三角的转移,容易得到 \(f_m=2f_{m-1}-\binom{m-1-2n}{n}\)

时间复杂度 \(O(r)\)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=4e7+10;
constexpr int inf=1e9+10;

constexpr int mod=998244353;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

int fac[N],inv[N],pw2[N];
inline void init(int n) {
	fac[0]=1;pw2[0]=1;
	f(i,1,n) {
		fac[i]=1ll*fac[i-1]*i%mod;
		add(pw2[i]=pw2[i-1],pw2[i-1]);
	}
	inv[n]=qpow(fac[n],mod-2);
	F(i,n,1) inv[i-1]=1ll*inv[i]*i%mod;
}

inline ll C(int n,int m) {
	if (n<0||m<0||n<m) return 0;
	return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}

ll lst=-1;
inline int solve(int n,int m) {
	int L=m-n*2;
	if (L<0) return 1;
	if (n>=L) return pw2[L];
	if (lst==-1) {
		ll ans=0;
		f(i,0,n) ans+=C(L,i);
		return lst=ans%mod;
	}
	lst=lst*2-C(L-1,n);
	return lst=((lst%mod+mod)%mod);
}

int main() {
	ll n;int L,R;
	scanf("%lld%d%d",&n,&L,&R);init(R);
	if (n>R) return puts("0"),0;
	int ans=0;
	f(i,max((int)n,L),R) ans^=(1ll*i*solve(n,i)%1000000007);
	printf("%d\n",ans);
	return 0;
}

【MX-X31-T4】「FAOI-R14」单人游戏

solution

神秘推式子题。

首先若特殊点 \(u\) 为叶子则无解,考虑 \(u\) 不是叶子的情况。

考虑删去特殊点 \(u\) 后形成的若干个连通块,那么随机选出的路径经过 \(u\) 当且仅当两个端点在不同的连通块内,那么答案只与这些连通块的大小有关,并且同一连通块内节点期望相同。

令分成了 \(d\) 个连通块,第 \(i\) 个连通块大小为 \(s_i\),其中每个节点期望均为 \(E_i\),令 \(T=\sum\limits_{i=1}^{d}s_i\times E_i\),即所有点的期望之和。

对于一个连通块 \(x\),可以得到:

\[E_x=1+\frac{1}{n-1}\times (s_x\times E_x + (1-p) \times (T- s_x\times E_x)) \]

意义为有 \(\frac{s_x}{n-1}\) 的概率选到同一个连通块内,此时期望为 \(E_x\),否则有 \(p\) 的概率结束游戏,\(1-p\) 的概率在其他连通块内随机选择一个节点继续游戏。

那么化简一下这个式子,可以得到:

\[E_x = \frac{n-1+(1-p)\times T}{n-1-p\times s_x} \]

所以我们只需要求出 \(T\) 即可。

\[T=\sum_{i=1}^{d} s_i\times E_i \]

\[T=(n-1+(1-p)\times T)\times \sum_{i=1}^{d} \frac{s_i}{n-1-p\times s_i} \]

\(S=\sum\limits_{i=1}^{d} \frac{s_i}{n-1-p\times s_i}\),化简可得:

\[T=\frac{(n-1)\times S}{1-(1-p)\times S} \]

对于每个节点预处理出对应的 \(T\),然后倍增得到 \(x\) 所在连通块大小,即可求出 \(E_x\)

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

constexpr int mod=998244353;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

int n,p,m,idx;
int f[N][20],dfn[N],siz[N],T[N],dep[N];
vector<int> e[N];

inline ll calc(int sz) {return qpow(((n-1-1ll*p*sz)%mod+mod)%mod,mod-2)*sz%mod;}
inline void dfs(int now,int fa) {
	dep[now]=dep[fa]+1;
	f[now][0]=fa;
	f(i,1,18) f[now][i]=f[f[now][i-1]][i-1];
	dfn[now]=++idx;
	siz[now]=1;
	ll sum=0;
	for (int v:e[now]) if (v^fa) {
		dfs(v,now);
		sum+=calc(siz[v]);
		siz[now]+=siz[v];
	}
	if (fa) sum+=calc(n-siz[now]);
	sum%=mod;
	T[now]=(n-1)*sum%mod*qpow(((1-(1-p)*sum)%mod+mod)%mod,mod-2)%mod;
}

inline int get_s(int x,int y) {
	int L=dfn[y],R=dfn[y]+siz[y]-1;
	if (!(L<=dfn[x]&&dfn[x]<=R)) return n-siz[y];
	F(i,18,0) if (dep[f[x][i]]>dep[y]) x=f[x][i];
	return siz[x];
}

inline int solve(int x,int u) {
	if (e[u].size()==1) return -1;
	ll X=(((n-1)+1ll*(1-p)*T[u])%mod+mod)%mod;
	ll Y=((n-1-1ll*p*get_s(x,u))%mod+mod)%mod;
	return X*qpow(Y,mod-2)%mod;
}

int main() {
	read(n);read(p);read(m);p=1ll*p*qpow(100,mod-2)%mod;
	f(i,2,n) {
		int u,v;read(u);read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	while (m--) {
		int u,v;read(u);read(v);
		printf("%d\n",solve(u,v));
	}
	return 0;
}

【MX-X31-T5】「FAOI-R14」noiday2t3 数据

solution

赛时特判掉度数小于 \(12\) 的点其他暴力做居然能过。

第一次见这个 trick,写篇题解记录一下。

由于平面图 \(\left\lvert E \right\rvert \le 3 \left\lvert V \right\rvert - 6\),所以一定存在一个度数不超过 \(5\) 的点。

考虑给边定向,每个节点只维护指向它的节点的背包,然后枚举它出边指向的点选或不选来计算答案。

那么每次找到一个度数不超过 \(5\) 的点,将它与还未删去节点的连边定向为出边,并删去这个点,剩下的图依然是平面图,所以每个点的出边数量不超过 \(5\)

修改时暴力修改出边指向节点的背包,即撤销原来贡献后再加入新的贡献,询问时枚举出边指向的点选或不选计算答案即可。

时间复杂度 \(O(mV+q_1 D V+ q_2 2^D)\),其中 \(V=5000,D=5\)

Code
#include<cstdio>
#include<algorithm>
#include<queue>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5000+10;
constexpr int V=5000;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

constexpr int mod=998244353;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

int n,m,q1,q2;
int a[N],d[N],num[N];
vector<int> e[N],T[N];
int f[N][N],g[N][N];

inline void mdf(int x) {g[x][0]=f[x][0];f(i,1,V) add(g[x][i]=g[x][i-1],f[x][i]);}
inline void ins(int *F,int x) {F(i,V,x) add(F[i],F[i-x]);}
inline void del(int *F,int x) {f(i,x,V) add(F[i],mod-F[i-x]);}

inline void topu() {
	queue<int> q;
	f(i,1,n) if (d[i]<=5) q.push(i);
	bitset<N> vis;
	while (!q.empty()) {
		int now=q.front();
		q.pop();
		if (vis[now]) continue;
		vis.set(now);
		for (int v:e[now]) if (!vis[v]) {
			d[v]--;
			if (d[v]<=5) q.push(v);
			T[now].push_back(v);
		}
	}
}

inline int query(int x,int y) {
	static int w[12];
	int cnt=0;ll ret=0;
	for (int v:T[x]) w[cnt++]=a[v];cnt--;
	for (int S=(1<<(cnt+1)),s=0;s<S;s++) {
		int sum=0,now=s;
		while (now) {
			int lb=(now&-now);
			sum+=w[num[lb]];
			now^=lb;
		}
		if (sum<=y) ret+=g[x][y-sum];
	}
	return ret%mod;
}

int main() {
	read(n);read(m);read(q1);read(q2);q1+=q2;
	f(i,1,n) {
		int L;read(L);d[i]=L;
		while (L--) {
			int v;read(v);
			e[i].push_back(v);
		}
	}
	topu();
	f(i,0,4) num[1<<i]=i;
	f(i,1,n) read(a[i]),f[i][0]=1;
	f(i,1,n) for (int v:T[i]) ins(f[v],a[i]);
	f(i,1,n) mdf(i);
	while (q1--) {
		int op,x,y;
		read(op);read(x);read(y);
		if (op==1) {
			for (int v:T[x]) del(f[v],a[x]);
			a[x]=y;
			for (int v:T[x]) ins(f[v],a[x]),mdf(v);
		}
		else printf("%d\n",query(x,y));
	}
	return 0;
}
posted @ 2026-09-04 08:38  Ff472130  阅读(2)  评论(0)    收藏  举报