7 月做题记录

CF1728G Illumination

我们考虑容斥,设 \(f(S)\) 为恰好 \(S\) 内部的点没有点亮的方案数,\(g(S)\) 为钦定其不点亮的方案数,则我们可以知道:

\[g(S)=\sum_{S\subset T}f(T)\\ f(S)=\sum_{S\subset T}(-1)^{|T|-|S|}g(T) \]

然后我们考虑不带修改的时候怎么求 \(g(T)\),我们发现一个 \(T\) 将序列划分成了 \(|T|+1\) 个区间,我们可以预处理出来 \(val(l,r)\) 表示 \([p_l,p_r]\) 区间内部不覆盖 \(p_l,p_r\) 的方案数,然后 \(g(T)=\prod_{i=1}^{|T|-1}val(T_i,T_{i+1})\) ,然后我们可以预处理出来。

我们先进行一次 \(O(2^mm)\) 的容斥,我们增加一个点的时候,对于每个 \(val(l,r)\) 的贡献是一个倍数,然后可以 \(O(m^2)\) 计算其带来的贡献。

更好的预处理方法是我们发现这个容斥每项贡献系数来自若干个区间,我们可以 dp 该容斥系数。

Code
// Problem: Illumination
// URL: https://www.luogu.com.cn/problem/CF1728G
// Memory Limit: 500 MB
// Time Limit: 8000 ms
// Author: Nityacke
// Time: 2024-07-09 11:35:18

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2e5+5,M=19,H=998244353;
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%H)
		if(b&1) ans=1ll*ans*a%H;
	return ans;
}
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline int dec(int x,int v){return x<v?x+H-v:x-v;}
inline int mul(int a,int b){return 1ll*a*b%H;}
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
inline void del(int &x,int v){if((x-=v)<0) x+=H;}
inline void Mul(int &a,int b){a=mul(a,b);}
int n,m,d,q,a[N],p[M],ans,g[M][M],Sg[M][M];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>d>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=m;++i) cin>>p[i];
	sort(a+1,a+n+1),sort(p+1,p+m+1),p[0]=-1e9,p[m+1]=1e9;
	for(int i=0;i<=m+1;++i)
		for(int j=0;j<=m+1;++j) g[i][j]=1;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=m+1;++j)
			for(int k=j;k<=m+1;++k)
				if(p[j]<=a[i]&&a[i]<=p[k]) Mul(g[j][k],min({d+1,a[i]-p[j],p[k]-a[i]}));
	int ans=0;
	for(int S=0;S<(1<<m);++S){
		vector<int>vec;vec.pb(0);
		int v=1;
		for(int i=0;i<m;++i)
			if(S>>i&1) vec.pb(i+1),v=H-v;
		vec.pb(m+1);
		int sz=(int)vec.size()-1;
		for(int i=0;i<sz;++i) Mul(v,g[vec[i]][vec[i+1]]);
		for(int i=0;i<sz;++i)
			add(Sg[vec[i]][vec[i+1]],mul(v,qp(g[vec[i]][vec[i+1]])));
		add(ans,v);
	}
	cin>>q;
	for(int x,res,i=1;i<=q;++i){
		cin>>x,res=ans;
		for(int j=0;j<=m+1;++j)
			for(int k=j;k<=m+1;++k)
				if(p[j]<=x&&x<=p[k]){
					int v=mul(g[j][k],min({x-p[j],p[k]-x,d+1}));
					add(res,mul(dec(v,g[j][k]),Sg[j][k]));
				}
		cout<<res<<"\n";
	}
}

CF1119H Triple

太厉害了。

首先我们考虑将三元组推广到一般情况:

给定 \(d_i(0\le i<m)\) 以及 \(a_{i,j}(1\le i\le n,0\le j<m)\),令集合幂级数 \(f_i(x)=\sum_{j=0}^{m-1}d_jx^{a_{i,j}}\),然后求 \(f_i\) 的异或卷积 \(g=\prod_{i=1}^nf_i\)

我们令 \(G=\hat g,F_i=\hat f_i\),则我们知道 \(G=\prod_{i=1}^nF_i\),这里的乘法表示对应位置相乘,然后我们考虑 \([x^p]F_i\),由于 FWT 定义我们知道其为 \(\sum_{j=0}^{m-1}(-1)^{|p\cap a_{i,j}|}d_j\),如果我们能得到 \(G\) 的每一位,就能 IFWT 得到 \(g\)

以下假设我们在求解 \([x^p]G\)

我们考虑将 \(d_j\) 的系数看成一个 \(m\) 位二进制数,如果第 \(j\) 位为 \(1\),表示这一位的系数为 \(-1\),设 \(c_0,c_1\ldots c_{2^m-1}\) 为对应组合分别的出现次数,那么我们如果能知道每一个 \(c\),我们就可以 \(O(2^m(m+\log {n2^m}))\) 的时间复杂度内计算出 \([x^p] G\)

求解这些未知数 \(c\) 我们需要 \(2^m\) 个线性无关方程,一个人类智慧的事情是我们可以对于每一个 \(T=0,1\ldots 2^m-1\)\(z_{i,T}=\oplus_{j\in T}a_{i,j}\) 代入集合幂级数,我们考虑集合幂级数 \(h_T=\sum_{i=1}^nx^{z_{i,T}},H_T=\hat h_T\)

然后我们考虑对于一个 \(k\)\(c_k\)\(H_T\) 对于 \([x^p]\) 的贡献,首先由于 FWT 的线性性,所以我们得到 \([x^p]H_T=\sum_{i=1}^n(-1)^{|p\cap z_{i,j}|}\)

然后由于 \(i\cap (j \oplus k)=(i\cap j)\oplus (i\cap k)\),所以我们知道 \((-1)^{|p\cap z_{i,T}|}=\prod_{j\in T}(-1)^{|p\cap a_{i,j}|}\)

因为对于 \(c_k\),仅有 \(k\) 的第 \(j\) 位为 \(1\)\(j\in T\) 的时候,才有 \(-1\) 的贡献,所以 \(\sum_{i=1}^n(-1)^{|p\cap z_{i,T}|}=\sum_{k=0}^{2^m-1}(-1)^{|k\cap T|}c_k\)

然后我们发现这个就是 \(c\) 做了 FWT 的 \([x^T]\) 的值,所以我们只需要对于每个 \(h_T\) 做 FWT,然后在提出 \([x^p]\) 项各处的值,拉出来 IFWT,就能得到 \([x^p]\) 中每一个 \(c_k\) 的值,然后就可以做了。

时间复杂度 \(O(nm2^m)\)

Code
// Problem: Triple
// URL: https://www.luogu.com.cn/problem/CF1119H
// Memory Limit: 250 MB
// Time Limit: 1500 ms
// Author: Nityacke
// Time: 2024-07-08 20:48:37

#include<bits/stdc++.h>
#define poly vector<int>
#define int long long
using namespace std;
const int N=1<<17,K=3,H=998244353;
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
int n,m,k,tot,c[K],a[N][K];
poly h[1<<K],val,ans;
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline int dec(int x,int v){return x<v?x+H-v:x-v;}
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
inline void del(int &x,int v){if((x-=v)<0) x+=H;}
inline void FWT(poly &a,int L,int v){
	for(int len=2,k=1;len<=L;len<<=1,k<<=1)
		for(int i=0;i<L;i+=len)
			for(int j=0,t1,t2;j<k;++j)
				t1=a[i+j],t2=a[i+j+k],a[i+j]=adc(t1,t2),a[i+j+k]=dec(t1,t2);
	if(v!=1) for(int i=0;i<L;i++) a[i]=1ll*a[i]*v%H;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,k=3,tot=1<<m,val.resize(1<<k),ans.resize(tot);
	for(int i=0;i<k;++i) cin>>c[i],c[i]%=H;
	for(int i=0;i<(1<<k);++i) h[i].resize(tot);
	for(int i=1;i<=n;++i){
		for(int j=0;j<k;++j) cin>>a[i][j];
		for(int S=1;S<(1<<k);++S)
			for(int j=0;j<k;++j)
				if(S>>j&1) val[S]^=a[i][j];
		for(int j=0;j<(1<<k);++j) ++h[j][val[j]],val[j]=0;
	}
	for(int i=0;i<(1<<k);++i) FWT(h[i],tot,1);
	for(int i=0;i<tot;++i){
		for(int j=0;j<(1<<k);++j) val[j]=h[j][i];
		FWT(val,1<<k,qp((H+1)>>1,k)),ans[i]=1;
		for(int S=0;S<(1<<k);++S){
			int sum=0;
			for(int j=0;j<k;++j)
				if(S>>j&1) del(sum,c[j]);
				else add(sum,c[j]);
			ans[i]=1ll*ans[i]*qp(sum,val[S])%H;
		}
	}
	FWT(ans,tot,qp((H+1)/2,m));
	for(int i=0;i<tot;++i) cout<<ans[i]<<" ";
}

CF1383E Strange Operation

我们考虑这个操作的影响,发现只有 \(11\) 是删除一个 \(1\),否则都是删除一个 \(0\),然后我们构造另外一个序列 \(b_1,b_2,\ldots b_m\) 分别代表相邻的两个 \(1\) 之间有多少个 \(0\),容易发现 \(b\) 数列和原字符串构成双射。

然后我们发现我们可以令一个 \(b_i>0\) 减去 \(1\),或者删去一个不在开头结尾的 \(0\),然后我们如何判定一个 \(b'\) 可以被 \(b\) 生成,我们考虑维护当前匹配到的位置,和我们对于一个新的 \(b'\) 找到第一个不小于他的数匹配。

然后我们考虑 dp,计算 \(f_i\) 表示匹配到 \(i\) 结尾的方案数,然后容斥系数可以用单调栈拆一下,顺便用前缀和优化,时间复杂度 \(O(n)\)

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,H=1e9+7;
char S[N];
int n,L,R,ans,top,pos[N],f[N],st[N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>(S+1),n=strlen(S+1),L=1,R=n;
	while(L<=n&&S[L]=='0') ++L;
	while(R&&S[R]=='0') --R;
	if(L>R) return cout<<n<<"\n",0;
	st[0]=1e9,pos[0]=f[1]=ans=1;
	for(int i=L,cnt=2;i<R;++cnt){
		int p=i+1,L=1;
		while(S[p]=='0') ++p,++L;
		int res=0,mx=0,lst=cnt-1;
		while(L>st[top]){
			res=(res+(f[lst]-f[pos[top]-1]+H)*(L-mx))%H;
			mx=st[top],lst=pos[top--]-1;
		}
		res=(res+(f[lst]-f[pos[top]-1]+H)*(L-mx))%H,ans=(ans+res)%H;
		f[cnt]=(res+f[cnt-1])%H,st[++top]=L,pos[top]=cnt,i=p;
	} 
	cout<<ans*L%H*(n-R+1)%H<<"\n";
}

CF1799G Count Voting

考虑二项式反演,然后我们设 \(g(m)\) 表示钦定往自己组投了 \(m\) 票,一共 \(k\) 个组,设每一组有 \(s_i\) 个人,一个人需要 \(a_i\) 张票, 然后随便推一下式子:

\[g(m)=\sum_{\sum d=m}\binom {m}{d_1,d_2\ldots d_k}\prod_{i=1}^k\sum_{\sum c=d_i}\binom {s}d\binom {d}{c_1,c_2,\ldots c_p}\binom {n-m}{a_1-c_1,a_2-c_2\ldots a_n-c_n} \]

应该没推错吧(),然后里面的系数你拆拆就是了(),时间复杂度 \(O(n^2)\)

Code
// Problem: Count Voting
// URL: https://www.luogu.com.cn/problem/CF1799G
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author: Nityacke
// Time: 2024-07-09 18:39:15

#include<bits/stdc++.h>
#define pb push_back
#define int long long
using namespace std;
const int N=205,H=998244353;
int n,m,c[N],t[N],b[N],fac[N],ifac[N],inv[N],len[N];
vector<int>G[N];
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline int dec(int x,int v){return x<v?x+H-v:x-v;}
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
inline void del(int &x,int v){if((x-=v)<0) x+=H;}
inline int C(int n,int m){return fac[n]*ifac[m]%H*ifac[n-m]%H;}
int f[N][N],g[N][N];
inline void init(int id){
	g[0][0]=1;
	for(int i=1;i<=len[id];i++)
		for(int j=0;j<=len[id];j++){
			g[i][j]=0;
			for(int k=0;k<=j&&k<=G[id][i];k++)
				add(g[i][j],g[i-1][j-k]*ifac[G[id][i]-k]%H*ifac[k]%H);
		}
	for(int j=0;j<=len[id];++j) g[len[id]][j]=g[len[id]][j]*fac[j]%H*C(len[id],j)%H;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>c[i];
	for(int i=1;i<=n;++i) cin>>t[i],b[i]=t[i];
	fac[0]=fac[1]=ifac[0]=ifac[1]=inv[1]=1,sort(b+1,b+n+1),m=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=m;++i) G[i].pb(0);
	for(int i=1;i<=n;++i)
		t[i]=lower_bound(b+1,b+m+1,t[i])-b,++len[t[i]],G[t[i]].pb(c[i]);
	for(int i=2;i<=n;i++)
		fac[i]=fac[i-1]*i%H,
		inv[i]=(H-H/i)*inv[H%i]%H,
		ifac[i]=ifac[i-1]*inv[i]%H;
	f[0][0]=1;
	for(int i=1;i<=m;++i){
		init(i);
		for(int j=0;j<=n;++j)
			for(int k=0;k<=min(j,len[i]);++k)
				add(f[i][j],f[i-1][j-k]*g[len[i]][k]%H);
	}
	int ans=0;
	for(int i=0;i<=n;++i)
		if(i&1) del(ans,f[m][i]*fac[n-i]%H);
		else add(ans,f[m][i]*fac[n-i]%H);
	cout<<ans<<"\n";
}

P10591 BZOJ4671 异或图

我们考虑斯特林反演,设 \(f(i)\) 表示钦定 \(i\) 个连通块的方案数,\(g(i)\) 为恰好 \(i\) 个的方案数,则我们知道:

\[f(m)=\sum_{i=m}^n{i\brace m}g(i)\\ g(m)=\sum_{i=m}^n(-1)^{i-m}{i\brack m}f(i) \]

然后我们考虑如何计算 \(f(i)\),我们考虑枚举每个点会被分到那个连通块,不同连通块之间不能连边,但是我们没有保证同一连通块之间一定有边,所以这是钦定,然后我们发现枚举完之后如何计算方案数,就是形如一个类似于一些边的状态异或为 \(0\),可以线性基解决,然后枚举的总方案数是 \(O(\text{bell}(n))\) 的,可以接受。

Code


​ #include<bits/stdc++.h>
​ #define int long long
​ using namespace std;
​ const int N=205;
​ char st[N];
​ bool w[65][15][15];
​ int n,s,f[15],c[15];
​ inline void add(int &x,int v){x+=v;}
​ inline void del(int &x,int v){x-=v;}
​ inline int fun(int x){return !x?1:xfun(x-1);}
​ struct LB{
​ int p[65],cnt;
​ inline void clear(){memset(p,0,sizeof p),cnt=0;}
​ inline void ins(int val){
​ for(int i=59;~i;--i)
​ if(val>>i&1){
​ if(!p[i]) return p[i]=val,++cnt,void();
​ else val^=p[i];
​ }
​ }
​ }B;
​ inline void dfs(int x,int mx){
​ if(x>n){
​ B.clear();
​ for(int p=1;p<=n;++p)
​ for(int q=p+1;q<=n;++q)
​ if(c[p]!=c[q]){
​ int val=0;
​ for(int i=1;i<=s;++i)
​ if(w[i][p][q]) val|=1ll<<(i-1);
​ B.ins(val);
​ }
​ add(f[mx],(1ll<<(s-B.cnt)));
​ return;
​ }
​ for(int i=1;i<=mx+1;++i) c[x]=i,dfs(x+1,max(mx,i));
​ }
​ signed main(){
​ ios::sync_with_stdio(false);
​ cin.tie(0),cout.tie(0);
​ cin>>s;
​ for(int i=1,now;i<=s;++i){
​ cin>>st,now=0;
​ if(i==1){
​ for(int j=2;j<=10;++j)
​ if(j
(j-1)/2==(int)strlen(st)) n=j;
​ }
​ for(int p=1;p<=n;++p)
​ for(int q=p+1;q<=n;++q) w[i][p][q]=w[i][q][p]=st[now++]-'0';
​ }
​ dfs(1,0);
​ int ans=0;
​ for(int i=1;i<=n;++i)
​ if(i&1) add(ans,f[i]fun(i-1));
​ else del(ans,f[i]
fun(i-1));
​ cout<<ans<<"\n";
​ }

P7360 「JZOI-1」红包

考虑 min-max 容斥,我们发现:

\[\operatorname{lcm}(T)=\prod_{S\subset T}\gcd(S)^{(-1)^{|S|+1}} \]

我们设

