ZJOI2022 题解

D1T1 树

\(f_S\) 为第一棵树最终叶子集合恰好为 \(S\) 的方案数,\(g_S\) 为第二棵树最终叶子集合恰好为 \(S\) 的方案数。那么答案就是 \(\sum_{S\subset T}f_Sg_{T/S}\),其中 \(T\) 为集合 \(\{1,2,\dots,n\}\)

\(f,g\) 不好直接计算,考虑容斥,再设 \(f'_S\) 表示第一棵树叶子集合包含 \(S\) 的方案数,\(g'_S\) 为第二棵树最终叶子集合包含为 \(S\) 的方案书,可以发现这两个方案数就可以 \(\mathcal O(n)\) 扫一遍计算了。

于是

\[\begin{aligned} ans&=\sum_{S\subset T}f_Sg_{T/S}\\ &=\sum_{S\subset T}(\sum_{S\subset U}(-1)^{|U|-|S|}f'_U)(\sum_{(T/S)\subset V}(-1)^{|V|-|T/S|}g'_V)\\ &=\sum_{S\subset T}(\sum_{S\subset U}(-1)^{|U|-|S|}f'_U)(\sum_{V\subset S}(-1)^{|S|-|V|}g'_{T/V})\\ &=\sum_{U\subset T}f'_U\sum_{V\subset U}g'_{T/V}\sum_{V\in S\in U}(-1)^{|U|-|S|}(-1)^{|S|-|T|}\\ &=\sum_{U\subset T}f'_U\sum_{V\subset U}g'_{T/V}\sum_{V\in S\in U}(-1)^{|U|-|T|}\\ &=\sum_{U\subset T}f'_U\sum_{V\subset U}g'_{T/V}(-2)^{|U|-|T|}\\ \end{aligned} \]

介于 \(f',g'\) 都可以在从左到右扫一遍的过程中计算,可以用 \(dp\) 计算 \(ans\),记 \(f_{i,j,k}\) 表示考虑了前 \(i\) 个点,有 \(j\) 个在 \(U\) 集合中,且 \(i\) 之后的点有 \(k\) 个在 \(V\) 集合中时的系数之和,于是有转移:

\[f_{i,j+1,k-1}\gets (k-1)(i-j-1)f_{i-1,j,k}\\ f_{i,j+1,k}\gets -2kf_{i-1,j,k}\\ f_{i,j,k}\gets kf_{i-1,j,k}\\ \]

分别表示将 \(i\) 点同时加入 \(U,V\)/之加入 \(U\)/都不加入。

