[省选集训2022] 模拟赛18

题目描述

给定两边各 \(n\) 个点的二分图,在这张图中,左边的 \(i\) 号点向右边的 \(1,2,3...a_i\) 号点连边。请你求出图中的简单环个数,答案对 \(998244353\) 取模,定义两个环不同为存在一条边在一个环出现了但是在另一个中不出现。

\(1\leq n\leq 5000,1\leq a_i\leq n\)

解法

首先可以把 \(a_i\) 从小到大排序,发现这样并不会改变答案而且简化了问题。

没有什么其他的性质了,直接考虑 \(dp\) 吧。考虑每次增量一个左部的点,然后考虑它连出去的两条边,达到的效果就是合并两条路径

所以我们可以记录路径个数,为了简化计算我们定义路径为有向路径。设 \(dp[i][j]\) 表示考虑前 \(i\) 个左部点,形成的有向路径数量是 \(j\) 个。转移可以分两部,第一步是添加 \(a_i-a_{i-1}\) 个右部单点:

\[dp[i][j]\leftarrow \sum_{k=0}^{a_i-a_{i-1}} dp[i-1][j-k]\cdot {a_i-a_{i-1}\choose k} \]

第二步是任取两条有向路径合并:

\[dp[i][j]\leftarrow dp[i][j+1]\cdot j\cdot (j+1) \]

时间复杂度 \(O(n^2)\),我们在第一步转移之后将 \(dp[i][1]\) 计入答案。

总结

不要纠结于神奇性质,有时候可以直接规划。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 5005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,ans,a[M],C[M][M],dp[M][M];
void add(int &x,int y) {x=(x+y)%MOD;}
signed main()
{
	freopen("ring.in","r",stdin);
	freopen("ring.out","w",stdout);
	n=read();
	for(int i=0;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
	for(int i=1;i<=n;i++) a[i]=read();
	sort(a+1,a+1+n);dp[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		int d=a[i]-a[i-1];
		for(int j=0;j<=a[i];j++)
			for(int k=max(0ll,j-d);k<=j;k++)
				add(dp[i][j],dp[i-1][k]*C[d][j-k]);
		add(ans,dp[i][1]-a[i]+MOD);
		for(int j=0;j<=a[i];j++)
			add(dp[i][j],dp[i][j+1]*j%MOD*(j+1));
	}
	printf("%lld\n",ans*((MOD+1)/2)%MOD);
}

题目描述

给定正整数 \(n\),问有多少正整数 \(1\leq k\leq n\) 满足:使得 \(a^2+b^2=k+c^2\) 的正整数 \(a,b,c\) 有唯一解。

\(n\leq 10^{11}\)

解法

通过打表发现,当 \(k\bmod 4=1/2\) 时,一组解都没有,我们可以在这个结论的基础上进一步分析。

\(c=2b-a\) 带入,整理得到 \(k=b(3b-4a)\),那么合法的 \((a,b,c)\) 对应 \(k\) 的一个分解 \(k=xy\),且满足 \(x+y=0\bmod 4\)\(3x>y\)

\(k\) 写成 \(u\cdot 2^{v}\) 的形式,那么当 \(v=0\) 时,只有当 \(u\bmod 4=3\) 才存在解,并且 \(u\) 是质数才存在唯一解(要不然有多种分配方式);当 \(v>0\) 时,只有 \(v=2/4\) 并且 \(u\) 时质数才有唯一的分解方式。

综上,满足条件的 \(k\) 当且仅当:\(k\) 为模 \(4\)\(3\) 的质数;\(k\)\(>2\) 的质数 \(\times 4\)\(k\)\(>2\) 的质数 \(\times 16\)\(k\)\(4\)\(k\)\(16\)

既然是统计质数个数可以考虑用 \(\tt min\_25\) 筛,主要是怎么统计模 \(4\)\(3\) 的质数?我们可以把初始的数分为两类:模 \(4\)\(1\) 和 模 \(4\)\(3\);那么筛的时候考虑用来筛的质数模 \(4\) 余几,就可以转移了。

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 2000005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,cnt,tot,w[M],p[M],vis[M],sp[M][2],g[M][2];
int id1[M],id2[M];
int get(int x)
{
	return x<=m?id1[x]:id2[n/x];
}
void init(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		{
			p[++cnt]=i;
			sp[cnt][0]=sp[cnt-1][0];
			sp[cnt][1]=sp[cnt-1][1];
			if(i>2) sp[cnt][i%4==3]++;
		}
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
signed main()
{
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	init(1e6);n=read();m=sqrt(n);
	for(int l=1,r;l<=n;l=r+1)
	{
		r=n/(n/l);
		w[++tot]=n/l;
		g[tot][0]=w[tot]-1>>2;
		g[tot][1]=w[tot]+1>>2;
		if(n/l<=m) id1[n/l]=tot;
		else id2[n/(n/l)]=tot;
	}
	for(int i=2;i<=cnt;i++)
		for(int j=1;j<=tot && p[i]*p[i]<=w[j];j++)
		{
			int k=get(w[j]/p[i]),f=p[i]%4==3;
			g[j][0]-=g[k][f]-sp[i-1][f];
			g[j][1]-=g[k][f^1]-sp[i-1][f^1];
		}
	int ans=g[get(n)][1];
	ans+=g[get(n/4)][0]+g[get(n/4)][1]+(n>=4);
	ans+=g[get(n/16)][0]+g[get(n/16)][1]+(n>=16);
	printf("%lld\n",ans);
}
posted @ 2022-03-31 20:32  C202044zxy  阅读(295)  评论(5编辑  收藏  举报