\[\begin{aligned} F(n,K)&=\prod_{i_1=1}^n\ldots\prod_{i_K=1}^n\gcd(i_1,\ldots i_K)\\ &=\prod_{k=1}^nk^{\sum_{i_1=1}^n\ldots \sum_{i_k=1}^n[\gcd(i_1,i_2\ldots i_K)=k]}\\ &=\prod_{k=1}^{n}k^{\sum_{d=1}^{\frac n k}\mu(d)(\frac n {kd})^K}\\ &=\prod_{T=1}^n(\prod_{d|T}d^{\mu(\frac T d)})^{(\frac nT)^K} \end{aligned} \]

然后我们考虑代入原答案,枚举 \(|S|\) 大小:

\[\begin{aligned} ans&=\prod_{i=1}^KF(n,i)^{(-1)^{i+1}\binom K in^{K-i}}\\ ans&=\prod_{i=1}^K\prod_{T=1}^n(\prod_{d|T}d^{\mu(\frac T d)})^{(\frac n T)^i(-1)^{i+1}\binom K in^{K-i}} \\ ans&=\prod_{T=1}^n(\prod_{d|T}d^{\mu(\frac T d)})^{\sum_{i=1}^K(\frac n T)^i(-1)^{i+1}\binom K in^{K-i}} \\ ans&=\prod_{T=1}^n(\prod_{d|T}d^{\mu(\frac T d)})^{-\sum_{i=1}^K(-\frac n T)^i\binom K in^{K-i}} \\ ans&=\prod_{T=1}^n(\prod_{d|T}d^{\mu(\frac T d)})^{n^K-(n-\frac n T)^K} \\ \end{aligned} \]

然后可以整除分块做到 \(O(n\log n+T\sqrt n\log n)\)

Code

// Problem: P7360 「JZOI-1」红包
// URL: https://www.luogu.com.cn/problem/P7360
// Memory Limit: 128 MB
// Time Limit: 2500 ms
// Author: Nityacke
// Time: 2024-07-09 20:51:22

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int H=998244353,pH=H-1,ppH=402653184,N=1e6+5;
int T,m,n,mu[N],val[N],ival[N],inv[N];
inline int qp(int a,int b=H-2,int HH=H){
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%HH)
		if(b&1) ans=1ll*ans*a%HH;
	return ans;
}
inline int read(){
	int x=0,fl=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)){
		x=x*10+c-'0',c=getchar();
		if(x>=ppH) fl=1,x%=ppH;
	}
	return x+ppH*fl;
}
inline void init(){
	mu[1]=1,ival[0]=val[0]=1;
	for(int i=1;i<N;++i) val[i]=1,inv[i]=qp(i);
	for(int i=1;i<N;++i)
		for(int j=i<<1;j<N;j+=i) mu[j]-=mu[i];
	for(int d=1;d<N;++d)
		for(int T=d;T<N;T+=d)
			if(mu[T/d]==1) val[T]=val[T]*d%H;
			else if(mu[T/d]==-1) val[T]=val[T]*inv[d]%H;
	for(int i=1;i<N;++i) val[i]=val[i-1]*val[i]%H,ival[i]=qp(val[i]);
}
inline void solve(){
	int ans=1;
	m=read(),n=read();
	for(int l=1,V,r=l;l<=m;l=r+1){
		r=m/(m/l),V=val[r]*ival[l-1]%H;
		ans=ans*qp(V,(qp(m,n,pH)-qp(m-m/l,n,pH)+pH))%H;
	}
	cout<<ans<<"\n";
}
signed main(){
	T=read(),init();
	while(T--) solve();
}

[ARC138D] Differ by K bits

你可能需要先知道格雷码。

然后你发现如果 \(P_i,P_{i+1}\) 相差 \(K\) 位,说明其异或有 \(K\) 位,然后我们把 \(popcount=K\) 的都拿出来,由于我们需要 \(P_i\) 不同,所以我们需要异或数组不能组成 \(0\),这一步可以插进线性基,然后对于如何构造,我们发现格雷码每次只修改一个位,所以直接用格雷码构造即可。

Code
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=20;
int n,k,p[N];
vector<int>vec;
inline void ins(int v){
    for(int _v=v,i=n-1;~i;--i)
        if(v>>i&1){
            if(!p[i]) return p[i]=v,vec.pb(_v),void();
            else v^=p[i];
        }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>k;
    for(int i=0;i<(1<<n);++i)
        if(__builtin_popcount(i)==k) ins(i);
    if((int)vec.size()<n) return cout<<"No\n",0;
    cout<<"Yes\n";
    for(int i=0;i<(1<<n);++i){
        int p=i^(i>>1),v=0;
        for(int j=0;j<n;++j)
            if(p>>j&1) v^=vec[j];
        cout<<v<<" ";
    }
}

CF1615F LEGOndary Grandmaster

trick1:把奇数位取反,操作转化成交换两个相邻数。

trick2:答案是 \(\sum|a_i-b_i|\)

然后枚举贡献计算即可。

Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5,H=1e9+7;
int _suf[N][N<<1],_pre[N][N<<1],*suf[N],*pre[N];
int T,n;
char a[N],b[N];
inline bool equal(char a,int b){return a=='?'||a==b+'0';}
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
inline void solve(){
    cin>>n>>(a+1)>>(b+1);
    for(int i=0;i<=n+1;++i)
        for(int j=-n;j<=n;++j) pre[i][j]=suf[i][j]=0;
    for(int i=1;i<=n;i+=2){
        if(a[i]!='?') a[i]='1'+'0'-a[i];
        if(b[i]!='?') b[i]='1'+'0'-b[i];
    }
    pre[0][0]=suf[n+1][0]=1;
    for(int i=0;i<n;++i)
        for(int j=-n;j<=n;++j)
            for(int p=0;p<2;++p)
                for(int q=0;q<2;++q)
                    if(equal(a[i+1],p)&&equal(b[i+1],q)) add(pre[i+1][j+p-q],pre[i][j]);
    for(int i=n+1;i>1;--i)
        for(int j=-n;j<=n;++j)
            for(int p=0;p<2;++p)
                for(int q=0;q<2;++q)
                    if(equal(a[i-1],p)&&equal(b[i-1],q)) add(suf[i-1][j+p-q],suf[i][j]);
    int ans=0;
    for(int i=1;i<=n;++i)
        for(int j=-n;j<=n;++j)
        add(ans,1ll*pre[i][j]*suf[i+1][-j]%H*abs(j)%H);
    cout<<ans<<'\n';
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>T;
    for(int i=0;i<N;++i) suf[i]=_suf[i]+N,pre[i]=_pre[i]+N;
    while(T--) solve();
}

CF1870G MEXanization

联考考过类似套路的 dp,但是现在都还没去补,谔谔。

我们发现除了 \(ans_1\) 之外答案单调,然后我们可以对询问不断判断能否增大,由于均摊,我们只需要判断 \(O(n)\) 次。

考虑设最后的 \(f(S)\)\(v\),则大于等于 \(v\) 的数都可以当成 \(0\) 看待。

然后我们从大到小考虑,维护一个 \(need\) 表示接下来的数还需要多少个,初始为 \(0\),只是对于 \(v\)\(1\),可以特判,假设现在我们判断到了 \(x\)\(x\) 个数为 \(cnt\)

  • 假设 \(cnt<need\) ,那么 \([0,x-1]\) 都要多出 \(need-cnt\) 个,所以我们需要多出 \(x\) 个数,由于 \(1+2+\ldots +O(\sqrt n)=O(n)\),所以这种情况只会发生 \(O(\sqrt n)\) 次,然后我们让 \(need+=need-cnt\)
  • 否则我们发现多出来的 \(x\) 只能当作 \(0\)\(cnt0+=cnt-need\)

最后比较 \(cnt0\)\(need\) 的个数即可。

Code
#include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+5;
    int T,n,a[N],ans[N],mn[N<<2],s[N<<2];
    int need,cnt,cur,Now,c0;
    #define k1 (k<<1)
    #define k2 (k<<1|1)
    #define mid ((l+r)>>1)
    inline void change(int x,int k=1,int l=1,int r=n){
        s[k]++;
        if(l==r) return mn[k]++,void();
        x<=mid?change(x,k1,l,mid):change(x,k2,mid+1,r);
        mn[k]=min(mn[k1],mn[k2]);
    }
    inline int ask(int L,int R,int k=1,int l=1,int r=n){
        if(L>R) return 0;
        if(L<=l&&R>=r) return s[k];
        if(R<=mid) return ask(L,R,k1,l,mid);
        if(L>mid) return ask(L,R,k2,mid+1,r);
        return ask(L,R,k1,l,mid)+ask(L,R,k2,mid+1,r);
    }
    inline void solve(int L,int R,int k=1,int l=1,int r=n){
        if(L<=l&&R>=r){
            if(need>cur/r) return need=n*2,void();
            if(mn[k]>=need) return cnt+=s[k]-need*(r-l+1),void();
            if(l==r) return need+=need-mn[k],void();
        }
        if(R>mid) solve(L,R,k2,mid+1,r);
        if(L<=mid) solve(L,R,k1,l,mid);
    }
    inline bool check(int x){
        need=1,cnt=c0+ask(x,n);
        if(x>1) solve(1,x-1);
        return cnt>=need;
    }
    inline void solve(){
        cin>>n;
        for(int i=1;i<=n;++i) cin>>a[i];
        int a1=a[1];
        for(int i=1;i<=n;++i) if(a[i]>n) a[i]=0;
        for(int i=0;i<=(n<<2);++i) mn[i]=s[i]=0;
        for(cur=1,Now=c0=0;cur<=n;++cur){
            if(a[cur]) change(a[cur]);
            else ++c0;
            while(check(Now+1)) ++Now;
            ans[cur]=Now;
        }
        ans[1]=max(ans[1],a1);
        for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
        cout<<'\n';
    }
    signed main(){
        ios::sync_with_stdio(false);
        cin.tie(0),cout.tie(0);
        cin>>T;
        while(T--) solve();
    }

P8990 [北大集训 2021] 小明的树

我们设点亮为白色,未点亮为黑色。

则我们发现白色形成了若干了子树,但是不好维护,但是我们发现黑色构成了一个跟 \(1\) 连通的连通块,判断是否只有一个连通块可以用 \(|V|-|E|=1\) 判断,然后权值为黑白边的个数,这个可以你用线段树轻松维护。

Code
// Problem: P8990 [北大集训 2021] 小明的树
// URL: https://www.luogu.com.cn/problem/P8990
// Memory Limit: 512 MB
// Time Limit: 3000 ms
// Author: Nityacke
// Time: 2024-07-10 15:40:55

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+5;
int n,m,a[N],b[N],u[N],v[N],pos[N];
struct tag{
    int a,b;
    inline tag operator+(const tag &x)const{return {a+x.a,b+x.b};}
}val[N<<2];
struct dat{
    int mn,cnt;
    ll ans;
    inline dat operator+(const dat &x)const{
        if(mn<x.mn) return *this;
        if(x.mn<mn) return x;
        return {mn,cnt+x.cnt,ans+x.ans};
    }
    inline dat operator+(const tag &x)const{
        return {mn+x.a,cnt,ans+1ll*x.b*cnt};
    }
}t[N<<2];
#define k1 (k<<1)
#define k2 (k<<1|1)
#define mid ((l+r)>>1)
inline void up(int k){t[k]=t[k1]+t[k2];}
inline void add(int k,tag v){t[k]=t[k]+v,val[k]=val[k]+v;}
inline void push(int k){if(val[k].a||val[k].b) add(k1,val[k]),add(k2,val[k]),val[k]={0,0};}
inline void change(int L,int R,tag v,int k=1,int l=1,int r=n-1){
    if(L<=l&&R>=r) return add(k,v);
    push(k);
    if(L<=mid) change(L,R,v,k1,l,mid);
    if(R>mid) change(L,R,v,k2,mid+1,r);
    up(k);
}
inline void build(int k=1,int l=1,int r=n-1){
    if(l==r) return t[k]={1-l,1,0},void();
    build(k1,l,mid),build(k2,mid+1,r),up(k);
}
inline void modify(int u,int v,int V){
    int st=min(pos[u],pos[v]),ed=max(pos[u],pos[v]);
    change(st,n-1,{V,0}),change(st,ed-1,{0,V});
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m,pos[1]=n;
    for(int i=1;i<n;++i) cin>>u[i]>>v[i];
    for(int i=1;i<n;++i) cin>>a[i],pos[a[i]]=i;
    build();
    for(int i=1;i<n;++i) modify(u[i],v[i],1);
    if(t[1].mn==1) cout<<t[1].ans<<"\n";
    else cout<<"0\n";
    for(int a,b,c,d,i=1;i<=m;++i)	{
        cin>>a>>b>>c>>d,modify(a,b,-1),modify(c,d,1);
        if(t[1].mn==1) cout<<t[1].ans<<"\n";
        else cout<<"0\n";
    }
}

CF1656H Equal LCM Subsets

我们考虑不断找必要条件,如果 \(A\) 中存在一个 \(a\),使得对于某个 \(p\),满足:

\[v_p(a)>\max_{b\in B}v_p(b) \]

那么 \(a\) 就不能存在,然后 \(a\) 删除之后可能有一些 \(B\) 里面的数不能满足这个要求,又要把 \(B\) 中不满足条件的数删除……

如果我们能快速找到要删除的数,那么这个题就可以做了,我们把上面的条件变成:

\[\gcd_{b\in B}(\frac {a}{\gcd(a,b)})>1 \]

然后我们可以对于每一个 \(a\) 建立一颗线段树,删除 \(a\) 的时候在 \(B\) 里每个元素的线段树上修改,一旦 \(\gcd\)\(1\) 就放进删除的队列,最后判断 \(A,B\) 是否都不为空即可。

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

Code
// Problem: Equal LCM Subsets
// URL: https://www.luogu.com.cn/problem/CF1656H
// Memory Limit: 500 MB
// Time Limit: 10000 ms
// Author: Nityacke
// Time: 2024-07-10 18:41:51

#include<bits/stdc++.h>
#define Int __int128
using namespace std;
const int N=1e3+5;
int T,n,m,del[2][N],sz[2],c[2];
Int a[2][N];
inline Int gcd(Int a,Int b){return b?gcd(b,a%b):a;}
inline Int read(){
    Int x=0;char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
    return x;
}
inline void write(Int x){
    static int st[50],top=0;
    do{st[++top]=x%10,x/=10;}while(x);
    while(top) putchar(st[top--]+'0');
    putchar(' ');
}
#define k1 (k<<1)
#define k2 (k<<1|1)
#define mid ((l+r)>>1)
struct ST{
    Int val[N<<2];
    inline void up(int k){val[k]=gcd(val[k1],val[k2]);}
    inline void build(Int *A,Int v,int k,int l,int r){
        if(l==r) return val[k]=v/gcd(A[l],v),void();
        build(A,v,k1,l,mid),build(A,v,k2,mid+1,r),up(k);
    }
    inline void change(int x,int k,int l,int r){
        if(l==r) return val[k]=0,void();
        if(x<=mid) change(x,k1,l,mid);
        else change(x,k2,mid+1,r);
        up(k);
    }
    inline Int calc(){return val[1];}
}t[2][N];
struct node{int op,id;};
inline void solve(){
    n=read(),m=read(),c[0]=c[1]=0,sz[0]=n,sz[1]=m;
    for(int k=0;k<2;++k)
        for(int i=1;i<=sz[k];++i) a[k][i]=read(),del[k][i]=0;
    queue<node>Q;
    for(int k=0;k<2;++k)
        for(int i=1;i<=sz[k];++i){
            t[k][i].build(a[k^1],a[k][i],1,1,sz[k^1]);
            if(t[k][i].calc()>1) Q.push({k,i}),++c[k],del[k][i]=1;
        }
    while(Q.size()){
        node x=Q.front();Q.pop();
        for(int i=1;i<=sz[x.op^1];++i)
            if(!del[x.op^1][i]){
                t[x.op^1][i].change(x.id,1,1,sz[x.op]);
                if(t[x.op^1][i].calc()>1) Q.push({x.op^1,i}),++c[x.op^1],del[x.op^1][i]=1;
            }
    }
    if(c[0]==sz[0]||c[1]==sz[1]) return puts("NO"),void();
    puts("YES"),write(sz[0]-c[0]),write(sz[1]-c[1]),puts("");
    for(int i=1;i<=sz[0];++i) if(!del[0][i]) write(a[0][i]);
    puts("");
    for(int i=1;i<=sz[1];++i) if(!del[1][i]) write(a[1][i]);
    puts("");
}
signed main(){
    T=read();
    while(T--) solve();
}

CF1651F Tower Defense

傻逼题,你随便颜色段均摊一下就完了。

注意长度为 \(1\) 的段可能有初值,偷着乐吧。

好像这是一个联考题才写的,谔谔。

