2023.7.3

A

求把一张无向图的所有边变为有向边,使得各点出度为 \(1\) 的方案数。

答案对 \(998244353\) 取模。

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

首先一定有 \(n=m\),然后环上的边有两种取法。

把环数找出来,然后判断这个连通块中 \(E\) 是否等于 \(2V\).

#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define Mod 998244353
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,m;
int head[N],ver[N<<1],nxt[N<<1],tot,go[N<<1];
int edge,vertex;
void add(int u,int v){
	nxt[++tot]=head[u];
	ver[tot]=v;
	head[u]=tot;
}
bool vis[N];
int ans=1;
void dfs(int u,int pre){
	vis[u]=true,vertex++;
	for(int i=head[u],v;i;i=nxt[i]){
		edge++;
		if((v=ver[i])==pre)continue;
		if(!vis[v])go[i]=true,dfs(v,u);
	}
}
int main(){
	n=read(),m=read();
	for(int u,v;m;m--){
		u=read(),v=read();
		add(u,v),add(v,u);
	}
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		edge=vertex=0,dfs(i,0);
		if(edge!=vertex*2){
			puts("0");
			return 0;
		}
	}
	for(int i=1;i<=tot;i+=2){
		if(!go[i]&&!go[i+1])(ans<<=1)%=Mod;
	}
	printf("%d\n",ans);
	
	return 0;
}

B

一个排列 \(P\) 的价值为其形成的所有环长的 \(\rm lcm\)\(k\) 次方。

给定 \(n,k\),求 \(\sum_{P}val(P)\).

答案对 \(998244353\) 取模。

\(2\le n\le 50\)\(1\le k\le 10^4\).

思考 \(\{a_1,a_2,\dots,a_n\}\) 为环长为 \(i\) 的环数。

合法的 \(\{a\}\)\(204226\) 种。

对于每个 \(\{a\}\) 计算其价值。

\[\text{lcm}^k\cdot\binom{n}{a_1}\cdot 1\cdot\binom{n-a_1}{2a_2}\cdot\binom{2a_2}{2}\cdot\binom{2(a_2-1)}{2}\dots\frac{1}{a_2!}\dots \]

里面有一坨东西记为

\[f(i,j)=\binom{ij}{j}\cdot\binom{(i-1)j}{j}\dots\binom{j}{j}\cdot\frac{1}{i!} \]

这个东西可以递推。

\[\text{lcm}^k\cdot\binom{n}{a_1}\cdot f(a_1,1)\cdot\binom{n-a_1}{2a_2}\cdot f(a_2,2)\dots \]

\(\rm dfs\) 里面记录一下当前价值和 \(\rm lcm\) 即可。