注意最后一个点不需要选择父亲,因此最后转移到答案时需要特判一下。复杂度 \(\mathcal O(n^3)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct barrett{
	ll M;__int128 mu;
	inline void init(int _M=0){M=_M;mu=-1ull/M;}
	inline ll reduce(const ll x)const{
		ll r=x-(mu*x>>64)*M;
		return r>=M?r-M:r;
	}
}Mod;
inline ll operator%(const ll &x,const barrett &mod) { return mod.reduce(x); }
inline void operator%=(ll &x,const barrett &mod){x=x%mod;}
const int N=510;
int n,m,mod,f[2][N][N];
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline void rec(int &x,int y){x=(x-y<0)?x-y+mod:x-y;}
int main(){
	scanf("%d%d",&n,&mod);Mod.init(mod);
	int las=0,cur=1;
	for(int i=0;i<=n;++i)
		f[0][0][i]=1;
	for(int i=1;i<=n;++i){
		int ret=0;
		for(int j=0;j<i;++j)
			for(int k=0;k<=n-i+1;++k){
				if(!f[las][j][k]) continue;
				int g=f[las][j][k],xs=(i>1)?i-1-j:1,tmp=1ll*g*xs%Mod;
				if(k){
					f[cur][j+1][k-1]=(f[cur][j+1][k-1]+1ll*tmp*(k-1))%Mod;//S&T
					if(k==1) inc(ret,tmp);
				}
				rec(f[cur][j+1][k],2ll*tmp*k%Mod);//only S
				if(!k) rec(ret,add(tmp,tmp));
				f[cur][j][k]=(f[cur][j][k]+1ll*tmp*k)%Mod;//no S no T
				if(!k) inc(ret,tmp);
				f[las][j][k]=0;
			}
		if(i>1)
			printf("%d\n",ret);
		swap(las,cur);
	}
	return 0;
}

D1T2 众数

可以发现最终的决策一定是选择一个区间 \([l,r]\) ,分别取出区间 \([l,r]\) 内的众数 \(x\),与区间外的众数 \(y\),然后通过对区间整体加 \(y-x\),因此最终答案就是区间内众数出现次数+区间外众数出现次数。

这个问题目测不弱于区间众数,因此考虑复杂度 \(\mathcal O(n\sqrt{n})\) 的做法,考虑根号分治:

  • 若在整个序列中 \(x\) 的出现次数 \(\ge \sqrt{n}\) 或者 \(y\) 的出现次数 \(\ge \sqrt{n}\)

    考虑枚举这个出现次数 \(\ge \sqrt{n}\) 的点 \(x\) 以及另一个数 \(y\),变为以下两个问题:

    • 将每个 \(x\) 看作 \(-1\),将 \(y\) 看作 \(1\),查询最大子段和加上 \(x\) 的个数计入答案。
    • 将每个 \(x\) 看作 \(1\),将 \(y\) 看作 \(-1\),查询最大子段和加上 \(y\) 的个数计入答案。

    于是可以先 \(\mathcal O(n)\) 扫一遍序列预处理前缀和后实现 \(\mathcal O(1)\) 查询区间内 \(x\) 的个数,然后就可以遍历一遍 \(y\) 的所有出现位置解决这两个问题了,单次复杂度是 \(\mathcal O(n)\) 的。

  • \(x,y\) 出现次数均 \(\le \sqrt{n}\)

    首先特判掉 \(x\) 只在选定区间的一边出现的情况。否则,考虑枚举 \([l,r]\)\(x\) 最靠左以及最靠右的出现位置,以及 \(y\)\([1,l)\) 中最靠右的出现位置和 \((r,n]\) 中最靠左的出现位置。得到两个二元组 \((x_1,x_2),(y_1,y_2)\),可以先暴力找出每个二元组以及其对应的贡献,可发现二元组的数量是 \(\mathcal O(n\sqrt{n})\).接下来相当于要对 \(\mathcal O(n\sqrt{n})\) 个区间 \([l,r]\) 被该区间完全覆盖的二元组的权值最大值,可以动态移动左端点,遇到每个二元组就进行后缀 \(\max\) /单点查询,使用树状数组维护复杂度为 \(\mathcal O(n\sqrt{n}\log n)\)。同时注意到由于所有二元组的权值都 \(\le \sqrt{n}\),且修改过程中每个位置的权值始终不减,可以在进行后缀取 \(\max\) 时,从起始点出发暴力修改,遇到改不了的就退出。那么每个点只会被修改 \(\mathcal O(\sqrt{n})\) 次,复杂度就是 \(\mathcal O(n\sqrt{n})\) 了。

总复杂度 \(\mathcal O(n\sqrt{n})\)

#include<bits/stdc++.h>
using namespace std;
namespace iobuff{
	const int LEN=1000000;
	char in[LEN+5],out[LEN+5];
	char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
	inline char gc(void){
		#ifdef TQX
		return getchar();
		#endif
		return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
	}
	inline void pc(char c){
		pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
		(*pout++)=c;
	}
	inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
	template<typename T> inline void read(T &x){
		static int f;
		static char c;
		c=gc(),f=1,x=0;
		while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
		while(c>='0'&&c<='9') x= 10*x+c-'0',c=gc();
		x*=f;
	}
	template<typename T> inline void putint(T x,char div){
		static char s[15];
		static int top;
		top=0;
		x<0?pc('-'),x=-x:0;
		while(x) s[top++]=x%10,x/=10;
		!top?pc('0'),0:0;
		while(top--) pc(s[top]+'0');
		pc(div);
	}
}
using namespace iobuff;
const int N=2e5+10;
int T,n,a[N],d[N],tot,pre[N],suf[N],f[N],pos[N],sq,ans[N],ct[N];
vector<int> ve[N];
inline void sub(){
	for(int i=0;i<=n+1;++i) ans[i]=ct[i]=pre[i]=suf[i]=0;
	int mx=0;
	for(int i=1;i<=n;++i){
		mx=max(mx,++ct[a[i]]);
		pre[i]=mx;
	}
	for(int i=1;i<=n;++i) ct[i]=0;mx=0;
	for(int i=n;i>=1;--i){
		++ct[a[i]];
		f[a[i]]=max(f[a[i]],pre[i-1]+ct[a[i]]);
		mx=max(mx,ct[a[i]]);
		suf[i]=mx;
	}
	for(int i=1;i<=n;++i) ct[i]=0;
	for(int i=1;i<=n;++i){
		++ct[a[i]];
		f[a[i]]=max(f[a[i]],ct[a[i]]+suf[i+1]);
	}
	for(int l=n;l>=1;--l){
		int x=a[l];
		if(ve[x].size()>=sq) continue;
		for(int i=pos[l];i<ve[x].size();++i){
			int r=ve[x][i],tmp=i-pos[l]+1;
			f[x]=max(f[x],(int)ve[x].size()-max(i-pos[l]-1,0)+ans[r]);
			while(r<=n&&ans[r]<tmp) ans[r]=tmp,++r;
		}
	}
}
int main(){
	read(T);
	while(T--){
		read(n);
		for(int i=1;i<=n;++i) read(a[i]),d[i]=a[i];
		sort(d+1,d+n+1);
		tot=unique(d+1,d+n+1)-d-1;
		for(int i=1;i<=tot;++i) ve[i].clear();
		for(int i=1;i<=n;++i) a[i]=lower_bound(d+1,d+tot+1,a[i])-d,ve[a[i]].push_back(i);
		for(int i=1;i<=tot;++i) f[i]=ve[i].size();
		int ans=-1;
		vector<int> ret;
		sq=ceil(sqrt(n))+0.5;
		for(int i=1;i<=tot;++i){
			if(ve[i].size()>=sq){
				for(int j=1;j<=n;++j)
					pre[j]=pre[j-1]+(a[j]==i);
				int cur=pre[n];
				for(int k=1;k<=tot;++k){
					if(k==i) continue;
					int mx1=0,mx2=0;
					for(int j=0;j<ve[k].size();++j){
						if(j==0) mx1=1;
						else mx1=max(1,mx1+1-(pre[ve[k][j]]-pre[ve[k][j-1]]));
						if(j) mx2=max(mx2,j)+pre[ve[k][j]]-pre[ve[k][j-1]];
						cur=max(cur,mx1+pre[n]);
						f[k]=max(f[k],mx2+(int)ve[k].size()-j);
					}
				}
				f[i]=max(f[i],cur);
			}
			for(int j=0;j<ve[i].size();++j) pos[ve[i][j]]=j;
		}
		sub();
		for(int i=1;i<=tot;++i){
			if(f[i]>ans) ans=f[i],ret.clear();
			if(f[i]==ans) ret.push_back(d[i]);
		}
		printf("%d\n",ans);
		for(int v:ret) printf("%d\n",v);
	}
	return 0;
}

D1T3 简单题

考虑题目中提到的图特殊性质:存在给图中边重新赋权的方法使得所有环上边权之和相等。

这个性质显然对于所有点双是独立的。对于某一个点双,手玩一下可以发现如果存在三个环 \(x,y,z\) 满足 \(x\)\(y\) 有交,\(x\)\(z\) 有交,那么在环 \(x\) 上,不在环 \(y\) 与环 \(z\) 上的边的权值必须是 \(0\) ,这与题设矛盾。从这一点出发,首先点双如果是一个环一定合法,接下来在环上两点 \(a,b\) 之间增加若干条不交的链,得到一个“杏仁”形状的图,这个图显然合法。但在杏仁的基础上在加边,就一定会出现上面的情况。因此合法的图满足每个点双都是杏仁图。

于是考虑对该图建立圆方树,那么我们需要能够快速计算两个圆点在点双内的合法路径数量以及所有路径的长度之和。对杏仁图这是容易计算的:

设杏仁图的链数为 \(r\),权值和为 \(s\),两端点为 \(a,b\)

  • 询问点 \(x,y\) 在杏仁的一条链上,不妨设 \(x\) 是距离 \(a\) 更近的点:路径数量为 \(r\),长度和为 \(s+(r-2)(dis(a,x)+dis(y,b))\)
  • 询问点 \(x,y\) 不在一条链上:路径数量为 \(2(r-1)\),长度为 \(2s+(r-3)(w_x,w_y)\),其中 \(w_x\)\(x\) 所在链的权值之和。

接下来就可以像一般圆方树求仙人掌上两点距离一样完成了,复杂度是 \(\mathcal O(n\log n)\) 的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10,M=6.4e5+10,mod=998244353;
inline int ksm(int x,int y){
	int ret=1;
	for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
	return ret;
}
inline int ad(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
struct node{
	int u,v,w,nxt;
};
int n,m,q,sum;
struct Val{
	int x,y;
	Val(int _x=0,int _y=0){x=_x;y=_y;}
};
inline Val operator +(const Val &a,const Val &b){
	return Val(1ll*a.x*b.x%mod,(1ll*a.x*b.y+1ll*a.y*b.x)%mod);
}
inline Val operator -(const Val &a,const Val &b){
	int iv=ksm(b.x,mod-2),xx=1ll*a.x*iv%mod;
	return Val(xx,1ll*dec(a.y,1ll*b.y*xx%mod)*iv%mod);
}

namespace DCCwork{
	node e[N<<1];
	int pos[N],first[N],cnt=1,de[N];
	inline void add(int u,int v,int w){
		de[u]++;
		e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;
	}
	struct DCC{
		vector<int> ve;
		vector<node> edg;
		int a,b,r,s,sz,*bel,*w;
		ll *da,*db;
		inline int gid(int x){return lower_bound(ve.begin(),ve.end(),x)-ve.begin();}
		inline Val query(int x,int y){
			if(x==y) return Val(1,0);
			x=gid(x);y=gid(y);
			if(bel[x]==bel[y]||(!bel[x])||(!bel[y])){
				if(da[x]>da[y]) swap(x,y);
				return Val(r,ad(s,1ll*(r-2)*((da[x]+db[y])%mod)%mod));
			}
			else{
				return Val(2*(r-1),ad(2ll*s%mod,1ll*dec(r,3)*ad(w[bel[x]],w[bel[y]])%mod));
			}
		}
		inline void init(){
			a=-1;b=-1;cnt=1;sz=ve.size();
			sort(ve.begin(),ve.end());
			for(int i=0;i<ve.size();++i) pos[ve[i]]=i,first[i]=de[i]=0;
			for(auto p:edg){
				add(pos[p.u],pos[p.v],p.w),add(pos[p.v],pos[p.u],p.w);
				s=ad(s,p.w);
			}
			for(int i=0;i<sz;++i){
				if(de[i]!=2){
					if(a==-1) a=i;
					else b=i;
				}
			}
			if(a==-1) a=0,b=1;
			r=0;
			bel=new int[sz];da=new ll[sz];db=new ll[sz];
			w=new int[sz];
			bel[a]=bel[b]=0;da[a]=db[b]=0;db[a]=da[b]=1e18;
			for(int i=first[a];i;i=e[i].nxt){
				int v=e[i].v;
				++r;
				int now=v,las=a;
				ll lw=e[i].w,dis=e[i].w;
				while(now!=b){
					bel[now]=r;
					da[now]=dis;
					for(int j=first[now];j;j=e[j].nxt)
						if(e[j].v!=las){
							las=now;lw=e[j].w;dis+=e[j].w;
							now=e[j].v;break;
						}
				}
				w[r]=dis%mod;
				now=las;las=b;dis=lw;
				while(now!=a){
					db[now]=dis;
					for(int j=first[now];j;j=e[j].nxt)
						if(e[j].v!=las){
							las=now;dis+=e[j].w;
							now=e[j].v;break;
						}
				}
			}
		}
	}D[N];
}
using DCCwork::D;
namespace Tree{
	struct edge{
		int u,v,nxt;Val w;
	}e[N<<1];
	int first[N],cnt=1,ct,fa[N],dep[N],pa[N][20];
	Val ans[N];
	inline void add(int u,int v,Val w){
		e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;
	}
	inline int LCA(int x,int y){
		if(dep[x]<dep[y]) swap(x,y);
		int t=dep[x]-dep[y];
		for(int i=0;(1<<i)<=t;++i) if(t&(1<<i)) x=pa[x][i];
		if(x==y) return x;
		for(int i=19;i>=0;--i)
			if(pa[x][i]^pa[y][i]) x=pa[x][i],y=pa[y][i];
		return pa[x][0];
	}
	inline int up(int x,int d){
		for(int i=0;(1<<i)<=d;++i) if(d&(1<<i)) x=pa[x][i];
		return x;
	}
	inline void dfs(int u,int f){
		dep[u]=dep[f]+1;fa[u]=pa[u][0]=f;
		for(int i=1;(1<<i)<=dep[u];++i) pa[u][i]=pa[pa[u][i-1]][i-1];
		for(int i=first[u];i;i=e[i].nxt){
			if(e[i].v==f) continue;
			int v=e[i].v;
			ans[v]=ans[u]+e[i].w;
			dfs(v,u);		
		}
	}
	inline void build(){
		ct=sum+n;ans[1]=Val(1,0);
		dfs(1,0);
	}
	inline int que(int x,int y){
		if(x==y) return 0;
		int lca=LCA(x,y);
		if(lca<=n) return (ans[x]+ans[y]-ans[lca]-ans[lca]).y;
		else{
			int px=up(x,dep[x]-dep[lca]-1),py=up(y,dep[y]-dep[lca]-1);
			return (ans[x]-ans[px]+ans[y]-ans[py]+D[lca-n].query(px,py)).y;
		}
	}
}
namespace graph{
	node e[M<<1];
	int first[N],cnt=1,dfn[N],low[N],stk[N],top,tot;
	int ed[N],sz;
	inline void add(int u,int v,int w){e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;}
	inline void tarjan(int u,int f){
		stk[++top]=u;
		dfn[u]=low[u]=++tot;
		for(int i=first[u];i;i=e[i].nxt){
			if(i==(f^1)) continue;
			int v=e[i].v;
			if(!dfn[v]){
				ed[++sz]=i;
				tarjan(v,i);
				if(low[v]>=dfn[u]){
					++sum;
					do{
						D[sum].ve.push_back(stk[top]);
						--top;
					}while(stk[top+1]!=v);
					D[sum].ve.push_back(u);
					do{
						D[sum].edg.push_back(e[ed[sz]]);
						--sz;
					}while(ed[sz+1]!=i);
					D[sum].init();
					for(int x:D[sum].ve){
						Val w=D[sum].query(x,u);
						Tree::add(sum+n,x,w);
						Tree::add(x,sum+n,w);
					}
				}
				low[u]=min(low[u],low[v]);
			}
			else{
				if(dfn[v]<dfn[u]) ed[++sz]=i,assert(sz<=1000000);
				low[u]=min(low[u],dfn[v]);
			}
		} 
	}
	inline void build(){
		for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,0);
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1,u,v,w;i<=m;++i){
		scanf("%d%d%d",&u,&v,&w);
		graph::add(u,v,w);graph::add(v,u,w);
	}
	graph::build();
	Tree::build();
	while(q--){
		int s,t;
		scanf("%d%d",&s,&t);
		printf("%d\n",Tree::que(s,t));
	}
	return 0;
}

D2T1 面条

首先假设操作过程中不进行除 \(2\),最后求答案时除去 \(2^k\) 即可。

\(t\)\(n\) 的最低位 \(1\) 的位置。应用 \(t+1\) 次操作后,最终会变成若干个长为 \(2^{t+1}\) 的连续相同段以及最后接一个长为 \(2^t\) 的段,接下来的操作不会改变它们的相等关系,可以将每段缩成一个数,得到序列 \(a_1,a_2,\dots a_m\)

那么一次操作会将其变成:

\[a_1+a_m,a_1+a_{m-1},a_2+a_{m-1},a_2+a_{m-2},\dots,a_{mid-1}+a_{mid},2a_{mid} \]

考虑差分,设初始差分为 \(d_1,d_2,\dots d_k\),那么每次操作会将其变成:

\[-d_{k},d_1,-d_{k-2},d_2,\dots,-d_{mid},d_{mid-1} \]

考虑在置换的过程中,维护序列 \(d_1,d_2,\dots,d_k,-d_1,-d_2,\dots,-d_k\),那么每次操作相当于对这个序列的一个置换。同时注意到由于总和不变,所求的 \(a_x\) 时刻以通过最终的 \(d\) 表示出来的,最终得到形如 \(a_x=\sum_{i=1}^{k}w_id_i\) 的形式,其中 \(w\) 可以容易预处理的。

将置换中的环拆出来,对于一个长为 \(len\) 的环,可以用一次卷积算出 \(\forall t\in[0,len)\),当 \(k\bmod len=t\) 时,环中的所有 \(d\) 对答案的贡献是多少。然后可以将相同长度的环叠加在一起,由于本质不同的环长不超过 \(\sqrt{n}\),就可以 \(\mathcal O(n\log n+q\sqrt{n})\) 完成了。

进一步转移到每次置换实际上是将 \(i\) 转移到 \(2i\bmod (n+1)\) 上,因此转移一定存在循环节 \(r\) 满足 \(r\)\(2\)\(\bmod n+1\) 下的乘法群的阶,因此所有环长的 \(lcm\) 即为 \(r\),可以直接计算模 \(r\) 意义下每个 \(k\) 的对应的答案,进而 \(\mathcal O(1)\) 回答询问。复杂度是 \(\mathcal O(n\log n+nd(n)+q)\) 的。

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef vector<int> vec; 
const int N=4e6+10,mod=998244353,iv=(mod+1)/2;
inline int ksm(int x,int y){
	int ret=1;
	for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
	return ret;
} 
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline void rec(int &x,int y){x=(x-y<0)?x-y+mod:x-y;}

struct LSP{
	int pw1[40010],pw2[40010];
	inline void init(int x){
		pw1[0]=pw2[0]=1; 
		for(int i=1;i<=32768;++i) pw1[i]=1ll*pw1[i-1]*x%mod;
		for(int i=1;i<=32768;++i) pw2[i]=1ll*pw2[i-1]*pw1[32768]%mod;
	}
	inline int query(ll x){
		x%=mod-1;
		return 1ll*pw1[x&32767]*pw2[x>>15]%mod;
	}
}T1,T2;
namespace Poly{
	struct poly{
		vec v;
		inline poly(int w=0):v(1){v[0]=w;}
		inline poly(const vec&w):v(w){}
			
		inline int operator [](int x)const{return x>=v.size()?0:v[x];}
		inline int& operator [](int x){if(x>=v.size()) v.resize(x+1);return v[x];}
		inline int size(){return v.size();}
		inline void resize(int x){v.resize(x);}
		
		inline poly slice(int len)const{
			if(len<=v.size()) return vec(v.begin(),v.begin()+len);
			vec ret(v);ret.resize(len);
			return ret;
		}
		inline poly operator *(const int &x)const{
			poly ret(v);
			for(int i=0;i<v.size();++i) ret[i]=1ll*ret[i]*x%mod; 
			return ret;
		}
		inline poly operator +(const poly &x)const{
			vec ret(max(v.size(),x.v.size()));
			for(int i=0;i<v.size();++i) ret[i]=add(v[i],x[i]);
			return ret;
		}
		inline poly operator -(const poly &x)const{
			vec ret(max(v.size(),x.v.size()));
			for(int i=0;i<v.size();++i) ret[i]=dec(v[i],x[i]);
			return ret;
		}
	};
	
	
	int Wn[N<<1],lg[N],r[N],tot;
	inline void init_poly(int n){
		int p=1;while(p<=n)p<<=1;
		for(int i=2;i<=p;++i) lg[i]=lg[i>>1]+1;
		for(int i=1;i<p;i<<=1){
			int wn=ksm(3,(mod-1)/(i<<1));
			Wn[++tot]=1;
			for(int j=1;j<i;++j) ++tot,Wn[tot]=1ll*Wn[tot-1]*wn%mod;
		}
	}
	inline void init_pos(int lim){
		int len=lg[lim]-1;
		for(int i=0;i<lim;++i) r[i]=(r[i>>1]>>1)|((i&1)<<len);
	}
	
	ull fr[N];
	const ull Mod=998244353;
	inline void NTT(int *f,int lim,int tp){
		for(int i=0;i<lim;++i) fr[i]=f[r[i]];
		for(int mid=1;mid<lim;mid<<=1){
			for(int len=mid<<1,l=0;l+len-1<lim;l+=len){
				for(int k=l;k<l+mid;++k){
					ull w1=fr[k],w2=fr[k+mid]*Wn[mid+k-l]%Mod;
					fr[k]=w1+w2;fr[k+mid]=w1+Mod-w2; 
				}
			}
		}
		for(int i=0;i<lim;++i) f[i]=fr[i]%Mod;
		if(!tp){
			reverse(f+1,f+lim);
			int iv=ksm(lim,mod-2);
			for(int i=0;i<lim;++i) f[i]=1ll*f[i]*iv%mod;
		}
	}
	inline poly to_poly(int *a,int n){
		poly ret;
		ret.resize(n);
		memcpy(ret.v.data(),a,n<<2);
		return ret;
	}
	
	namespace Basic{
		int f[N],g[N];
		inline poly mul(poly F,poly G,int n,int m){
			int rec=n+m-1,d=max(n,m);F.resize(n);G.resize(m);
			memcpy(f,F.v.data(),sizeof(int)*(n));
			memcpy(g,G.v.data(),sizeof(int)*(m));
			if(d<=150){
				poly ret;ret.resize(rec);
				for(int i=0;i<n;++i)
					for(int j=0;j<m;++j) inc(ret[i+j],1ll*f[i]*g[j]%mod);
				return ret;
			}
			int len=lg[rec],lim=1<<len+1;
			memset(f+n,0,sizeof(int)*(lim-n));
			memset(g+m,0,sizeof(int)*(lim-m));
			init_pos(lim);
			NTT(f,lim,1);NTT(g,lim,1);
			for(int i=0;i<lim;++i) f[i]=1ll*f[i]*g[i]%mod;
			NTT(f,lim,0);
			return to_poly(f,rec);
		}
	}
	using Basic::mul;
}
using namespace Poly;
int n,q,x,a[N],b[N],test,T;
ll k_max;
unsigned long long seed;
unsigned long long rd(unsigned long long &x) {
	x^=(x<<13);
	x^=(x>>7);
	x^=(x<<17);
	return x;
}

int sz,f[23][N],stk[N],m,bel[N],ivn,S,d[N],xs[N],to[N];
int vis[N],fin,who;
vector<int> cir;
vector<vector<int> > vcir;
vector<int> ve;
inline void init(){
	ivn=ksm(n,mod-2); 
	int lb=n&(-n);
	for(int i=1;i<=n;++i) f[0][i]=a[i];
	for(int i=1;(1<<i)<=(lb<<1);++i){
		for(int j=1;j<=n/2;++j) 
			f[i][j*2-1]=f[i][j*2]=1ll*iv*(f[i-1][j]+f[i-1][n-j+1])%mod;
		fin=i;
	}
	m=S=0;
	for(int l=1,r;l<=n;l=r+1){
		r=l;
		while(r<n&&f[fin][r+1]==f[fin][r]) ++r;
		stk[++m]=f[fin][r];
		for(int i=l;i<=r;++i) bel[i]=m,S=add(S,f[fin][i]);
		xs[m]=1ll*dec(0,n-r)*ivn%mod;
	}	
	m--;
	for(int i=1;i<=m;++i){
		d[i]=dec(stk[i+1],stk[i]),d[i+m]=dec(0,d[i]);
		xs[i+m]=0;
	}
	for(int i=1;i<bel[x];++i) inc(xs[i],1);
	for(int i=1,l=1,r=m;i<=m;++i){
		if(i&1){
			to[r+m]=i;
			to[r]=i+m;r--;
		}
		else{
			to[l]=i;
			to[l+m]=i+m;l++;
		}
	}
	for(int i=1;i<=(m<<1);++i) vis[i]=0;
	vcir.clear();who=0; 
	for(int i=1;i<=(m<<1);++i){
		if(vis[i]) continue;
		cir.clear();
		int now=i;
		while(!vis[now]){
			cir.push_back(now);vis[now]=1;
			now=to[now];
		}
		who=max(who,(int)cir.size());
		vcir.push_back(cir);
	}
	if(!who) return ;
	ve.clear();
	ve.resize(who);
	for(auto cir:vcir){
		int len=cir.size();
		poly f,g;
		f.resize(len<<1);g.resize(len);
		for(int j=0;j<len;++j)
			f[j]=f[j+len]=xs[cir[j]],g[j]=d[cir[len-1-j]]; 
		f=mul(f,g,f.size(),g.size());
		for(int j=0;j<who;++j) inc(ve[j],f[(j%len)+len-1]);
	}
}
inline int solve(ll k){
	if(k<=fin) return f[k][x];
	k-=fin;
	int ans=who?ve[k%who]:0;
	int ret=1ll*add(1ll*S*T1.query(k)%mod*ivn%mod,ans)*T2.query(k)%mod;
	return ret;
}
int main(){
	scanf("%d%d%llu",&test,&T,&seed);
	T1.init(2);T2.init(iv);
	init_poly(2000000);
	for(int Case=1;Case<=T;Case++){
		scanf("%d%d%d%lld",&n,&q,&x,&k_max);
		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
		init();
		ll ans=0;
		for(int i=1;i<=q;++i){
			ll k=rd(seed)%k_max;
			ans^=1ll*i*solve(k);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

D2T2 计算几何

神秘题,考虑取出被去掉的哪些坐标均为偶数的点,在它们之间连边,那么可以发现每个原本的点都可以对应一条边,点选不选就可以看作边选不选。

于是问题相当于要求被选择的边不能有公共点,因此可以看作是关于新形成图形上的所有点之间的一个最大匹配问题。这与洛谷P8114 完全一致,于是可以知道最大匹配为完美匹配,方案数就是 \(P8814\) 所求,边长就是\(\max(a+b-c,0),\max(a+c-b,0),\max(b+c-a,0)\),然后就可以用该题的方法完成了。

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353,N=3e6+10;
int a,b,c,fac[N],H[N],tp;
inline int ksm(int x,int y){
	int ret=1;
	for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
	return ret; 
}
inline void init(int n){
	if(!tp){
		tp=2;
		fac[0]=fac[1]=1;
		H[0]=H[1]=1;
	}
	while(tp<=n){
		fac[tp]=1ll*fac[tp-1]*tp%mod;
		H[tp]=1ll*H[tp-1]*fac[tp-1]%mod;
		++tp;
	}
}
inline int calc(int a,int b,int c){
	init(a+b+c);
	int fz=1ll*H[a]*H[b]%mod*H[c]%mod*H[a+b+c]%mod;
	int fm=1ll*H[a+b]*H[b+c]%mod*H[a+c]%mod;
	return 1ll*fz*ksm(fm,mod-2)%mod;
} 
int main(){
	int T;scanf("%d",&T);
	while(T--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		if(a+b<=c) c=min(c,a+b);
		if(a+c<=b) b=min(b,a+c);
		if(b+c<=a) a=min(a,b+c);
		printf("%lld %d\n",1ll*(a+b-c)*(a+c-b)+1ll*(a+b-c)*(b+c-a)+1ll*(a+c-b)*(b+c-a),calc(a+b-c,a+c-b,b+c-a));
	}
	return 0;
}

D2T3 深搜

考虑离散化所有权值,然后对于某一个权值 \(i\),计算从所有点出发向下走到某个点满足图中不遍历到任何 \(<i\) 的点的概率和,即可求出答案。固定权值 \(i\) 时这可以通过树形 \(dp\) \(\mathcal O(n)\) 完成。

考虑 \(i\gets i+1\) 带来的影响,这相当于标记某个点 \(u\),将它有白点变为黑点。这会导致以下影响:

  • \(u\) 的祖先 \(x\) 与子树中点 \(y\),将 \(p(x,y)\) 直接变为 \(0\)
  • 将子树中有黑点的点称为灰点。对于 \(p(x,y)\),在 \(x,y\) 路径上且不为 \(y\) 的点 \(t\) 都会对 \(p(x,y)\) 产生贡献:记 \(num\)\(t\) 儿子中灰点的数量,那么如果 \(y\)\(t\) 的某个灰儿子的子树中,会对 \(p(x,y)\) 产生 \(\frac{1}{num}\) 的贡献,否则会产生 \(\frac{1}{num+1}\) 的贡献。因此在每次修改点 \(u\) 时可以暴力遍历它的所有祖先变成灰点,可以发现原本不是灰点的一定是链,可以暴力进行修改。

对每个点记录 \(g(x)\) 表示从 \(x\) 出发 \(dfs\) 到子树中的任意一点且不经过黑点的概率之和,每次将某个点变灰时都同时更改它的所有祖先 \(g\)\(g\) 的转移可以写作矩阵形式,用全局平衡二叉树维护动态 \(dp\) 即可,复杂度 \(\mathcal O(n\log n)\)

代码,代码没有。

posted @ 2022-05-13 15:47  cjTQX  阅读(136)  评论(0编辑  收藏  举报