Code
// Problem: Tower Defense
// URL: https://www.luogu.com.cn/problem/CF1651F
// Memory Limit: 500 MB
// Time Limit: 4000 ms
// Author: Nityacke
// Time: 2024-07-10 19:17:11

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N=2e5+5,M=N*24;
struct tree{
    int lc,rc;
    ll a,b;
}t[M];
int n,q,c[N],r[N],rt[N],tot;
int nc[N],tim[N],st[N],top;
ll Sum,Now,ans;
vector<int>G[N];
#define k1 t[k].lc
#define k2 t[k].rc
#define mid ((l+r)>>1)
inline void up(int k){t[k].a=t[k1].a+t[k2].a,t[k].b=t[k1].b+t[k2].b;}
inline void build(int &k,int l=1,int r=n){
    k=++tot;
    if(l==r) return t[k].a=::r[l],void();
    build(k1,l,mid),build(k2,mid+1,r),up(k);
}
inline void ins(int x,int &k,int l=1,int r=n){
    t[++tot]=t[k],k=tot;
    if(l==r) return t[k].a=0,t[k].b=c[l],void();
    if(x<=mid) ins(x,k1,l,mid);
    else ins(x,k2,mid+1,r);
    up(k);
}
inline void qry(int L,int R,int k,int l=1,int r=n){
    if(L<=l&&R>=r) return Sum+=Now*t[k].a+t[k].b,void();
    if(L<=mid) qry(L,R,k1,l,mid);
    if(R>mid) qry(L,R,k2,mid+1,r);
}
inline int find(int L,int R,ll &S,int k,int l=1,int r=n){
    if(L<=l&&R>=r){
        Sum=Now*t[k].a+t[k].b;
        if(S>=Sum||(S==Sum&&l!=r)) return S-=Sum,-1;
        if(l==r) return l;
    }
    int res=-1;
    if(L<=mid) res=find(L,R,S,k1,l,mid);
    if(~res) return res;
    return find(L,R,S,k2,mid+1,r);
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>c[i]>>r[i];
        if(c[i]/r[i]+1<N) G[c[i]/r[i]+1].pb(i);
    }
    cin>>q,build(rt[0]),st[top=0]=n+1;
    for(int i=1;(i<N)&&(rt[i]=rt[i-1]);++i)
        for(auto v:G[i]) ins(v,rt[i]);
    for(int i=n;i;--i) st[++top]=i,nc[top]=c[i];
    for(ll t,h,i=1;i<=q;++i){
        cin>>t>>h;
        while(top){
            if(st[top-1]==st[top]+1){
                Sum=min((ll)c[st[top]],1ll*r[st[top]]*(t-tim[top])+nc[top]);
                if(Sum<=h){h-=Sum,--top;continue;}
                else{nc[top]=Sum-h,tim[top]=t;break;}
                continue;
            }
            Sum=0,Now=t-tim[top],qry(st[top],st[top-1]-1,rt[Now]);
            if(Sum<=h){h-=Sum,--top;continue;}
            int id=find(st[top],st[top-1]-1,h,rt[Now]);
            if(id==st[top-1]-1) --top;
            else st[top]=id+1;
            st[++top]=id,nc[top]=min((ll)c[id],1ll*Now*r[id])-h,tim[top]=t;
            break;
        }
        if(!top) ans+=h;
        if(st[top]!=1) st[++top]=1,nc[top]=0,tim[top]=t;
    }
    cout<<ans<<"\n";
}

P9655 『GROI-R2』 Beside You

我们考虑对于每一个右括号,维护向上对应的左括号,然后我们就知道一个左括号可能对应很多右括号,而这些括号构成了一个联通块,而每个连通块大小可以用虚树经典结论做,然后一个连通块可以挂到另一个连通块下,然后 dp 即可。

Code
// Problem: P9655 『GROI-R2』 Beside You
// URL: https://www.luogu.com.cn/problem/P9655
// Memory Limit: 512 MB
// Time Limit: 1200 ms
// Author: Nityacke
// Time: 2024-07-10 20:25:07

#include<bits/stdc++.h>
#define pb push_back
#define int long long
using namespace std;
const int N=5e5+5;
int n,dp[N],stk[N],cnt,acc[N],lst[N];
int top[N],sz[N],dep[N],f[N],son[N];
char ch[N];
vector<int>G[N];
inline void dfs(int x,int fa){
    if(ch[x]=='(') stk[++cnt]=x,lst[x]=x;
    else if(cnt) acc[x]=stk[cnt--];
    sz[x]=1,dep[x]=dep[f[x]=fa]+1;
    for(auto v:G[x])
        if(v!=fa) dfs(v,x),sz[x]+=sz[v],
        son[x]=sz[v]>sz[son[x]]?v:son[x];
    if(~acc[x]) stk[++cnt]=acc[x];
    else if(ch[x]=='(') --cnt;
}
inline void dfs2(int x,int tp){
    top[x]=tp;
    if(son[x]) dfs2(son[x],tp);
    for(auto v:G[x])
        if(v!=f[x]&&v!=son[x]) dfs2(v,v);
}
inline int lca(int u,int v){
    while(top[u]!=top[v])
        if(dep[top[u]]>=dep[top[v]]) u=f[top[u]];
        else v=f[top[v]];
    return dep[u]<dep[v]?u:v;
}
inline int dis(int u,int v){return dep[u]+dep[v]-2*dep[lca(u,v)];}
inline void dfs3(int x){
    if(~acc[x]) dp[acc[x]]+=dis(lst[acc[x]],x),lst[acc[x]]=x;
    for(auto v:G[x])
        if(v!=f[x]) dfs3(v);
}
inline void dfs4(int x){
    for(auto v:G[x])
        if(v!=f[x]) dfs4(v);
    if(ch[x]=='('&&~acc[f[x]]) dp[acc[f[x]]]+=dp[x];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>(ch+1);
    for(int u,v,i=1;i<n;++i)
        cin>>u>>v,G[u].pb(v),G[v].pb(u);
    for(int i=1;i<=n;++i) acc[i]=lst[i]=-1;
    dfs(1,0),dfs2(1,1),dfs3(1);
    for(int i=1;i<=n;++i)
        if(lst[i]!=i&&lst[i]!=-1)
            dp[i]+=dis(lst[i],i),dp[i]=dp[i]/2+1;
    dfs4(1);
    int ans=0;
    for(int i=1;i<=n;++i) ans=max(ans,dp[i]);
    cout<<ans<<"\n";
}

CF1452G Game On Tree

我们发现 Alice 一定是走到一个点然后停下来等 Bob 图图他,该点的离 B 距离最小值就是答案,然后我们考虑一个答案对哪些有贡献, 我们发现 \(u\) 能走到 \(v\) 当且仅当 \(dis(u,v)<f_v\),所以我们只需要每个 \(v\) 对自己距离 \(<f_v\) 的点贡献答案。

这是邻域取 \(\max\),且有一定支配性,点分治写起来很简单, \(O(n\log n)\)

有一个 \(O(n^{\frac 53 })\) 的神秘做法,在 \(n=2\times 10^5\) 只需要跑 460ms。

Code
// Problem: Game On Tree
// URL: https://www.luogu.com.cn/problem/CF1452G
// Memory Limit: 500 MB
// Time Limit: 3000 ms
// Author: Nityacke
// Time: 2024-07-10 20:52:46

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2e5+5;
vector<int>G[N],S[N];
int n,k,dis[N],ans[N],mx[N];
#define PII pair<int,int>
#define fi first
#define se second
queue<PII>Q;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;++i) dis[i]=1e9;
    for(int u,v,i=1;i<n;++i)
        cin>>u>>v,G[u].pb(v),G[v].pb(u);
    cin>>k;
    for(int x,i=1;i<=k;++i)	cin>>x,Q.push({x,dis[x]=0});
    while(Q.size()){
        int x=Q.front().fi;Q.pop();
        for(auto v:G[x])
            if(dis[v]>dis[x]+1) Q.push({v,dis[v]=dis[x]+1});
    }
    for(int i=1;i<=n;++i) S[dis[i]].pb(i);
    for(int i=n;i;--i){
        for(auto v:S[i]) Q.push({v,i-1});
        while(Q.size()){
            PII x=Q.front();Q.pop();
            if(!ans[x.fi]) ans[x.fi]=i;
            if(!x.se||mx[x.fi]>=x.se) continue;
            mx[x.fi]=x.se;
            for(auto v:G[x.fi]) Q.push({v,x.se-1});
        }
    }
    for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
}

[ABC221G] Jumping sequence

考虑对于每个点 \((x,y)\) 变成 \((x+y,x-y)\),这样你发现两维就独立了,然后 bitset 优化背包即可。

Code
// Problem: [ABC221G] Jumping sequence
// URL: https://www.luogu.com.cn/problem/AT_abc221_g
// Memory Limit: 1 MB
// Time Limit: 5000 ms
// Author: Nityacke
// Time: 2024-07-11 11:51:00

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int V=3.6e6+5,N=2e3+5;
int n,a,b,x,y,d[N],sum;
bitset<V>f[N];
inline void fail(){cout<<"No",exit(0);}
char opt[4]={'L','D','U','R'},ans[N];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>x>>y,a=x+y,b=x-y;
    for(int i=1;i<=n;++i) cin>>d[i],sum+=d[i];
    if((a+sum)%2||(b+sum)%2) fail();
    a=(a+sum)/2,b=(b+sum)/2,f[0][0]=1;
    for(int i=1;i<=n;++i) f[i]=f[i-1]|(f[i-1]<<d[i]);
    if(a<0||b<0||!f[n][a]||!f[n][b]) fail();
    for(int i=n;i;--i){
        int op=0;
        if(!f[i-1][a]) a-=d[i],op|=2;
        if(!f[i-1][b]) b-=d[i],op|=1;
        ans[i]=opt[op];
    }
    cout<<"Yes\n";
    for(int i=1;i<=n;++i) cout<<ans[i];
}

CF1798F Gifts from Grandfather Ahmed

根据 EGZ 定理:\(2n-1\) 个数里一定能找出 \(n\) 个数使得他们的和是 \(n\) 的倍数,然后我们按 \(s_i\) 排序后背包即可。

Code
// Problem: Gifts from Grandfather Ahmed
// URL: https://www.luogu.com.cn/problem/CF1798F
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author: Nityacke
// Time: 2024-07-11 16:36:10

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=205;
int n,K,a[N],vis[N],val[N];
vector<int>ans[N];
bool f[N][N][N];
struct node{
	int val,id;
	inline bool operator<(const node a)const{return val<a.val;}
}s[N];
signed main(){
	cin>>n>>K;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=K;++i) cin>>s[i].val,s[i].id=i;
	sort(s+1,s+K+1);
	for(int o=1,now;o<K;++o){
		f[0][0][0]=1,now=s[o].val;
		for(int i=1;i<=n;++i)
			for(int j=0;j<=min(now,i);++j)
				for(int k=0;k<now;++k){
					f[i][j][k]=f[i-1][j][k];
					if(!vis[i]&&j) f[i][j][k]|=f[i-1][j-1][((k-a[i])%now+now)%now];
				}
		int t=0,sum=now;
		for(int i=n;i;--i)
			if(!vis[i]&&sum&&f[i-1][sum-1][((t-a[i])%now+now)%now])
				t=((t-a[i])%now+now)%now,--sum,vis[i]=1,ans[s[o].id].pb(a[i]);
	}
	int sum=0,now=s[K].val;
	for(int i=1;i<=n;++i)
		if(!vis[i]) sum=(sum+a[i])%now,ans[s[K].id].pb(a[i]);
	ans[s[K].id].pb(now-sum);
	cout<<ans[s[K].id].back()<<"\n";
	for(int i=1;i<=K;++i,cout<<"\n")
		for(auto v:ans[i]) cout<<v<<' ';
}

[ARC096F] Sweet Alchemy

我们发现值域很大,不能直接背包,但是我们可以大部分使用 \(\frac V w\) 直接贪,后面部分背包,具体的,每个数留 \(n\) 个背包,剩下的按 \(\frac V w\) 排序后直接贪心即可。

Code
// Problem: [ARC096F] Sweet Alchemy
// URL: https://www.luogu.com.cn/problem/AT_arc096_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-11 15:58:22

#include<bits/stdc++.h>
#define pb push_back
#define int long long
using namespace std;
const int N=55;
int lim,n,S,d,w[N],v[N],f[N*N*N];
vector<int>G[N];
inline void dfs(int x){
    v[x]=1;
    for(auto y:G[x])
        dfs(y),w[x]+=w[y],v[x]+=v[y];
}
inline void ins(int nd,int val){
    for(int i=lim;i>=val;i--) f[i]=min(f[i-val]+nd,f[i]);
}
inline void ins(int nd,int val,int c){
    for(int i=0;(1<<i)<=c;++i)
        ins((1<<i)*nd,(1<<i)*val),c-=1<<i;
    if(c) ins(nd*c,val*c);
}
struct Node{int nd,val,id;}a[N];
inline bool cmp(Node a,Node b){return a.val*b.nd>b.val*a.nd;}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>S>>d>>w[1],lim=n*n*n,memset(f,0x3f,sizeof f),f[0]=0;
    for(int i=2,x;i<=n;++i) cin>>w[i]>>x,G[x].pb(i);
    dfs(1);
    for(int i=2;i<=n;++i) ins(w[i],v[i],min(d,n));
    for(int i=1;i<=n;++i) a[i]={w[i],v[i],i};
    sort(a+1,a+n+1,cmp);
    int ans=0;
    for(int i=0;i<=lim;++i)
        if(f[i]<=S){
            int cnt,rest=S-f[i],res=i;
            for(int j=1;j<=n;++j)
                if(a[j].id==1) res+=rest/a[j].nd*a[j].val,rest%=a[j].nd;
                else
                    cnt=min(max(0ll,d-n),rest/a[j].nd),
                    res+=cnt*a[j].val,rest-=cnt*a[j].nd;
            ans=max(ans,res);
        }
    cout<<ans<<"\n";
}

[ARC096E] Everything on It

我们把问题转化成没有元素出现一次及以下,考虑二项式反演。

对于我们钦定了 \(m\) 个数只能出现一次及以下的,我们考虑如何计数。

我们枚举这 \(m\) 个数,\(\binom n m\),然后枚举这 \(m\) 个数被分到了 \(j\) 个集合,由于有些数出现一次,有些数不出现,所以方案数是 \({m+1\brace j+1}\)

对于剩下的 \(n-m\) 个数,\(2^{n-m}\) 个集合,每个集合可以放可以不放,有 \(2^{2^{n-m}}\) 种情况,然后我们枚举的这 \(j\) 个集合还可以放数,有 \((2^{n-m})^j\) 种方法。

所以我们得到:

\[ans=\sum_{i=0}^n(-1)^i\binom n i2^{2^{n-i}}\sum_{j=0}^i{i+1\brace j+1}(2^{n-i})^j \]

\(O(n^2)\) 计算即可。

Code
// Problem: [ARC096E] Everything on It
// URL: https://www.luogu.com.cn/problem/AT_arc096_c
// Memory Limit: 512 MB
// Time Limit: 4000 ms
// Author: Nityacke
// Time: 2024-07-11 18:42:30

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e3+5;
int C[N][N],S[N][N],n,ans,H,pw[N];
inline int qp(int a,int b=H-2,int HH=H){
    int ans=1;
    for(;b;b>>=1,a=a*a%HH)
        if(b&1) ans=ans*a%HH;
    return ans;
}
inline int adc(int x,int y){return x+y>=H?x+y-H:x+y;}
inline int sig(int x){return x&1?H-1:1;}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>H;
    for(int i=0;i<N;++i){
        C[i][0]=1,S[i][0]=(i==0),pw[i]=(i==0?1:adc(pw[i-1],pw[i-1]));
        for(int j=1;j<=i;++j) C[i][j]=adc(C[i-1][j],C[i-1][j-1]),S[i][j]=adc(S[i-1][j-1],j*S[i-1][j]%H);
    }
    for(int i=0;i<=n;++i){
        int res=0;
        for(int v=1,j=0;j<=i;++j,v=v*pw[n-i]%H) res=adc(res,S[i+1][j+1]*v%H);
        ans=adc(ans,sig(i)*C[n][i]%H*qp(2,qp(2,n-i,H-1))%H*res%H);
    }
    cout<<ans<<"\n";
}

CF1781F Bracket Insertion

奇怪的 dp 题,我们将 \((,)\) 分别看成 \(1,-1\),则合法条件是所有前缀和 \(\ge 0\)

我们对 \((2n-1)!\) 种插入方案求概率和,最后除掉即可。

我们考虑第一个字符必然是 \((\),然后与它对应的字符一定是 \()\),然后划分成的两个部分,一个部分所有前缀和应该 \(\ge -1\),另一部分大于 \(0\)