#include<bits/stdc++.h>
#define ll long long
#define N 55
#define Mod 998244353
using namespace std;
ll qpow(ll k,ll b,ll p){
	ll ret=1;
	while(b){
		if(b&1)(ret*=k)%=p;
		(k*=k)%=p,b>>=1; 
	}
	return ret;
}
ll n,k,ans;
ll fac[N],pw[N],C[N][N],f[N][N];
ll inv[N];
void init(){
	fac[0]=C[0][0]=inv[0]=1;
	for(int i=1;i<=n;i++){
		C[i][0]=C[i][i]=pw[i]=1;
		fac[i]=fac[i-1]*i%Mod,inv[i]=qpow(fac[i],Mod-2,Mod);
		for(int j=1;j<=k;j++)
			(pw[i]*=i)%=Mod;
		for(int j=1;j<i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%Mod;
	}
	for(int j=1;j<=n;j++){
		f[0][j]=1;
		for(int i=1;i<=n/j;i++)
			f[i][j]=f[i-1][j]*C[i*j][j]%Mod*fac[j-1]%Mod;
	}
	for(int j=1;j<=n;j++)
		for(int i=1;i<=n/j;i++)
			(f[i][j]*=inv[i])%=Mod;
}
void dfs(ll lst,ll cur,ll lcm,ll sum){
	if(!lst){
		(ans+=sum)%=Mod;
		return;
	}
	if(cur==n+1||lst<cur)return;
	dfs(lst,cur+1,lcm,sum);
	ll add=cur/__gcd(lcm,cur);
	for(int i=1;i<=n/cur;i++)
		dfs(lst-cur*i,cur+1,lcm*add,sum*pw[add]%Mod*C[lst][cur*i]%Mod*f[i][cur]%Mod);
}
int main(){
	cin>>n>>k,init();
	dfs(n,1,1,1);
	printf("%lld\n",ans);
	
	return 0;
}

C

Group Projects

开题速度真的块。

问将 \(n\) 个数分为若干组且极差的总和不超过 \(m\) 的方案数。

\(1\le n\le 200\)\(1\le m\le 1000\).

先排序,正常思路分 \(4\) 种情况:

  • 加到一个组中且该组未闭合,贡献 \(0\).

  • 加到一个组中且使该组闭合,贡献 \(a_i\).

  • 新建一个未闭合的组,贡献 \(-a_i\).

  • 新建一个闭合的组,贡献 \(0\).

\(f_{i,j,k}\) 为前 \(i\) 个数有 \(j\) 个未闭合区间,总极差为 \(k\) 的方案数:

\[f_{i,j,k}=f_{i-1,j,k}\times j+f_{i-1,j+1,k-a_i}\times(j+1)+f_{i-1,j-1,k+a_i}+f_{i-1,j,k} \]

有负下标且状态数为 \(O(n^2\sum a_i)\),不太行。

费用提前计算,令 \(d=a_i-a_{i-1}\),此时的 dp 方程为

\[f_{i,j,k}=f_{i-1,j,k-j\times d}\times j+f_{i-1,j+1,k-(j+1)\times d}\times(j+1)+f_{i-1,j-1,k-(j-1)\times d}+f_{i-1,j,k-j\times d} \]

\[=f_{i-1,j,k-j\times d}\times(j+1)+f_{i-1,j+1,k-(j+1)\times d}\times (j+1)+f_{i-1,j-1,k-(j-1)\times d} \]

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

#include<bits/stdc++.h>
#define ll long long
#define N 205
#define M 1010
#define Mod 1000000007
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,m,a[N],f[N][N][M];
int ans;
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	sort(a+1,a+1+n);
	f[1][1][0]=f[1][0][0]=1;
	for(int i=2;i<=n;i++)
		for(int j=0;j<=i;j++)
			for(int k=0,d;k<=m;k++){
				d=a[i]-a[i-1];
				if(k-j*d>=0)
					(f[i][j][k]+=1ll*f[i-1][j][k-j*d]*(j+1)%Mod)%=Mod;
				if(k-(j+1)*d>=0)
					(f[i][j][k]+=1ll*f[i-1][j+1][k-(j+1)*d]*(j+1)%Mod)%=Mod;
				if(k-(j-1)*d>=0&&j)
					(f[i][j][k]+=f[i-1][j-1][k-(j-1)*d])%=Mod;
			}
	for(int i=0;i<=m;i++)
		(ans+=f[n][0][i])%=Mod;
	printf("%d\n",ans);
	
	return 0;
} 

D

\(3n\) 张卡片,数字值域为 \(\lbrack 1,n\rbrack\)

  • 每次取最左端的 \(5\) 张,任意调整顺序并删除最左边的 \(3\) 张,若这 \(3\) 张相等获得 \(1\) 贡献。

  • 若最后剩下的 \(3\) 张相等,获得 \(1\) 贡献。

输出最大的贡献。

\(1\le n\le 2000\).

\(34\) 分做法是设 \(f_{i,j,k}\) 为第 \(i\) 次删除时左边两张是 \(j\)\(k\) 的最大贡献。

转移有 \(10\) 种,时间复杂度 \(O(10\times n^3)\).

思考如何减少状态转移数。

  • \(x\space y\space a\space a\space a\)

直接不处理,全部 \(f+1\).

  • \(x\space y\space a\space b\space c\)

然后你发现确定了 \(a,b,c\) 之后在原数组上新增的状态只会有 \(O(3n)\) 种。

所以不要滚动。

posted @ 2023-08-06 20:06  SError  阅读(272)  评论(0编辑  收藏  举报