然后我们发现前缀和必须 \(\ge -j(j>0)\) 的时候,我们第一个就可以填 \()\) 了,然后我们设计 dp 状态就是 \(f_{i,j}\) 表示前缀和 \(\ge -j\),使用 \(i\) 次操作的概率和,dp 转移是简单的。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=505,H=998244353;
inline int qp(int a,int b=H-2){
    int ans=1;
    for(;b;b>>=1,a=a*a%H)
        if(b&1) ans=ans*a%H;
    return ans;
}
int n,p,q,f[N][N],fac[N],ifac[N];
inline int C(int n,int m){return n<m||m<0?0:fac[n]*ifac[m]%H*ifac[n-m]%H;}
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>p,p=p*qp(10000)%H,q=(H+1-p)%H,fac[0]=ifac[0]=1;
    for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H,ifac[i]=ifac[i-1]*qp(i)%H;
    for(int i=0;i<N;++i) f[0][i]=1;
    for(int i=1;i<=n;++i){
        f[i][n+1]=1;
        for(int j=0;j<=n;++j){
            for(int k=0;k<i;++k) add(f[i][j],p*C(i,k+1)%H*f[k][j+1]%H*f[i-k-1][j]%H);
            if(j) for(int k=0;k<i;++k) add(f[i][j],q*C(i,k+1)%H*f[k][j-1]%H*f[i-k-1][j]%H);
        }
    }
    int ans=f[n][0];
    for(int i=1;i<=n*2;i+=2) ans=ans*qp(i)%H;
    cout<<ans<<"\n";
}

CF1149D Abandoning Roads

我们首先考虑这个最小生成树的形态,我们发现先用 \(a\) 连接,然后我们不同的连通块之间只会有 \(1\)\(b\) 边。

然后我们考虑跑最短路怎么跑,直接跑的问题在于我们发现有可能存在 \(S,T\) 在同一个连通块,但是 \(S\to T\) 在本来连通块的距离比一条形如 \(S\to u\to T\) 的路径长,且 \(u\)\(S\) 不在同一个连通块。

我们考虑怎么办,我们考虑最短路时压一个状态 \(sta\),如果我们不能再走到第 \(i\) 个连通块,也就是已经到过第 \(i\) 个连通块的时候,我们就把 \(sta\) 的第 \(i\) 位变成 \(1\),然后跑最短路即可,时间复杂度 \(O(2^nm\log m)\)

然后我们考虑如何优化,我们发现,对于大小 \(\le 3\) 的连通块,走出去再走回来的代价 \(2b\) 一定比我直接走内部要劣,所以我们只需要状压大小 \(\ge 4\) 的连通块即可。

时间复杂度 \(O(2^{\lfloor \frac n 4 \rfloor}m\log m)\) 可以通过。

Code
// Problem: Abandoning Roads
// URL: https://www.luogu.com.cn/problem/CF1149D
// Memory Limit: 500 MB
// Time Limit: 5000 ms
// Author: Nityacke
// Time: 2024-07-12 20:29:29

#include<bits/stdc++.h>
#define int long long
#define pi pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=75,K=18;
int n,m,A,B,w[N][N],vis1[N],vis2[N];
int id[N],sz[N],cnt,dim;
int f[N][1<<K];
priority_queue<pair<int,pi>,vector<pair<int,pi>>,greater<pair<int,pi>>>Q;
inline int dfs1(int x){
	vis1[x]=1;
	int sz=1;
	for(int i=1;i<=n;++i)
		if(w[x][i]==A&&!vis1[i]) sz+=dfs1(i);
	return sz;
}
inline void dfs2(int x,int bl){
	vis2[x]=1,id[x]=bl,++sz[bl];
	for(int i=1;i<=n;++i)
		if(w[x][i]==A&&!vis2[i]) dfs2(i,bl);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>A>>B,memset(w,0x3f,sizeof w);
	for(int u,v,e,i=1;i<=m;++i)
		cin>>u>>v>>e,w[u][v]=w[v][u]=e;
	for(int i=1;i<=n;++i)
		if(!vis1[i]) if(dfs1(i)>3) dfs2(i,cnt++);
	dim=cnt,memset(vis1,0,sizeof vis1);
	for(int i=1;i<=n;++i)
		if(!vis1[i]) if(dfs1(i)<=3) dfs2(i,cnt++);
	memset(f,0x3f,sizeof f),f[1][0]=0,Q.push({0,{1,0}});
	while(!Q.empty()){
		int d=Q.top().fi,u=Q.top().se.fi,S=Q.top().se.se;Q.pop();
		if(f[u][S]<d) continue;
		for(int v=1;v<=n;++v){
			if(id[u]==id[v]&&w[u][v]!=A) continue;
			if(id[v]<dim&&(S>>id[v]&1)) continue;
			int tS=S;
			if(id[u]<dim&&id[u]!=id[v]) tS|=1<<id[u];
			if(f[v][tS]>w[u][v]+d) Q.push({f[v][tS]=w[u][v]+d,{v,tS}});
		}
	}
	for(int i=1;i<=n;++i){
		int ans=f[i][0];
		for(int j=1;j<(1<<dim);++j) ans=min(ans,f[i][j]);
		cout<<ans<<" ";
	}
}

CF1146G Zoning Restrictions

套路同 P3592 和 P4766

我们考虑何时计算一个限制的贡献,我们考虑区间 dp,如果我们当前区间 \([L,R]\) 和限制 \((l,r,x,c)\) 满足 \([l,r]\subset [L,R]\),那么我们就考虑计算其贡献。

然后计算贡献我们可以枚举最大值和最大值位置,然后递归下去做即可,用二维前缀和优化后时间复杂度 \(O(n^4)\)

Code
// Problem: Zoning Restrictions
// URL: https://www.luogu.com.cn/problem/CF1146G
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author: Nityacke
// Time: 2024-07-12 21:46:39

#include<bits/stdc++.h>
using namespace std;
const int N=55;
vector<int>G[N];
#define pb push_back
int f[N][N][N],n,h,m,L[N],R[N],w[N];
int buc[N][N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>h>>m;
	for(int x,i=1;i<=m;++i)
		cin>>L[i]>>R[i]>>x>>w[i],G[x+1].pb(i);
	for(int i=1;i<=h;++i){
		for(auto v:G[i])
			for(int i=1;i<=L[v];++i)
				for(int j=R[v];j<=n;++j) buc[i][j]+=w[v];
		for(int len=1;len<=n;++len)
			for(int l=1,r=len;r<=n;++l,++r){
				f[i][l][r]=f[i-1][l][r];
				for(int k=l;k<=r;++k){
					int val=i*i-(buc[l][r]-buc[l][k-1]-buc[k+1][r]);
					f[i][l][r]=max(f[i][l][r],f[i][l][k-1]+f[i][k+1][r]+val);
				}
			}
	}
	cout<<f[h][1][n]<<"\n";
}

[AGC012E] Camel and Oases

tangtang 题,我们发现不同的 \(V\) 只有 \(O(\log V)\) 种,然后我们可以对每种 \(V\) 预处理出一次可以选到哪些区间。

然后我们发现我们需要每个权值选一个区间使其覆盖整个序列,然后你可以维护每种选择的集合 \(S\) 时能覆盖最大的 \([1,L]\) 和最小的 \([R,n]\),然后随便做一下即可。

这玩意能有铜牌题???感觉不如 Flip Ceils 一根毛。

Code
// Problem: [AGC012E] Camel and Oases
// URL: https://www.luogu.com.cn/problem/AT_agc012_e
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-12 22:08:59

#include<bits/stdc++.h>
using namespace std;
const int N=1<<20;
int n,V,Cnt,a[N],cnt,tot;
int l[20][N],r[20][N],ans[N];
int L[N<<2],R[N<<2],val[N];
#define pi pair<int,int>
pi seg[N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>V;
	for(int i=1;i<=n;++i) cin>>a[i];
	int v=V;
	while(v) v>>=1,val[cnt++]=v;
	val[cnt]=V,tot=(1<<cnt)-1;
	for(int i=0;i<=cnt;++i){
		l[i][1]=1,r[i][n]=n,l[i][0]=1,r[i][n+1]=n;
		for(int j=2;j<=n;++j) l[i][j]=a[j]-a[j-1]<=val[i]?l[i][j-1]:j;
		for(int j=n-1;j;--j) r[i][j]=a[j+1]-a[j]<=val[i]?r[i][j+1]:j;
	}
	for(int i=1;i<=n;++i) seg[i]={l[cnt][i],r[cnt][i]};
	sort(seg+1,seg+n+1),Cnt=unique(seg+1,seg+n+1)-seg-1;
	if(Cnt>cnt+1) goto End;
	for(int i=0;i<=tot;++i) R[i]=n+1;
	for(int S=0;S<=tot;++S)
		for(int i=0;i<cnt;++i)
			if(!(S>>i&1))
				L[S|(1<<i)]=max(L[S|(1<<i)],r[i][L[S]+1]),
				R[S|(1<<i)]=min(R[S|(1<<i)],l[i][R[S]-1]);
	for(int i=1;i<=n;++i){
		bool fl=0;
		for(int S=0;S<=tot;++S)
			if(L[S]>=i-1&&R[tot^S]<=r[cnt][i]+1){fl=1;break;}
		for(int j=i;j<=r[cnt][i];++j) ans[j]=fl;
		i=r[cnt][i];
	}
	End:;
	for(int i=1;i<=n;++i) puts(ans[i]?"Possible":"Impossible");
}

CF848D Shake It!

我们手玩一下状态,发现是若干个 \(S\to G\to T\)\(G\) 是一个图,然后答案是若干个这样的图的卷积。

dp 状态很好设计,就是对于 \(G\)\(f_{n,m}\) 表示操作 \(n\) 次,最大流为 \(m\) 的方案数。

然后 dp 时我们钦定中间有一个点是 \(S\to T\) 拓展出来的第一个点,然后考虑左边和右边分别操作了多少次即可。

Code
// Problem: Shake It!
// URL: https://www.luogu.com.cn/problem/CF848D
// Memory Limit: 250 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-13 19:18:51

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=55,H=1e9+7;
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
inline int calcg(int i,int j);
int n,m,inv[N],f[N][N],g[N][N],h[N][N],G[N][N][N],visf[N][N],visg[N][N];
inline int calcf(int i,int j){
	if(i<j) return 0;
	if(!i||!j) return i==j;
	if(visf[i][j]) return f[i][j];
	for(int x=1;x<=i;++x)
		for(int y=1;y<=j;++y) calcg(x,y);
	visf[i][j]=1,f[i][j]=h[i][j];
	return f[i][j];
}
inline int calcg(int i,int j){
	if(i<j) return 0;
	if(!i||!j) return i==j;
	if(visg[i][j]) return g[i][j];
	visg[i][j]=1;
	for(int l=0,r=i-1;l<i;++l,--r){
		int v=0;
		for(int p=j-1;p<=r;++p) v+=calcf(r,p);
		add(g[i][j],v%H*calcf(l,j-1)%H),v=0;
		for(int p=j;p<=l;++p) v+=calcf(l,p);
		add(g[i][j],v%H*calcf(r,j-1)%H);
	}
	G[i][j][0]=1;
	for(int p=1;p<=n;++p) G[i][j][p]=G[i][j][p-1]*(g[i][j]+p-1)%H*inv[p]%H;
	for(int p=n;p;--p)
		for(int q=n;q;--q)
			for(int k=1,pp=p-i,qq=q-j;pp>=0&&qq>=0;++k,pp-=i,qq-=j)
				add(h[p][q],h[pp][qq]*G[i][j][k]%H);
	return g[i][j];
}
signed main(){
	cin>>n>>m,h[0][0]=inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=(H-H/i)*inv[H%i]%H;
	cout<<calcf(n,m-1)<<"\n";
}

[ARC099F] Eating Symbols Hard

我们考虑维护每个位置的生成函数 \(\sum_{i=-\infty}^\infty a_ix^i\),然后我们随机代入一个 \(x\) 值哈希即可。

Code
// Problem: [ARC099F] Eating Symbols Hard
// URL: https://www.luogu.com.cn/problem/AT_arc099_d
// Memory Limit: 1 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-14 21:03:33

#include<bits/stdc++.h>
#define int long long
#define pi pair<int,int>
using namespace std;
const int N=3e5+5,B1=237,B2=637,H1=998244353,H2=1004535809;
int n,ans,I1,I2;
int g[N][2],S[N],T[N];
char s[N];
map<pi,int>buc;
inline int qp(int a,int b,int c){
	int ans=1;
	for(;b;b>>=1,a=a*a%c)
		if(b&1) ans=ans*a%c;
	return ans;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>(s+1),g[0][0]=g[0][1]=1;
	I1=qp(B1,H1-2,H1),I2=qp(B2,H2-2,H2);
	for(int i=1;i<=n;++i){
		if(s[i]=='-')
			g[i][0]=g[i-1][0],g[i][1]=g[i-1][1],
			S[i]=(S[i-1]-g[i][0]+H1)%H1,T[i]=(T[i-1]-g[i][1]+H2)%H2;
		else if(s[i]=='+')
			g[i][0]=g[i-1][0],g[i][1]=g[i-1][1],
			S[i]=(S[i-1]+g[i][0])%H1,T[i]=(T[i-1]+g[i][1])%H2;
		else if(s[i]=='<')
			g[i][0]=g[i-1][0]*I1%H1,g[i][1]=g[i-1][1]*I2%H2,
			S[i]=S[i-1],T[i]=T[i-1];
		else 
			g[i][0]=g[i-1][0]*B1%H1,g[i][1]=g[i-1][1]*B2%H2,
			S[i]=S[i-1],T[i]=T[i-1];
		++buc[{S[i],T[i]}];
	}
	++buc[{0,0}];
	for(int i=0,x,y;i<n;++i){
		--buc[{S[i],T[i]}];
		x=(S[i]+S[n]*g[i][0])%H1,y=(T[i]+T[n]*g[i][1])%H2;
		ans+=buc[{x,y}];
	}
	cout<<ans<<"\n";
}

[AGC018D] Tree and Hamilton Path

我们先考虑求回路,对于答案,我们知道经典构造之 \(\sum w_i\min(sz_i,n-sz_i)\)

然后我们考虑从中删去一条边使得其更优,但是我们需要想一个办法去刻画一定存在于某个回路最优解里面的边,我们可以证明,对于一个最优解中的所有边,我们一定每条边都会经过重心,证明我们可以反证法,然后找离重心最近的点就可以了。

注意有两个重心需要特判。

Code
// Problem: [AGC018D] Tree and Hamilton Path
// URL: https://www.luogu.com.cn/problem/AT_agc018_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-15 08:10:31

#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pb push_back
#define pi pair<int,int>
using namespace std;
const int N=1e5+5;
int mx[N],p1,p2,n,sz[N],ans;
vector<pi>G[N];
inline void dfs(int x,int fa){
	sz[x]=1;
	for(auto v:G[x])
		if(v.fi!=fa){
			dfs(v.fi,x),sz[x]+=sz[v.fi],mx[x]=max(mx[x],sz[v.fi]);
			ans+=2*v.se*min(n-sz[v.fi],sz[v.fi]);
		}
	mx[x]=max(mx[x],n-sz[x]);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int u,v,w,i=1;i<n;++i)
		cin>>u>>v>>w,G[u].pb({v,w}),G[v].pb({u,w});
	dfs(1,0),p1=1;
	for(int i=2;i<=n;++i)
		if(mx[i]<mx[p1]) p2=0,p1=i;
		else if(mx[i]==mx[p1]) p2=i;
	int mn=1e9;
	for(auto v:G[p1])
		if(!p2||v.fi==p2) mn=min(mn,v.se);
	cout<<ans-mn<<"\n";
}

[ARC093F] Dark Horse

我们首先可以默认自己从第一个点开始,最后方案数乘上 \(2^n\) 即可。

然后我们考虑怎样的情况我们会被击败,就是对于一个节点,能打败我们的点是里面的最小值,这样他会杀出来并图图我们,所以我们就希望我们一路上没有节点的兄弟的最小值能图图我们,即对于大小为 \(1,2,4\ldots 2^{n-1}\) 的每个节点,不存在节点最小值是 \(M\) 个点之一。

这个不存在似乎不是很好计算,我们考虑子集反演,然后钦定 \(S\) 分别为某个节点的最小值,然后计算方案数。

我们考虑将 \(A\) 数组从大到小考虑,假设目前考虑 \(a_x\),状压还没选择的组的状态,然后我们有两种可能性:

  • \(a_x\) 作为某个组的最小值,转移是简单的。
  • \(a_x\) 并不是最小值,从 \(a_{x+1}\) 的状态继承过来。

时间复杂度 \(O(nm2^n)\)

Code
// Problem: [ARC093F] Dark Horse
// URL: https://www.luogu.com.cn/problem/AT_arc093_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-15 09:27:36

#include<bits/stdc++.h>
#define int long long
#define pc(x) __builtin_popcount(x)
using namespace std;
const int N=2e5+5,H=1e9+7;
int n,m,fac[N],ifac[N],pw[N],a[N],f[17][N],tot;
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline int C(int n,int m){return n<m||m<0?0:fac[n]*ifac[m]%H*ifac[n-m]%H;}
inline int adc(int x,int y){return x+y>=H?x+y-H:x+y;}
inline void add(int &x,int v){x=adc(x,v);}
inline int sig(int x){return x&1?H-1:1;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,fac[0]=ifac[0]=pw[0]=1,tot=1<<n;
	for(int i=1;i<=m;++i) cin>>a[i];
	sort(a+1,a+m+1,greater<int>());
	for(int i=1;i<N;++i)
		pw[i]=adc(pw[i-1],pw[i-1]),fac[i]=fac[i-1]*i%H,ifac[i]=ifac[i-1]*qp(i)%H;
	f[0][0]=1;
	for(int i=1;i<=m;++i)
		for(int S=0;S<tot;++S){
			add(f[i][S],f[i-1][S]);
			for(int k=0;k<n;++k)
				if(!(S>>k&1)) add(f[i][S|(1<<k)],f[i-1][S]*C(tot-a[i]-S,pw[k]-1)%H*fac[pw[k]]%H);
		}
	int ans=0;
	for(int S=0;S<tot;++S) add(ans,sig(pc(S))*f[m][S]%H*fac[tot-1-S]%H);
	cout<<ans*tot%H<<"\n";
}

CF903G Yet Another Maxflow Problem

考虑最大流等于最小割,然后我们可以证明我们在 \(A\to A,B\to B\) 中每个部分最多只会割掉一条边,然后我们考虑如果我们割的 \(A_x\to A_{x+1},B_y\to B_{y+1}\) 时的代价,我们推一下式子发现对于每个 \(x\),其 \(y\) 一定,那么我们预处理出来 \(x\) 的代价,就变成了单点修改,区间最大值,线段树维护。

Code
// Problem: Yet Another Maxflow Problem
// URL: https://www.luogu.com.cn/problem/CF903G
// Memory Limit: 250 MB
// Time Limit: 4000 ms
// Author: Nityacke
// Time: 2024-07-15 09:49:54

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,M=8e5+5;
int n,m,q,val[N],a[N],b[N];
namespace ST{
	int mn[M],tag[M];
	#define k1 (k<<1)
	#define k2 (k<<1|1)
	#define mid ((l+r)>>1)
	#define ls k1,l,mid
	#define rs k2,mid+1,r
	inline void up(int k){mn[k]=min(mn[k1],mn[k2]);}
	inline void add(int k,int v){tag[k]+=v,mn[k]+=v;}
	inline void push(int k){if(tag[k]) add(k1,tag[k]),add(k2,tag[k]),tag[k]=0;}
	inline void build(int k=1,int l=1,int r=n){
		tag[k]=0;
		if(l==r) return mn[k]=val[l],void();
		build(ls),build(rs),up(k);
	}
	inline void change(int L,int R,int v,int k=1,int l=1,int r=n){
		if(L>R) return;
		if(L<=l&&R>=r) return add(k,v);
		push(k);
		if(L<=mid) change(L,R,v,ls);
		if(R>mid) change(L,R,v,rs);
		up(k);
	}
};
#define pi pair<int,int>
#define pb push_back
#define fi first
#define se second
vector<pi>G[N];
inline void init(){
	for(int i=1;i<=n-1;++i) val[i+1]=b[i];
	ST::build();
	for(int i=1;i<=n;++i){
		for(auto v:G[i]) ST::change(1,v.fi,v.se);
		val[i]=ST::mn[1]+a[i];
	}
	ST::build();
}
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]>>b[i];
	for(int u,v,w,i=1;i<=m;++i)
		cin>>u>>v>>w,G[u].pb({v,w});
	init();
	cout<<ST::mn[1]<<"\n";
	for(int u,v,i=1;i<=q;++i){
		cin>>u>>v,ST::change(u,u,v-a[u]),a[u]=v;
		cout<<ST::mn[1]<<"\n";
	}
}

P3679 [CERC2016] 二分毯 Bipartite Blanket

广义霍尔定理:给定一张二分图。如果存在一个匹配覆盖左部图中的点集 \(X\),且存在一个匹配覆盖右部图中的点集 \(Y\),那么存在一个匹配同时覆盖 \(X\)\(Y\)

那么我们用霍尔定理可以对左右边分别求出可行的覆盖点集和,然后排序一下双指针即可。

Code
// Problem: P3679 [CERC2016] 二分毯 Bipartite Blanket
// URL: https://www.luogu.com.cn/problem/P3679
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Author: Nityacke
// Time: 2024-07-15 10:11:21

#include<bits/stdc++.h>
#define int long long
#define pc(x) __builtin_popcount(x)
#define pb push_back
using namespace std;
const int N=20;
int vis[1<<N],S[1<<N],w[1<<N];
inline void check(int *wA,int *vA,int n){
	for(int i=1;i<(1<<n);++i)
		w[i]=w[i-(i&-i)]|wA[__lg(i&-i)],
		S[i]=S[i-(i&-i)]+vA[__lg(i&-i)],
		vis[i]=pc(w[i])>=pc(i);
	for(int i=0;i<n;++i)
		for(int j=0;j<(1<<n);++j)
			if(j>>i&1) vis[j]=min(vis[j],vis[j-(1<<i)]);
}
char st[N+5];
int ans,T,n,m;
int vA[N],vB[N],wA[N],wB[N];
vector<int>vec;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;++i){
		cin>>st;
		for(int j=0;j<m;++j)
			if(st[j]=='1') wA[i]|=1<<j,wB[j]|=1<<i;
	}
	for(int i=0;i<n;++i) cin>>vA[i];
	for(int i=0;i<m;++i) cin>>vB[i];
	cin>>T,vis[0]=1;
	check(wA,vA,n);
	for(int i=0;i<(1<<n);++i)
		if(vis[i]) vec.pb(S[i]);
	sort(vec.begin(),vec.end());
	check(wB,vB,m);
	for(int i=0;i<(1<<m);++i)
		if(vis[i]) ans+=vec.end()-lower_bound(vec.begin(),vec.end(),T-S[i]);
	cout<<ans<<"\n";
}

P3343 [ZJOI2015] 地震后的幻想乡

似乎是斯特林反演题目,但是感觉不如积分。

我们设首次联通的时间是 \(X\),设 \(P(t)=Pr(X\ge t)\),那么根据期望的定义有:

\[E(X)=\int_0^1P(t)\mathrm dt \]

如果 \(X\ge t\),说明图不连通,我们可以枚举 \(1\) 所在连通块里的点 \(S\),首先 \(S\) 要连通(联通的概率我们定义为 \(1-P_S(t)\)),其次需要 \(S\) 于外界的边的 \(e\) 都大于 \(t\),则我们可以知道:

\[P_S(t)=\sum_{1\in S_0\subsetneq S}(1-t)^{T(S_0,S-S_0)}[1-P_{S_0}(t)](1\in S) \]

边界条件是 \(P_{\{1\}}(t)=0\)

那么我们得到:

\[\begin{aligned} \int_0^1P_S(t)&=\sum_{1\in S_0\subsetneq S}\int_0^1(1-t)^{T(S_0,S-S_0)}[1-P_{S_0}(t)]\mathrm dt\\ &=\sum_{1\in S_0\subsetneq S}(\int_0^1(1-t)^{T(S_0,S-S_0)}-\int_0^1(1-t)^{T(S_0,S-S_0)}P_{S_0}(t))\mathrm dt\\ &=\sum_{1\in S_0\subsetneq S}(\frac 1 {1+T(S_0,S-S_0)}-\int_0^1(1-t)^{T(S_0,S-S_0)}P_{S_0}(t))\mathrm dt\\ \end{aligned} \]

同理我们知道:

\[\int_0^1(1-t)^kP_S(t)=\sum_{1\in S_0\subsetneq S}(\frac 1 {1+k+T(S_0,S-S_0)}-\int_0^1(1-t)^{k+T(S_0,S-S_0)}P_{S_0}(t))\mathrm dt \]

然后我们发现,我们可以根据 \(S,k\) 递推,然后答案就是 \(\int_0^1(1-t)^0P_U(t)\mathrm dt\)

时间复杂度 \(O(3^nm)\)

Code
#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=10,M=50;
int n,m,w[N],tot;
db f[1<<N][M];
#define pc(x) __builtin_popcount(x)
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,tot=(1<<n)-1;
	for(int u,v,i=1;i<=m;++i)
		cin>>u>>v,--u,--v,w[u]|=1<<v,w[v]|=1<<u;
	for(int S=3;S<=tot;++S) if(S&1)	
		for(int T=S&(S-1);T;T=(T-1)&S) if(T&1){
			int cnt=0;
			for(int i=0;i<n;++i)
				if((S>>i&1)&&!(T>>i&1)) cnt+=pc(T&w[i]);
			for(int i=0;i+cnt<=m;++i)
				f[S][i]+=1.0/(1+cnt+i)-f[T][i+cnt];
		}
	printf("%.6lf\n",f[tot][0]);
}

CF1842H Tenzing and Random Real Numbers

我们首先让所有 \(x\) 减去 \(0.5\),则 \(x\in [-0.5,0.5]\),然后我们的约束变成了和 \(0\) 的关系,我们可以知道这个只跟绝对值更大数的符号有关。
我们考虑状压绝对值的相对大小,设我们考虑了前 \(S\) 个数,然后我们枚举下一个数是正数还是负数,转移即可,时间复杂度 \(O(n2^n)\)

Code
#include<iostream>
#define int long long
using namespace std;
const int N=20,H=998244353;
int f[1<<N],n,m,w[2][N],tot;
inline int qpow(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,tot=(1<<n)-1;
	for(int i=1,opt,u,v;i<=m;++i)
		cin>>opt>>u>>v,--u,--v,w[opt][u]|=1<<v,w[opt][v]|=1<<u;
	f[0]=1;
	for(int S=0;S<=tot;++S)
		for(int j=0;j<n;++j)
			if(S>>j&1){
				for(int v=0;v<2;++v)
					if(!(w[v^1][j]&S)) add(f[S],f[S-(1<<j)]); 
			}
	int ans=f[tot];
	for(int i=1;i<=n;++i) ans=ans*qpow(2*i)%H;
	cout<<ans<<"\n";
}

[AGC020F] Arcs on a Circle

我们先把最长链作为起始点,然后可以 \(O(n!)\) 枚举相对大小,这时坐标变成了 \(nc\) 个,我们可以在上面 dp 即可。

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

一个复杂度更优的做法是我们发现

令分段数为 \(m\),自主放置的线段数为 \(n\),合法的放置线段的方案数是一个关于 \(m\)\(n\) 次多项式,因为线段总数才 \(n\) 条,不可能存在更高次的系数。

然后我们需要求

\[\lim_{m\to \infty}\frac {f(m)}{m^n} \]

我们不难发现就是 \([x^m]f(x)\),然后我们 dp 代入 \(n\) 个点值,插值即可。

时间复杂度 \(O(2^nn^4c^2)\)

Code
// Problem: [AGC020F] Arcs on a Circle
// URL: https://www.luogu.com.cn/problem/AT_agc020_f
// Memory Limit: 512 MB
// Time Limit: 5000 ms
// Author: Nityacke
// Time: 2024-07-15 16:45:01

#include<bits/stdc++.h>
#define db double
using namespace std;
const int M=55,N=6;
int n,c,tot,L[M];
db f[M*N][1<<N],ans,cnt;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>c,tot=(1<<(n-1))-1;
	for(int i=0;i<n;++i) cin>>L[i];
	sort(L,L+n);
	do{
		for(int i=0;i<=c*n;++i)
			for(int S=0;S<=tot;++S) f[i][S]=0;
		f[L[n-1]*n][0]=1;
		for(int i=1,p;i<=c*n;++i) if((p=i%n-1)>=0)	
			for(int j=i;j<=c*n;++j)
				for(int S=0;S<=tot;++S)
					if(!(S>>p&1)) f[min(c*n,max(j,i+L[p]*n))][S|(1<<p)]+=f[j][S];
		ans+=f[c*n][tot],++cnt;
	}while(next_permutation(L,L+n-1));
	printf("%.16lf\n",ans/cnt/pow(c,n-1));
}

[AGC005F] Many Easy Problems

有点 tang 的题。

我们考虑一个点 \(x\) 对于 \(ans_k\) 的贡献。

我们不难发现就是

\[\binom n k-\sum_{(x,y)\in E}\binom {sz_y}k \]

然后我们把 \(sz_y\) 全部找出来,设个数分别为 \(cnt_i\),则:

\[\begin{aligned} ans_k&=n\binom n k-\sum_{i=1}^na_i\binom i k\\ &=n\binom n k-\frac 1 {k!}\sum_{i=1}^n (a_ii!)\times \frac 1 {(i-k)!} \end{aligned} \]

然后你发现差卷积,NTT 即可。

Code
// Problem: [AGC005F] Many Easy Problems
// URL: https://www.luogu.com.cn/problem/AT_agc005_f
// Memory Limit: 512 MB
// Time Limit: 5000 ms
// Author: Nityacke
// Time: 2024-07-15 23:14:13

#include<bits/stdc++.h>
#define poly vector<int>
#define int long long
using namespace std;
const int N=(1<<19),H=924844033,G=5;
int rev[N],W[N],iG;
inline int qp(int a,int b=H-2){
int ans=1;
for(;b;b>>=1,a=1ll*a*a%H)
	if(b&1) ans=1ll*ans*a%H;
return ans;
}
inline int dec(int x,int v){return x<v?x-v+H:x-v;}
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline void NTT(poly &a,int L,int v){
for(int i=0;i<L;++i)
	if(rev[i]<i) swap(a[rev[i]],a[i]);
for(int mid=1,len=2;len<=L;mid=len,len<<=1){
	int w=qp(v==1?G:iG,(H-1)/len);W[0]=1;
	for(int i=1;i<mid;++i) W[i]=1ll*W[i-1]*w%H;
	for(int l=0;l<L;l+=len)
		for(int i=l,x;i<mid+l;++i)
		x=1ll*W[i-l]*a[i+mid]%H,a[i+mid]=dec(a[i],x),a[i]=adc(a[i],x);
}
if(v==-1) for(int i=0,val=qp(L);i<L;++i) a[i]=1ll*a[i]*val%H;
}
inline poly mul(poly a,poly b){
int L=1,len=(int)a.size()+(int)b.size()-1;
while(L<=len) L<<=1;
a.resize(L),b.resize(L);
for(int i=0;i<L;++i) rev[i]=(rev[i>>1]>>1)|(i&1?L>>1:0);
NTT(a,L,1),NTT(b,L,1);
for(int i=0;i<L;++i) a[i]=1ll*a[i]*b[i]%H;
return NTT(a,L,-1),a.resize(len),a;
}
int n,m,cnt[N],sz[N],fac[N],ifac[N];
poly a,b;
vector<int>E[N];
#define pb push_back
inline void dfs(int x,int fa){
	sz[x]=1;
	for(auto v:E[x])
		if(v!=fa) dfs(v,x),sz[x]+=sz[v],++cnt[sz[v]];
	if(sz[x]!=n) ++cnt[n-sz[x]];
}
inline int C(int n,int m){return fac[n]*ifac[m]%H*ifac[n-m]%H;}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n,fac[0]=ifac[0]=1,iG=qp(G);
for(int i=1,u,v;i<n;++i)
	cin>>u>>v,E[u].pb(v),E[v].pb(u);
for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H,ifac[i]=ifac[i-1]*qp(i)%H;
	dfs(1,0),a.resize(n+1),b.resize(n+1);
	for(int i=0;i<=n;++i) a[i]=ifac[i],b[i]=cnt[i]*fac[i]%H;
	reverse(b.begin(),b.end());
	poly ans=mul(a,b);
	for(int i=1;i<=n;++i)
		cout<<(n*C(n,i)-ifac[i]*ans[n-i]%H+H)%H<<"\n";
}

[ABC214G] Three Permutations

幻视 AGC005D

我们考虑二项式反演,然后问题变成了钦定了 \(i\) 个满足相等关系的方案数。

根据经验,我们考虑 \(p_i\to q_i\) 连边,然后我们发现了若干个环还有一些孤立点,先不考虑独立点,每个环的贡献独立,我们可以分开计算。

然后我们考虑我们一个环里找出 \(i\) 个位置满足 \(i=p_i||i=q_i\) 的方案数,我们考虑就是选出 \(i\) 条边,你可以选一个点,且每个点只能被选 \(1\) 次,然后我们发现,如果我们边转点一下,变成了选 \(i\) 条边匹配的方案数,这个我们考虑破环为链,最边上的边选不选即可,设环大小为 \(c\),则方案数为:

\[\binom {2c-i}i+\binom {2c-i-1}{i-1} \]

Code
// Problem: [ABC214G] Three Permutations
// URL: https://www.luogu.com.cn/problem/AT_abc214_g
// Memory Limit: 1 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-15 19:56:07

#include<bits/stdc++.h>
#define int long long
#define poly vector<int>
#define pb push_back
using namespace std;
const int N=6005,H=1e9+7;
vector<int>G[N];
int n,cnt,p[N],q[N],vis[N],fac[N],ifac[N];
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline void dfs(int x){
	if(vis[x]) return;
	++cnt,vis[x]=1;
	for(auto v:G[x]) dfs(v);
}
inline poly operator*(poly a,poly b){
	poly c((int)a.size()+(int)b.size()-1,0);
	for(int i=0;i<(int)a.size();++i)
		for(int j=0;j<(int)b.size();++j)
			c[i+j]=(c[i+j]+a[i]*b[j])%H;
	return c;
}
inline int C(int n,int m){return n<m||m<0?0:fac[n]*ifac[m]%H*ifac[n-m]%H;}
inline int sig(int x){return x&1?H-1:1;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n,fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H,ifac[i]=ifac[i-1]*qp(i)%H;
	for(int i=1;i<=n;++i) cin>>p[i];
	for(int i=1;i<=n;++i) cin>>q[i],G[p[i]].pb(q[i]),G[q[i]].pb(p[i]);
	poly ans={1};
	for(int i=1;i<=n;++i)
		if(!vis[i]){
			cnt=0,dfs(i);
			poly now(cnt+1,0);
			if(cnt>1) for(int i=0;i<=cnt;++i) now[i]=(C(2*cnt-i,i)+C(2*cnt-i-1,i-1))%H;
			else now[0]=now[1]=1;
			ans=ans*now;
		}
	int res=0;
	for(int i=0;i<=n;++i)
		res=(res+sig(i)*fac[n-i]%H*ans[i])%H;
	cout<<res<<"\n";
}

[ARC080F] Prime Flip

先变成 \(a_i \oplus a_{i-1}\),差为偶数我们发现可以 \(2\) 次解决一对,差为奇数可以 \(1\) 次或者 \(3\) 次解决一对。

我们考虑用网络流匹配 \(1\) 次的,内部匹配 \(2\) 次的,最后如果匹配不完奇偶匹配一次。

Code
// Problem: [ARC080F] Prime Flip
// URL: https://www.luogu.com.cn/problem/AT_arc080_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-17 09:36:07

#include<iostream>
#define int long long
using namespace std;
const int N=2e5+5,INF=1e18;
int n,m;
namespace Flow{
	int S,T,w[N],to[N],cur[N],h[N],nxt[N],tot=1;
	inline void add(int u,int v,int e){
		to[++tot]=v,nxt[tot]=h[u],h[u]=tot,w[tot]=e,
		to[++tot]=u,nxt[tot]=h[v],h[v]=tot,w[tot]=0;
	}
	int Q[N],L,R,d[N];
	inline bool bfs(){
		for(int i=1;i<=T;++i) d[i]=0,cur[i]=h[i];
		d[S]=1,Q[L=R=1]=S;
		while(L<=R){
			int x=Q[L++];
			for(int i=h[x];i;i=nxt[i])
				if(w[i]&&!d[to[i]]) d[Q[++R]=to[i]]=d[x]+1;
		}
		return d[T];
	}
	inline int dfs(int x,int flow){
		if(x==T) return flow;
		int used=0;
		for(int &i=cur[x];i;i=nxt[i])
			if(w[i]&&d[to[i]]==d[x]+1){
				int val=dfs(to[i],min(flow-used,w[i]));
				if(!val) d[to[i]]=0;
				w[i]-=val,w[i^1]+=val;
				if((used+=val)==flow) break;
			}
		return used;	
	}
	inline int Dinic(){
		int ans=0;
		while(bfs()) ans+=dfs(S,INF);
		return ans;
	}
	#define S Flow::S
	#define T Flow::T
	#define add Flow::add
}
int a[N],b[N],tot;
inline int check(int x){
	if(x<=2||!(x&1)) return 0;
	for(int i=3;i*i<=x;i+=2)
		if(!(x%i)) return 0;
	return 1;
}
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];
	b[++tot]=a[1];
	for(int i=2;i<=n;++i)
		if(a[i]-a[i-1]>1) b[++tot]=a[i-1]+1,b[++tot]=a[i];
	b[++tot]=a[n]+1,S=tot+1,T=S+1;
	int c1=0,c2=0;
	for(int i=1;i<=tot;++i)
		if(b[i]&1){
			++c1,add(S,i,1);
			for(int j=1;j<=tot;++j)
				if(!(b[j]&1)&&check(abs(b[i]-b[j]))) add(i,j,1);
		}
		else ++c2,add(i,T,1);
	int mx=Flow::Dinic();
	cout<<mx+(c1-mx)/2*2+(c2-mx)/2*2+((c1-mx)&1)*3<<"\n";
}

[ARC079F] Namori Grundy

我们首先发现图是一个外向基环树,然后一个点的权值是出边集合权值的 mex,然后这个东西我们可以先把非环上的部分算了,然后枚举环上某个点的权值 check,可以证明这个权值只有 \(O(\log n)\),复杂度可以接受。

Code
// Problem: [ARC079F] Namori Grundy
// URL: https://www.luogu.com.cn/problem/AT_arc079_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-17 10:29:51

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2e5+5;
int n,rt,tot=1e9,f[N],col[N],a[N],vis[N],mark[N];
vector<int>G[N];
inline int mex(int x){
int ans=0;
for(auto v:G[x]) vis[a[v]]=1;
while(vis[ans]) ++ans;
for(auto v:G[x]) vis[a[v]]=0;
return ans;
}
inline void dfs1(int x){
for(auto v:G[x])
	if(!col[v]){
		col[v]=col[x],dfs1(v);
		if(mark[v]) mark[x]=1;
	}
	else if(col[v]==col[x]) return mark[x]=1,void();
}
inline void dfs(int x){
for(auto v:G[x]) dfs(v);
a[x]=mex(x);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int x,i=1;i<=n;++i) cin>>x,G[x].pb(i);
for(int i=1;i<=n;++i){
	if(!col[i]) col[i]=i,dfs1(i);
	if(mark[i]) break;
}
for(int i=1;i<=n;++i)
	if(mark[i]){
		vector<int>vec;
		for(auto v:G[i])
			if(mark[v]) f[v]=i;
			else dfs(v),vec.pb(a[v]);
		sort(vec.begin(),vec.end()),vec.erase(unique(vec.begin(),vec.end()),vec.end());
		if(!rt||(int)vec.size()+1<tot) rt=i,tot=(int)vec.size()+1;
	}
for(int i=0;i<=tot+1;++i){
	a[rt]=i;
	for(int x=f[rt];x!=rt;x=f[x]) a[x]=mex(x);
	if(mex(rt)==i) return cout<<"POSSIBLE\n",0;
}
cout<<"IMPOSSIBLE\n";
}

P8292 [省选联考 2022] 卡牌

一眼丁真,考虑根号分治,我们发现,小于 \(\sqrt{2000}\) 的质数只有 \(c=14\) 个,考虑如何计算。

我们将所有质数分成大质数和小质数,我们发现如果我们不关注小质数,我们对于每个大质数算出有多少个数是 \(i\) 的倍数,记作 \(f_i\),然后如果我们需要 \(i\),就乘上 \(2^{f_i}-1\),否则乘上 \(2^{f_i}\) 即可。

现在我们发现出现了小质数,为啥会有问题,我们发现因为彼此不独立,考虑容斥,我们容斥时钦定不含有 \(S\) 集合里面的小质数,然后我们考虑对于每个大质数预处理出不含有 \(S\) 的数的个数,和对于所有数不含有 \(S\) 的个数,然后就做完了。

Code
// Problem: P8292 [省选联考 2022] 卡牌
// URL: https://www.luogu.com.cn/problem/P8292
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Author: Nityacke
// Time: 2024-07-16 19:11:17

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2005,M=17000,H=998244353;
int n,m,ans,id[N],sub[N],p[310],idx,cnt[N],vis[N],pw[1000005];
inline int qp(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%H)
		if(b&1) ans=1ll*ans*a%H;
	return ans;
}
vector<int>vec[N];
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline void add(int &x,int v){x=adc(x,v);}
inline void init(){
	pw[0]=1;
	for(int i=1;i<=1e6;++i) pw[i]=adc(pw[i-1],pw[i-1]);
	for(int i=2;i<N;++i)
		if(!vis[i]){
			id[i]=++idx,p[idx]=i;
			for(int j=i<<1;j<N;j+=i) vis[j]=1;
		}
	for(int i=2;i<N;++i)
		for(int j=1;j<=idx;++j)
			if(i%p[j]==0){
				if(j<=14) sub[i]|=1<<(j-1);
				vec[i].pb(j);
			}
}
int sum[M],f[M][310];
#define pc(x) __builtin_popcount(x)
int Q[18005];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n,init();
	for(int x,i=1;i<=n;++i) cin>>x,++cnt[x];
	cin>>m;
	for(int S=0;S<(1<<14);++S){
		for(int j=2;j<N;++j)
			if(!(sub[j]&S)&&cnt[j])
				f[S][vec[j].back()]+=cnt[j],sum[S]+=cnt[j];
		sum[S]+=cnt[1];
	}
	for(int S,c;m--;){
		ans=S=0,cin>>c;
		for(int i=1;i<=c;++i){
			cin>>Q[i],Q[i]=id[Q[i]];
			if(Q[i]<=14) S|=1<<(Q[i]-1);
		}
		sort(Q+1,Q+c+1);
		for(int T=0;T<(1<<14);++T)
			if((T|S)==S){
				int cnt=sum[T],res=1;
				for(int i=1;i<=c;++i)
					if(Q[i]>14) res=1ll*res*(pw[f[T][Q[i]]]-1)%H,cnt-=f[T][Q[i]];
				res=1ll*res*pw[cnt]%H;
				if(pc(T)&1) add(ans,H-res);
				else add(ans,res);
			}
		cout<<ans<<"\n";
	}
}

[ARC087F] Squirrel Migration

首先根据经典结论,最大权值是 \(\sum_ww\min(n-sz_i,sz_i)\)

然后我们考虑让重心为根,这样就不用考虑 \(\min\) 的问题。

问题变成了所有 \((i,p_i)\) 经过根,但是这个还是不好做,考虑二项式反演,钦定 \(i\) 个路径不经过的方案数,我们发现这个对于重心每个儿子的子树独立,方案数分别为 \(\binom {sz} i^2 i!\)

然后卷积起来即可。

Code
// Problem: [ARC087F] Squirrel Migration
// URL: https://www.luogu.com.cn/problem/AT_arc087_d
// Memory Limit: 512 MB
// Time Limit: 5000 ms
// Author: Nityacke
// Time: 2024-07-16 12:49:53

#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=5005,H=1e9+7;
int n,rt=1,mx[N],sz[N],f[N],fac[N],ifac[N],ans;
vector<int>G[N];
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline void dfs(int x,int fa){
	sz[x]=1;
	for(auto v:G[x])
		if(v!=fa) dfs(v,x),sz[x]+=sz[v],mx[x]=max(mx[x],sz[v]);
	mx[x]=max(mx[x],n-sz[x]);
}
inline int C(int n,int m){return fac[n]*ifac[m]%H*ifac[n-m]%H;}
inline int sig(int x){return x&1?H-1:1;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n,fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H,ifac[i]=ifac[i-1]*qp(i)%H;
	for(int u,v,i=1;i<n;++i)
		cin>>u>>v,G[u].pb(v),G[v].pb(u);
	dfs(1,0),f[0]=1;
	for(int i=2;i<=n;++i)
		if(mx[i]<mx[rt]) rt=i;
	dfs(rt,0);
	for(auto v:G[rt])
		for(int i=n;i;--i)
			for(int j=1;j<=min(sz[v],i);++j)
				f[i]=(f[i]+f[i-j]*C(sz[v],j)%H*C(sz[v],j)%H*fac[j])%H;
	for(int i=0;i<=n;++i) ans=(ans+sig(i)*f[i]%H*fac[n-i])%H;
	cout<<ans<<"\n";
}

[ABC260Ex] Colorfulness

出题人的马被 NTT 测了。

我们先分为两部分,对于 \(k\in [1,n-1]\) 求相邻小球不同有 \(k\) 对的方案数 \(a_k\)

第二部分,计算答案。

\[\begin{aligned} ans_k&=\sum_{i=1}^na_ii^k\\ &=[z^k]\sum_{k=0}^{\infty}\sum_{i=1}^na_ii^kz^k\\ &=[z^k]\sum_{i=1}^na_i\sum_{k=0}^{\infty}i^kz^k\\ &=[z^k]\sum_{i=1}^n\frac {a_i}{1-iz} \end{aligned} \]

然后分治 NTT 计算即可。

我们考虑如何计算 \(a_i\),发现这个不弱于错排,考虑二项式反演。

对于每种颜色,假设有 \(c_i\) 个球,考虑钦定有 \(d_i\) 对相邻的方案数,我们发现是

\[\begin{aligned} \sum_{\sum d=k}\binom {n-k}{c_1-d_1,c_2-d_2\cdots}\prod c_i!\binom {c_i-1}{d_i} \end{aligned} \]

我们拆开式子,发现还是分治 NTT 的形式,然后再 NTT 卷积优化二项式反演即可,就做完了。

Code

// Problem: [ABC260Ex] Colorfulness
// URL: https://www.luogu.com.cn/problem/AT_abc260_h
// Memory Limit: 1 MB
// Time Limit: 8000 ms
// Author: Nityacke
// Time: 2024-07-16 14:42:29

#include<bits/stdc++.h>
#define poly vector<int>
#define int long long
using namespace std;
const int N=1<<19,H=998244353,G=3,iG=(H+1)/G;
int n,m,rev[N],W[N];
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%H)
		if(b&1) ans=1ll*ans*a%H;
	return ans;
}
inline int dec(int x,int v){return x<v?x-v+H:x-v;}
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline void NTT(poly &a,int L,int v){
	for(int i=0;i<L;++i)
		if(rev[i]<i) swap(a[rev[i]],a[i]);
	for(int mid=1,len=2;len<=L;mid=len,len<<=1){
		int w=qp(v==1?G:iG,(H-1)/len);W[0]=1;
		for(int i=1;i<mid;++i) W[i]=1ll*W[i-1]*w%H;
		for(int l=0;l<L;l+=len)
			for(int i=l,x;i<mid+l;++i)
				x=1ll*W[i-l]*a[i+mid]%H,a[i+mid]=dec(a[i],x),a[i]=adc(a[i],x);
	}
	if(v==-1) for(int i=0,val=qp(L);i<L;++i) a[i]=1ll*a[i]*val%H;
}
inline poly operator*(poly a,poly b){
	int L=1,len=(int)a.size()+(int)b.size()-1;
	while(L<=len) L<<=1;
	a.resize(L),b.resize(L);
	for(int i=0;i<L;++i) rev[i]=(rev[i>>1]>>1)|(i&1?L>>1:0);
	NTT(a,L,1),NTT(b,L,1);
	for(int i=0;i<L;++i) a[i]=1ll*a[i]*b[i]%H;
	return NTT(a,L,-1),a.resize(len),a;
}
inline poly Inv(const poly a){
		int st[25],top=0,len=(int)a.size();
		while(len>1) st[++top]=len,len=(len+1)>>1;
		poly t=(poly){qp(a[0])};
		while(top){
				len=st[top--];
				poly b(len);
				for(int i=0;i<len;++i) b[i]=a[i];
				b=b*t,b.resize(len);
				for(int i=0;i<len;++i) b[i]=dec(H,b[i]);
				b[0]=adc(b[0],2),t=b*t,t.resize(len);
		}
		return t;
}
int p[N],idx,cnt[N],a[N],b[N],fac[N],ifac[N];
map<int,int>mp;
poly f[N],A,B;
inline poly solve(int l,int r){
	if(l==r) return f[l];
	int mid=(l+r)>>1;
	return solve(l,mid)*solve(mid+1,r);
}
inline int sig(int x){return x&1?H-1:1;}
poly t[N<<2];
#define k1 (k<<1)
#define k2 (k<<1|1)
#define mid ((l+r)>>1)
inline void pre(int k,int l,int r){
	if(l==r) return t[k]=(poly){1,H-l},void();
	pre(k1,l,mid),pre(k2,mid+1,r),t[k]=t[k1]*t[k2];
}
inline poly operator+(poly a,poly b){
	a.resize(max(a.size(),b.size()));
	for(int i=0;i<(int)b.size();++i) a[i]=adc(a[i],b[i]);
	return a;
}
inline poly calc(int k,int l,int r){
	if(l==r) return (poly){p[l]};
	return calc(k1,l,mid)*t[k2]+calc(k2,mid+1,r)*t[k1];
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m,fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H,ifac[i]=ifac[i-1]*qp(i)%H;
	for(int i=1;i<=n;++i)
		cin>>a[i],mp[a[i]]=1;
	for(auto &v:mp) v.second=++idx;
	for(int i=1;i<=n;++i) ++cnt[mp[a[i]]];
	for(int i=1;i<=idx;++i){
		f[i].resize(cnt[i]);
		for(int j=0;j<cnt[i];++j)
			f[i][j]=fac[cnt[i]-1]*fac[cnt[i]]%H*ifac[cnt[i]-1-j]%H
			*ifac[cnt[i]-j]%H*ifac[j]%H;
	}
	A=solve(1,idx),A.resize(n+1,0),B.resize(n+1);
	for(int i=0;i<=n;++i) A[i]=A[i]*fac[n-i]%H;
	for(int i=0;i<=n;++i) A[i]=A[i]*fac[i]%H,B[i]=sig(i)*ifac[i]%H;
	reverse(A.begin(),A.end()),A=A*B;
	for(int i=0;i<n;++i) p[n-1-i]=A[n-i]*ifac[i]%H;
	idx=n;
	while(idx&&!p[idx]) --idx;
	if(!idx) idx=1;
	pre(1,1,idx);
	poly ans=calc(1,1,idx);
	t[1].resize(m+1),ans=ans*Inv(t[1]);
	for(int i=1;i<=m;++i) cout<<ans[i]<<" ";
}

[ARC134F] Flipping Coins

我们先把排列拆成若干个环,则我们手玩一下发现一个环可以拆成若干个极长上升段,当且仅当一个段长度为奇数才会出现一个硬币是正面的情况。

所以一个极长上升段的 EGF 可以写成 \(F(x)=\sum_{i=0}w^{i\bmod 2}\frac {x^i}{i!}\)

然后我们考虑一个环的 EGF \(G(x)\),然后发现,似乎并不是 \(\sum_{i=1}\frac {F^i}i=\ln(1-F)\),因为我们没法保证极长性质,怎么办捏,谔谔。

我们考虑构造连续段的生成函数 \(C\),我们设一个长度为 \(i\) 的容斥系数为 \(c_i\),则满足:

\[\sum\prod c_i=f_i \]

所以设 \(H(x)=\sum_{i=0}w^{i\bmod 2}x^i\),则

\[\frac 1 {1-C(x)}=H(x)\\ C(x)=1-\frac 1 {H(x)} \]

然后我们发现我们需要 \(C\) 的 EGF,所以:

\[Ans=\exp G(x)=\exp (\ln(1-C_e))=1-C_e=(\frac 1 {H(x)})_e \]

Code
// Problem: [ARC134F] Flipping Coins
// URL: https://www.luogu.com.cn/problem/AT_arc134_f
// Memory Limit: 1 MB
// Time Limit: 5000 ms
// Author: Nityacke
// Time: 2024-07-16 16:07:43

#include<bits/stdc++.h>
#define poly vector<int>
#define int long long
using namespace std;
const int N=1<<19,H=998244353,G=3,iG=(H+1)/G;
int n,w,rev[N],W[N];
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%H)
		if(b&1) ans=1ll*ans*a%H;
	return ans;
}
inline int dec(int x,int v){return x<v?x-v+H:x-v;}
inline int adc(int x,int v){return x+v>=H?x+v-H:x+v;}
inline void NTT(poly &a,int L,int v){
	for(int i=0;i<L;++i)
		if(rev[i]<i) swap(a[rev[i]],a[i]);
	for(int mid=1,len=2;len<=L;mid=len,len<<=1){
		int w=qp(v==1?G:iG,(H-1)/len);W[0]=1;
		for(int i=1;i<mid;++i) W[i]=1ll*W[i-1]*w%H;
		for(int l=0;l<L;l+=len)
			for(int i=l,x;i<mid+l;++i)
				x=1ll*W[i-l]*a[i+mid]%H,a[i+mid]=dec(a[i],x),a[i]=adc(a[i],x);
	}
	if(v==-1) for(int i=0,val=qp(L);i<L;++i) a[i]=1ll*a[i]*val%H;
}
inline poly operator*(poly a,poly b){
	int L=1,len=(int)a.size()+(int)b.size()-1;
	while(L<=len) L<<=1;
	a.resize(L),b.resize(L);
	for(int i=0;i<L;++i) rev[i]=(rev[i>>1]>>1)|(i&1?L>>1:0);
	NTT(a,L,1),NTT(b,L,1);
	for(int i=0;i<L;++i) a[i]=1ll*a[i]*b[i]%H;
	return NTT(a,L,-1),a.resize(len),a;
}
inline poly Inv(const poly a){
		int st[25],top=0,len=(int)a.size();
		while(len>1) st[++top]=len,len=(len+1)>>1;
		poly t=(poly){qp(a[0])};
		while(top){
				len=st[top--];
				poly b(len);
				for(int i=0;i<len;++i) b[i]=a[i];
				b=b*t,b.resize(len);
				for(int i=0;i<len;++i) b[i]=dec(H,b[i]);
				b[0]=adc(b[0],2),t=b*t,t.resize(len);
		}
		return t;
}
poly a;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>w,a.resize(n+1);
	for(int i=0;i<=n;++i) a[i]=(i&1?w:1);
	a=Inv(a);
	for(int i=1,Now=1;i<=n;++i) Now=Now*qp(i)%H,a[i]=a[i]*Now%H;
	a=Inv(a);
	int ans=a[n];
	for(int i=1;i<=n;++i) ans=ans*i%H;
	cout<<ans<<"\n";
}

CF1109D Sasha and Interesting Fact from Graph Theory

我们考虑枚举 \(a,b\) 之间 \(i\) 条边,然后接下来就是 \(n\) 个有标号点,\(i+1\) 棵有根树的森林计数。

\(T\) 是有标号有根树的 EGF,则我们知道:

\[T(z)=z\exp T(z)\\ z=\frac {T(z)}{\exp T(z)} \]

然后我们就需要 \(n![z^n]T(z)^k\)

lagrange 反演,启动:

\[\begin{aligned} n![z^n]H(T(z))&=(n-1)![z^{n-1}]H'(z)(\frac z {G(z)})^{n}\\ &=(n-1)![z^{n-1}]kz^{k-1}e^{nz}\\ &=kn^{n-k-1}n^{\underline k} \end{aligned} \]

由于我们钦定了根,所以还需要除以 \(n^{\underline k}\)
然后我们推一下式子:

\[\begin{aligned} Ans=\sum_{i=1}^{n-1}\binom {m-1}{i-1}\binom {n-2} {i-1}(i-1)! m^{n-i-1}(i+1)n^{n-i-2} \end{aligned} \]

Code
// Problem: Sasha and Interesting Fact from Graph Theory
// URL: https://www.luogu.com.cn/problem/CF1109D
// Memory Limit: 250 MB
// Time Limit: 2500 ms
// Author: Nityacke
// Time: 2024-07-18 15:13:46

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,H=1e9+7;
int fac[N],ifac[N],n,m,a,b;
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline int C(int n,int m){return n<m||m<0?0:fac[n]*ifac[m]%H*ifac[n-m]%H;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>a>>b,fac[0]=1;
	for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H;
	ifac[N-1]=qp(fac[N-1]);
	for(int i=N-1;i;--i) ifac[i-1]=ifac[i]*i%H;
	int ans=0;
	for(int i=1;i<n;++i){
		int val=C(m-1,i-1)*C(n-2,i-1)%H*fac[i-1]%H*qp(m,n-1-i)%H;
		if(i!=n-1) val=val*(i+1)%H*qp(n,n-i-2);
		ans=(ans+val)%H;
	}
	cout<<ans<<"\n";
}

CF1327F AND Segments

首先拆位,然后对于为 \(1\) 的区间,他们必须全部为 \(1\),然后计算 \(f_{i,j}\) 表示考虑到第 \(i\) 个数,上一个 \(0\)\(j\) 的方案数,然后这个可以用个队列做,然后就做完了。

Code
// Problem: AND Segments
// URL: https://www.luogu.com.cn/problem/CF1327F
// Memory Limit: 250 MB
// Time Limit: 3000 ms
// Author: Nityacke
// Time: 2024-07-22 14:46:08

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,H=998244353;
int n,k,m,a[N],L[N],R[N],V[N],f[N],pos[N];
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
inline void dec(int &x,int v){if((x-=v)<0) x+=H;}
inline int solve(int x){
	for(int i=1;i<=n+1;++i) a[i]=pos[i]=0;
	for(int i=1;i<=m;++i)
		if(V[i]>>x&1) ++a[L[i]],--a[R[i]];
		else pos[R[i]]=max(pos[R[i]],L[i]);
	for(int i=1;i<=n+1;++i) a[i]+=a[i-1],pos[i]=max(pos[i],pos[i-1]),f[i]=0;
	f[0]=1;
	int sum=1,l=0;
	for(int i=1;i<=n+1;++i){
		while(l<pos[i]) dec(sum,f[l]),f[l++]=0;
		f[i]=a[i]?0:sum,add(sum,f[i]);
	}
	return f[n+1];
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>k>>m;
	for(int i=1;i<=m;++i) cin>>L[i]>>R[i]>>V[i],R[i]++;
	int ans=1;
	for(int i=0;i<k;++i) ans=1ll*ans*solve(i)%H;
	cout<<ans<<"\n";
}

CF1584F Strange LCS

我们考虑对于一个字符的时候,我们发现如果对于所有串 \(pos_x<pos_y\),则 \(x\) 可以转移到 \(y\),对于这个题我们考虑状压用过的 \(pos\),然后 dp 即可。

Code
// Problem: Strange LCS
// URL: https://www.luogu.com.cn/problem/CF1584F
// Memory Limit: 250 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-22 15:47:06

#include<bits/stdc++.h>
using namespace std;
const int N=10,S=115;
int T,n,len[N],mp[130],AP,AS;
int f[S][1<<N],buc[N][S],p[N][S][2];
pair<int,int> tr[S][1<<N];
char s[N][S];
inline void print(int id,int S){
	if(id) print(tr[id][S].first,tr[id][S].second),cout<<s[0][id-1];
}
inline void solve(){
	cin>>n,memset(f,0,sizeof f),memset(buc,0,sizeof buc),AP=AS=0;
	for(int i=0;i<n;++i){
		cin>>s[i],len[i]=strlen(s[i]);
		for(int j=0;j<len[i];++j){
			int id=mp[(int)s[i][j]];
			p[i][id][buc[i][id]++]=j;
		}
	}
	for(int i=0;i<len[0];++i)
		for(int S=0;S<(1<<n);++S) if(!i||f[i][S])
			for(int j=i+1;j<=len[0];++j){
				int fl=1,msk=0,pre=i?mp[(int)s[0][i-1]]:0,now=mp[(int)s[0][j-1]];
				for(int o=0;o<n&&fl;++o){
					int b=S>>o&1,d=-1;
					if(!buc[o][now]||(i&&buc[o][pre]<=b)){fl=0;break;}
					if(!i) continue;
					for(int k=0;k<buc[o][now]&&d==-1;++k)
						if(p[o][now][k]>p[o][pre][b]) d=k;
					if(~d) msk|=d<<o;
					else fl=0;
				}
				int v=!i?1:f[i][S]+1;
				if(fl&&f[j][msk]<v){
					f[j][msk]=v,tr[j][msk]={i,S};
					if(v>f[AP][AS]) AP=j,AS=msk;
				}
			}
	cout<<f[AP][AS]<<"\n",print(AP,AS),cout<<"\n";
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	for(int i='a';i<='z';++i) mp[i]=i-'a';
	for(int i='A';i<='Z';++i) mp[i]=i-'A'+26;
	while(T--) solve();
}

P4649 [IOI2007] training 训练路径

我们首先把问题转化成选若干条不交路径权值最大,然后我们考虑怎么做这个问题。

发现每个点度数很小,然后我们考虑设 \(f_{x,S}\) 表示不考虑 \(x\) 来自 \(S\) 子树的贡献,每个路径在 LCA 处统计贡献,这个是好计算的,时间复杂度 \(O(2^10n+mn)\)

Code
// Problem: P4649 [IOI2007] training 训练路径
// URL: https://www.luogu.com.cn/problem/P4649
// Memory Limit: 62 MB
// Time Limit: 300000 ms
// Author: Nityacke
// Time: 2024-07-23 14:32:10

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=(1<<10)+5;
int n,m,cnt,Sum,f[N],dep[N],dp[N][N];
int son[N][15],rid[N];
vector<int>G[N],S[N];
struct edge{int a,b,val;}E[N*5];
inline void dfs(int x,int fa){
	dep[x]=dep[f[x]=fa]+1;
	for(auto v:G[x])
		if(v!=fa) dfs(v,x);
}
inline void solve(int x){
	int tot=0;
	for(auto v:G[x]) if(v!=f[x]) solve(v),rid[v]=1<<tot,son[x][tot]=v,++tot;
	for(int S=0;S<(1<<tot);++S)
		for(int i=0;i<tot;++i)
			if(!(S>>i&1)) dp[x][S]+=dp[son[x][i]][0];
	for(auto id:S[x]){
		int val=E[id].val,a=E[id].a,b=E[id].b;
		if(a!=x){
			val+=dp[a][0];
			for(;f[a]!=x;a=f[a]) val+=dp[f[a]][rid[a]];
		}
		if(b!=x){
			val+=dp[b][0];
			for(;f[b]!=x;b=f[b]) val+=dp[f[b]][rid[b]];
		}
		for(int S=0;S<(1<<tot);++S)
			if(!(S&rid[a])&&!(S&rid[b])) 
				dp[x][S]=max(dp[x][S],dp[x][S|rid[a]|rid[b]]+val);
	}
}
inline int lca(int u,int v){
	while(u!=v)
		if(dep[u]>=dep[v]) u=f[u];
		else v=f[v];
	return u;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int a,b,c,i=1;i<=m;++i){
		cin>>a>>b>>c;
		if(!c) G[a].pb(b),G[b].pb(a);
		else E[++cnt]={a,b,c},Sum+=c;
	}
	dfs(1,0);
	for(int i=1;i<=cnt;++i)
		if(!((dep[E[i].a]+dep[E[i].b])&1)) S[lca(E[i].a,E[i].b)].pb(i);
	solve(1),cout<<Sum-dp[1][0]<<"\n";
}

CF1542E2 Abnormal Permutation Pairs (hard version)

我们考虑先枚举 LCP 长度为 \(i\),然后后面的部分不难发现还是相当于一个 \(n-i\) 阶排列,枚举 \(p_{i+1}=a,q_{i+1}=b\),则 \(a<b\),然后我们发现后面相当于 \(n-i-1\) 阶排列,然后 \(p,q\) 分别有了 \(a-1,b-1\) 个逆序对,然后我们只需要对于后面的排列 dp,然后你写出生成函数随便做一做就好了。

Code
// Problem: Abnormal Permutation Pairs (hard version)
// URL: https://www.luogu.com.cn/problem/CF1542E2
// Memory Limit: 500 MB
// Time Limit: 4000 ms
// Author: Nityacke
// Time: 2024-07-23 08:37:50

#include<bits/stdc++.h>
using namespace std;
const int N=505,B=251*505;
int n,H,A[N],ans,_f[2][N*N],*f[2],suf[N*N];
inline void add(int &x,int v){if((x+=v)>=H) x-=H;}
signed main(){
	cin>>n>>H,f[0]=_f[0]+B,f[1]=_f[1]+B,f[0][0]=A[0]=1;
	for(int i=1;i<=n;++i) A[i]=1ll*(n-i+1)*A[i-1]%H;
	for(int i=1;i<n;++i){
		int C=(i+1)*(i+1)/2;
		for(int j=-C;j<=C;++j) f[i&1][j]=0;
		for(int j=-C;j<=C;++j)
			if(f[(i-1)&1][j])
			add(f[i&1][j+i+1],f[(i-1)&1][j]),
			add(f[i&1][j-i+1],f[(i-1)&1][j]),
			add(f[i&1][j+1],1ll*(H-2)*f[(i-1)&1][j]%H);
		for(int o=2;o;--o)
			for(int j=-C+1;j<=C;++j) add(f[i&1][j],f[i&1][j-1]);
		suf[C+1]=0;
		for(int j=C;~j;--j) suf[j]=suf[j+1],add(suf[j],f[i&1][j]);
		for(int a=0;a<=i;++a)
			for(int b=a+1;b<=i;++b)
				add(ans,1ll*A[n-i-1]*suf[b-a+1]%H);
	}
	cout<<ans<<"\n";
}

P5912 [POI2004] JAS

就是求深度最小的点分树深度。

我们考虑点分树有什么性质,假设我们存在两个点 \(x,y\) 满足 \(d_x=d_y\),则一定存在一个点 \(z\in (x,y)\)\(d_z<d_x\),原因显然。

然后对于这个题,我们假设 \(d_x\) 表示还需要 \(d_x\) 次询问,则我们发现,我们可以在树上 dp,维护子树内的 \(d\)\(S\) 集合,然后合并的时候分讨找出最小的即可,时间复杂度可以做到 \(O(n)\)

Code
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=5e4+5,K=1<<17;
int n,S[N],f[N];
vector<int>G[N];
inline int lg(int x){return !x?0:__lg(x);}
inline void dfs(int x,int fa){
	int sum=0,msk=0;
	if(G[x].size()==1&&fa) return S[x]=1,void();
	for(auto v:G[x])
		if(v!=fa){
		dfs(v,x),f[x]=max(f[x],f[v]);
		msk|=sum&S[v],sum|=S[v];
	}
	msk=(K-(1<<lg(msk)))&(K-1-sum);
	int v=__lg(msk&-msk);
	f[x]=max(f[x],v),S[x]=(sum&(K-(1<<v)))|(1<<v);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int u,v,i=1;i<n;++i)
		cin>>u>>v,G[u].pb(v),G[v].pb(u);
	dfs(1,0),cout<<f[1]<<"\n";
}

CF1119F Niyaz and Small Degrees

我们先考虑对于 \(x=1\) 的情况,考虑 dp,维护 \(f_{x,0},f_{x,1}\) 表示断/不断 \(x\) 与其父亲的边,然后你可以拉个堆维护最值。

对于 \(x\in [1,n]\) 的情况,我们发现 \(\sum deg_i=O(n)\),所以如果我们只访问度数大于 \(x\) 的点复杂度就很对,然后我们动态删一些点,在合法点的连通块中 dp 即可。

Code
// Problem: Niyaz and Small Degrees
// URL: https://www.luogu.com.cn/problem/CF1119F
// Memory Limit: 250 MB
// Time Limit: 3000 ms
// Author: Nityacke
// Time: 2024-07-23 18:56:22

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n,ans[N],d[N],id[N],vis[N],Now,f[N][2];
#define pi pair<int,int>
#define fi first
#define se second
#define pb push_back
vector<pi>e[N];
struct Heap{
	priority_queue<int>Q1,Q2;
	int sum=0;
	inline void push(int x){Q1.push(x),sum+=x;}
	inline void del(int x){Q2.push(x),sum-=x;}
	inline int top(){
		while(Q1.size()&&Q2.size()&&Q1.top()==Q2.top()) Q1.pop(),Q2.pop();
		return Q1.top();
	}
	inline void pop(){sum-=top(),Q1.pop();}
	inline int size(){return Q1.size()-Q2.size();}
}h[N];
inline void del(int x){
	for(auto [v,w]:e[x])
		if(d[v]>Now) h[v].push(w);
}
inline void dfs(int x,int fa){
	vis[x]=1;
	int deg=d[x]-Now,res=0;
	while(e[x].size()&&d[e[x].back().fi]<=Now) e[x].pop_back();
	for(auto [v,w]:e[x])
		if(v!=fa) dfs(v,x);
	while(h[x].size()>deg) h[x].pop();
	vector<int>t1,t2;
	for(auto [v,w]:e[x])
		if(v!=fa){
			if(f[v][1]+w<=f[v][0]) --deg,res+=f[v][1]+w;
			else res+=f[v][0],h[x].push(w+f[v][1]-f[v][0]),t1.pb(w+f[v][1]-f[v][0]);
		}
	while(h[x].size()&&h[x].size()>deg) t2.pb(h[x].top()),h[x].pop();
	f[x][0]=h[x].sum+res;
	while(h[x].size()&&h[x].size()>deg-1) t2.pb(h[x].top()),h[x].pop();
	f[x][1]=h[x].sum+res;
	for(auto v:t2) h[x].push(v);
	for(auto v:t1) h[x].del(v);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int u,v,w,i=1;i<n;++i)
		cin>>u>>v>>w,e[u].pb({v,w}),e[v].pb({u,w}),
		++d[u],++d[v],ans[0]+=w;
	for(int i=1;i<=n;++i) id[i]=i;
	sort(id+1,id+n+1,[&](int x,int y){return d[x]<d[y];});
	for(int i=1;i<=n;++i) 
		sort(e[i].begin(),e[i].end(),[&](pi a,pi b){return d[a.fi]>d[b.fi];});
	int Idx=1;
	for(Now=1;Now<=n;++Now){
		while(Idx<=n&&d[id[Idx]]==Now) del(id[Idx++]);
		for(int j=Idx;j<=n;++j) vis[id[j]]=0;
		for(int j=Idx;j<=n;++j) 
			if(!vis[id[j]]) dfs(id[j],0),ans[Now]+=f[id[j]][0];
	}
	for(int i=0;i<n;++i) cout<<ans[i]<<" ";
}

[AGC065D] Not Intersect

不是很困难的题。

组合意义天地灭,代数推导保平安。

首先破环为链,分别有这条被破的边选和不选的两种情况。

然后我们设 \(f_{n,m}\) 表示一条长度为 \(n+1\) 的链,且 \(1\)\(n\) 之间不能连边的方案数。

然后我们枚举第 \(n+1\) 个点有无边相连

  • 没有边的情况,考虑 \(1\)\(n\) 之间可能有边,\(f_{n-1,m-1}+f_{n-1,m}\)
  • 有边的情况,枚举最左边的一条 \((i,n+1)\),然后考虑 \((1,i)\) 之间有无边,\(\sum_{i=1}^{n-1}\sum_{x=0}^{m-1}f_{i,x}f_{n-i+1,m-x-1}+\sum_{i=1}^{n-1}\sum_{x=0}^{m-2}f_{i,x}f_{n-i+1,m-x-2}\)

答案就是 \(f_{n,m}+f_{n,m-1}\),这样我们就有了 \(O(n^4)\) 做法。

考虑优化,令生成函数 \(F(x,y)=\sum_{n\ge 0}\sum_{m\ge 0}f_{n,m}x^ny^m\)

则我们知道:

\[\begin{aligned} F&=Fx+Fxy+F^2y+F^2y^2+x\\ x&=\frac {F-F^2y-F^2y^2}{Fy+F+1} \end{aligned} \]

然后 lagrange 反演:

\[\begin{aligned} [x^n]F&=\frac 1 n[x^{n-1}](\frac {x(xy+x+1)}{x-x^2y-x^2y^2})^n\\ &=\frac 1 n[x^{n-1}]((y+1)x+1)^n(\frac 1 {1-y(y+1)x})^n\\ &=\frac 1 n\sum_{a=0}^{n-1}[x^{n-1-a}]((y+1)x+1)^n[x^a](\frac 1 {1-y(y+1)x})^n\\ &=\frac 1 n\sum_{a=0}^{n-1}\binom n {n-1-a}(y+1)^{n-1-a}\binom {n-1+a}{n-1}y^a(y+1)^{a}\\ &=\frac 1 n\sum_{a=0}^{n-1}\binom {n-1+a}{n-1}\binom n {n-1-a}(y+1)^{n-1}y^a \end{aligned} \]

所以

\[\begin{aligned} [x^ny^m]F(x,y)&=\frac 1 n\sum_{a=0}^{n-1}\binom {n-1+a}{n-1}\binom n {n-1-a}[y^{m-a}](y+1)^{n-1}\\ &=\frac 1 n\sum_{a=0}^{n-1}\binom {n-1+a}{n-1}\binom n {n-1-a}\binom {n-1}{m-a} \end{aligned} \]

然后可以 \(O(n)\) 计算。

似乎有高妙组合意义做法。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e7+5,H=1e9+7;
int n,m,fac[N],ifac[N];
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline int C(int n,int m){return n<m||m<0?0:fac[n]*ifac[m]%H*ifac[n-m]%H;}
inline int f(int n,int m){
	int ans=0;
	for(int a=0;a<=n-1;++a)
		ans=(ans+C(n,a+1)*C(n-1+a,a)%H*C(n-1,m-a))%H;
	return ans*qp(n)%H;
}
signed main(){
	cin>>n>>m,fac[0]=1;
	if(!m) return cout<<"1\n",0;
	for(int i=1;i<N;++i) fac[i]=fac[i-1]*i%H;
	ifac[N-1]=qp(fac[N-1]);
	for(int i=N-1;i;--i) ifac[i-1]=ifac[i]*i%H;
	cout<<(f(n-1,m-1)+f(n-1,m))%H<<"\n";
}

CF1874D Jellyfish and Miku

我们先来推一下式子,设 \(f(i)\) 表示从 \(0 \to i\) 的期望,然后我们解一下方程。

\[\begin{aligned} f(i+1)&=f(i)+1+\frac {a_i}{a_{i+1}+a_i}(f(i+1)-f(i-1))\\ \frac {a_{i+1}}{a_i+a_{i+1}}f(i+1)&=1+f(i)-\frac {a_i}{a_i+a_{i+1}}f(i-1)\\ g(i+1)&=f(i+1)-f(i)\\ \frac {a_{i+1}}{a_i+a_{i+1}}g(i+1)&=1+\frac {a_i}{a_i+a_{i+1}}g(i)\\ g(i+1)&=\frac {a_i}{a_{i+1}}g(i)+\frac {a_i}{a_{i+1}}+1 \end{aligned} \]

然后归纳得到

\[\begin{aligned} g(i)&=1+\sum_{j=1}^{i-1}\frac {a_j}{a_i}\\ f(n)&=n+\sum_{1\le i<j\le n}\frac {a_i}{a_j} \end{aligned} \]

然后我们可以感觉到 \(a\) 不降,证明可以邻项交换。

然后 dp 即可,时间复杂度 \(O(nm\log m)\)

Code
// Problem: Jellyfish and Miku
// URL: https://www.luogu.com.cn/problem/CF1874D
// Memory Limit: 500 MB
// Time Limit: 2000 ms
// Author: Nityacke
// Time: 2024-07-25 11:03:55

#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=3e3+5;
db f[N][N];
int n,m;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=0;i<=n;++i)
		for(int j=0;j<=m;++j) f[i][j]=1e18;
	f[0][0]=0;
	for(int i=0;i<n;++i)
		for(int j=0;j<=m;++j)
			for(int k=1;k<=m/(n-i);++k)
			if(j+k<=m) f[i+1][j+k]=min(f[i+1][j+k],f[i][j]+(1.0*j/k));
	printf("%.9lf\n",n+2*f[n][m]);
}

连续段 DP

对于一些排列计数和最优化问题,我们考虑这个做法。

我们从小到大或者从大到小,每次加入一个点的时候,我们存在几种情况。

  • 单独作为一个连续段。
  • 加入一个连续段的左边或者右边。
  • 合并两个连续段。

你也可以从笛卡尔树的角度考虑,发现就是

  • 增加一个叶子。
  • 找一个左儿子或者右儿子。
  • 同时拥有左儿子或者右儿子。

P5999 [CEOI2016] kangaroo

我们考虑我们加入一个点可以怎么办。

  • 加入一个连续段,可以放在的位置数是 \(j+1\) 个,\(f_{i-1,j+1}\gets f_{i-1,j}\times (j+1)\)
  • 加入一个连续段一段,我们发现这个会导致出现 \(a,b,c\) 连续且 \(a>b>c\),不合法。
  • 合并两个连续段,有 \(j-1\) 个位置,\(f_{i-1,j-1}\gets (j-1)\times f_{i-1,j}\)

然后对于 \(i>s\)\(i>t\) 不能在首尾开连通块,特殊处理一下。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+5,H=1e9+7;
int f[N][N],n,S,T;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>S>>T,f[1][1]=1;
	for(int i=2;i<=n;++i)
		for(int j=1;j<=i;++j)
			if(i!=S&&i!=T){
				f[i][j]=(j-(i>S)-(i>T))*f[i-1][j-1];
				f[i][j]=(f[i][j]+j*f[i-1][j+1])%H;
			}
			else f[i][j]=(f[i-1][j]+f[i-1][j-1])%H;
	cout<<f[n][1]<<"\n";
}

P9197 [JOI Open 2016] 摩天大楼

我们考虑离散化之后我们可以把 \(|a-b|\) 拆成 \(\sum_{i=l}^rA_{i+1}-A_i\),然后我们同时使用费用提前计算的思想即可。

我们发现合并的时候只跟与外界有连接的端点数有关系,就是连续段数减去边缘的 \(a_1,a_n\) 是否确定,然后我们把边缘有几个端点压进状态即可。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=105,M=1005,H=1e9+7;
int n,m,a[N],f[2][N][M][3];
inline void add(int &x,int v){x=(x+v)%H;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,f[1][0][0][0]=1;
	if(n==1) return cout<<"1\n",0;
	for(int i=1;i<=n;++i) cin>>a[i];
	sort(a+1,a+n+1);
	for(int i=0;i<n;++i){
		for(int j=0;j<=i;++j)
			for(int k=0;k<=m;++k)
				for(int l=0;l<=2;++l){
					int K=k+(2*j-l)*(a[i+1]-a[i]),v=f[1][j][k][l];
					if(K<0||K>m||!v) continue;
					add(f[0][j+1][K][l],v*(j+1-l)),add(f[0][j][K][l],v*(2*j-l));
					if(j) add(f[0][j-1][K][l],v*(j-1));
					if(l<2) add(f[0][j+1][K][l+1],v*(2-l)),add(f[0][j][K][l+1],v*(2-l));
				}
		memcpy(f[1],f[0],sizeof f[1]),memset(f[0],0,sizeof f[0]);
	}
	int ans=0;
	for(int i=0;i<=m;++i) add(ans,f[1][1][i][2]);
	cout<<ans<<"\n";
}

CF1153F Serval and Bonus Problem

先考虑范围为 \([0,1)\) 的情况,乘上 \(l\) 就是答案。

然后我们对于每个位置 \(x\),枚举被覆盖的次数及其概率,则答案是:

\[\begin{aligned} ans&=\int_0^1\sum_{i=k}^n\binom n i(2x(1-x))^i(1-2x(1-x))^{n-i}\mathrm dx\\ &=\sum_{i=k}^n\binom n i\int_0^12^ix^i(1-x)^i\sum_{j=0}^{n-i}(-1)^j\binom {n-i}j2^jx^j(1-x)^j\mathrm dx\\ &=\sum_{i=k}^n\sum_{j=0}^{n-i}(-1)^j2^{i+j}\binom n i\binom {n-i}j\int_0^1x^{i+j}(1-x)^{i+j}\mathrm dx\\ &=\sum_{i=k}^n\sum_{j=0}^{n-i}(-1)^j2^{i+j}\binom n {i+j}\binom {i+j}j\int_0^1x^{i+j}(1-x)^{i+j}\mathrm dx\\ &=\sum_{i=k}^n2^i\binom n i\int_0^1x^i(1-x)^i\mathrm dx\sum_{j=0}^{i-k}(-1)^j\binom i j\\ \end{aligned} \]

我们发现

\[\begin{aligned} \int_0^1x^i(1-x)^i\mathrm dx&=\Beta(i+1,i+1)\\ \Beta(p,q)&=\frac {\Gamma(p)\Gamma(q)}{\Gamma(p+q)}=\frac{(p-1)!(q-1)!}{(p+q-1)!} \end{aligned} \]

此时就可以做到 \(O(n^2)\) 了。
然后我们观察一下后面部分:

\[\begin{aligned} \sum_{j=0}^{i-k}(-1)^j\binom i j&=\sum_{j=0}^{i-k}(-1)^j(\binom {i-1}j+\binom {i-1}{j-1})\\ &=\sum_{j=0}^{i-k}(-1)^j\binom {i-1}j+\sum_{j=0}^{i-k}(-1)^j\binom {i-1}{j-1}\\ &=\sum_{j=0}^{i-k}(-1)^j\binom {i-1}j-\sum_{j=0}^{i-k}(-1)^{j-1}\binom {i-1}{j-1}\\ &=\sum_{j=0}^{i-k}(-1)^j\binom {i-1}j-\sum_{j=0}^{i-k-1}(-1)^{j}\binom {i-1}{j}\\ &=(-1)^{i-k}\binom {i-1}{i-k} \end{aligned} \]

然后可以做到 \(O(n)\)

Code
// Problem: Serval and Bonus Problem
// URL: https://www.luogu.com.cn/problem/CF1153F
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author: Nityacke
// Time: 2024-07-29 10:39:39

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3+5,H=998244353;
int fac[N],ifac[N],pw[N],n,k,L;
inline int qp(int a,int b=H-2){
	int ans=1;
	for(;b;b>>=1,a=a*a%H)
		if(b&1) ans=ans*a%H;
	return ans;
}
inline void init(){
	fac[0]=pw[0]=1;
	for(int i=1;i<N;++i) 
		pw[i]=(pw[i-1]+pw[i-1])%H,fac[i]=fac[i-1]*i%H;
	ifac[N-1]=qp(fac[N-1]);
	for(int i=N-1;i;--i) ifac[i-1]=ifac[i]*i%H;
}
inline int Beta(int p,int q){return fac[p-1]*fac[q-1]%H*ifac[p+q-1]%H;}
inline int C(int n,int m){return n<m||m<0?0:fac[n]*ifac[m]%H*ifac[n-m]%H;}
inline int sig(int x){return x&1?H-1:1;}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>k>>L,init();
	int ans=0;
	for(int i=k;i<=n;++i)
		ans=(ans+sig(i-k)*C(i-1,i-k)%H*C(n,i)%H*pw[i]%H*Beta(i+1,i+1))%H;
	cout<<ans*L%H<<"\n";
}
posted @ 2024-07-29 21:21  Nityacke  阅读(43)  评论(0)    收藏  举报