20240727比赛总结

T1 随 (rand)

https://gxyzoj.com/d/hzoj/p/710

本场最难题,因为误判了难度以及自己写矩阵快速幂的实力,浪费了接近2h

10pt:因为\(1\le a_i<mod\),所以当mod=2时,a数组只有1,所以直接输出1即可

20pts:显然,当只有一个数时,直接计算即可

50pts:当\(m\le 300\)时,直接模拟每次操作,计算最终得到每个值的概率,统计即可

100pts:

在50分的基础上继续优化,主要的时间复杂度在m次操作上

可以发现,每一次转移时,每个数所能转移到的数是固定的,所以考虑矩阵快速幂

首先开个桶,记录每个数出现的次数,将m分成\(\sum 2^i\),每次枚举完\(i\)后维护\(cnt\)数组

\(cnt_i\)表示在进行k轮后为i的方案数,在转移时,\(cnt_{i\times j\% mod}=\sum cnt_i\times cnt_j\)

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,m,mod,a[100005];
ll cnt[1005],f[1005],tmp[1005];
ll qpow(ll x,int y,int q)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%q;
		x=x*x%q;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d%d",&n,&m,&mod);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		cnt[a[i]]++;
	}
	if(mod==2)
	{
		printf("1");
		return 0;
	}
	if(n==1)
	{
		printf("%d",qpow(a[1],m,mod));
		return 0;
	}
	ll t=qpow(qpow(n,m,p),p-2,p),ans=0;
	f[1]=1;
	while(m)
	{
		if(m&1)
		{
			for(int i=0;i<mod;i++)
			{
				tmp[i]=f[i],f[i]=0;
			}
			for(int i=0;i<mod;i++)
			{
				for(int j=0;j<mod;j++)
				{
					f[i*j%mod]=(f[i*j%mod]+tmp[i]*cnt[j])%p;
				}
			}
		}
		for(int i=0;i<mod;i++)
		{
			tmp[i]=cnt[i],cnt[i]=0;
		}
		for(int i=0;i<mod;i++)
		{
			for(int j=0;j<mod;j++)
			{
				cnt[i*j%mod]=(cnt[i*j%mod]+tmp[i]*tmp[j])%p;
			}
		}
		m>>=1;
	}
	for(int i=0;i<mod;i++)
	{
		ans+=i*f[i]%p;
		ans%=p;
	}
	printf("%d",ans*t%p);
	return 0;
}

T2 单(single)

https://gxyzoj.com/d/hzoj/p/711

对于所有t=0,可以up and down直接求解

\(f_i\)表示子树内,\(g_i\)表示子树外,\(P_i\)表示i的子树内时权值之和,则:

\[g_i=g_{fa}+f_{fa}-f_i+P_{rt}-2P_i \]

化简得:

\[b_u=b_{fa}+P_{rt}-2P_{u} \]

即:

\[b_u-b_{fa}=P_{rt}-2P_u \]

所以当t=1时,如果知道\(P_{rt}\),就可以差分求出其他值

考虑将上述的式子加起来,得到\(\sum k_i dp_i=(n-1)P_{rt}-2\sum P_i\)

\(b_{rt}=\sum P_i\)

所以dfs求解即可

代码:

#include<cstdio>
#define ll long long
using namespace std;
int T,n,edgenum,head[100005],a[100005],b[100005],f[100005];
int dp1[100005],dp2[100005];
struct edge{
	int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
void dfs(int u,int fa)
{
	f[u]=a[u],dp1[u]=dp2[u]=0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		f[u]+=f[v];
		dp1[u]+=dp1[v];
	}
	dp1[u]+=f[u]-a[u];
}
void dfs1(int u,int fa)
{
	if(u!=1)
	{
		dp2[u]+=dp2[fa]+f[1]-f[u];
		dp2[u]+=dp1[fa]-dp1[u]-f[u];
	}
	b[u]=dp1[u]+dp2[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs1(v,u);
	}
}
ll sum1[100005],sum;
void dfs2(int u,int fa)
{
	if(u!=1)
	sum1[u]=b[u]-b[fa];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs2(v,u);
	}
}
void dfs3(int u,int fa)
{
	a[u]=sum1[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs3(v,u);
		a[u]-=sum1[v];
	}
}
int main()
{
//	freopen("1.txt","r",stdin);
//	freopen("2.txt","w",stdout);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		edgenum=0;
		for(int i=1;i<=n;i++)
		{
			head[i]=a[i]=b[i]=sum1[i]=0;
		}
		for(int i=1;i<n;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add_edge(u,v);
			add_edge(v,u);
		}
		int t;
		scanf("%d",&t);
		if(t==0)
		{
			for(int i=1;i<=n;i++) scanf("%d",&a[i]);
			dfs(1,0);
			dfs1(1,0);
			for(int i=1;i<=n;i++) printf("%d ",b[i]);
		}
		else
		{
			for(int i=1;i<=n;i++) scanf("%d",&b[i]);
			dfs2(1,0);
			sum=0;
			for(int i=2;i<=n;i++) sum+=sum1[i];
			sum+=2ll*b[1];
			sum/=(n-1);
			for(int i=2;i<=n;i++)
			{
				sum1[i]=-(sum1[i]-sum)/2;
			}
			sum1[1]=sum;
			dfs3(1,0);
			for(int i=1;i<=n;i++) printf("%d ",a[i]);
		}
		printf("\n");
	}
	return 0;
}

T3 题(problem)

https://gxyzoj.com/d/hzoj/p/712

计数四合一

typ=0

因为要回到原点,所以相反的两个方向的步数要一致,所以可以枚举其中一个方向的总步数即可

for(int i=0;i<=n;i+=2)
{
	ans+=C(n,i)*C(i,i/2)%p*C(n-i,(n-i)/2)%p;
	ans%=p;
}

typ=1

因为只能在x的非负半轴上运动,所以必然在任何时刻都有向右的步数大于等于向左的步数

所以直接求卡特兰数第\(\frac{n}{2}\)项即可

printf("%d",((C(n,n/2)-C(n,n/2+1)+p)%p));

typ=2,可以使用类似背包的做法

容易想到,如果要换轴,就必须要回到原点,所以它必然是横向和纵向交替出现且每段两种方向的次数是一样的

所以,可以枚举同轴走的步数,求出方案数,然后设\(dp_i\)表示走了i步的方案数,用背包求解即可

因为开始可以走横轴也可以走纵轴,所以要乘2

代码:

for(int i=2;i<=n;i+=2)
{
	f[i]=C(i,i/2);
}
dp[0]=1;
for(int i=2;i<=n;i++)
{
	for(int j=2;j<=i;j+=2)
	{
		dp[i]+=dp[i-j]*f[j]%p;
		dp[i]%=p;
	}
}
printf("%d",dp[n]*2%p);

typ=3

因为x,y均大于等于0,显然卡特兰数

此时,像操作0一样枚举横向的次数即可

注意,当\(i\)\(n-i\)中有一个是0时,直接加卡特兰数

ans=2*((C(n,n/2)-C(n,n/2+1)+p)%p)%p;
for(int i=2;i<n;i+=2)
{
	int t1=(C(i,i/2)-C(i,i/2+1)+p)%p;
	int t2=(C((n-i),(n-i)/2)-C((n-i),(n-i)/2+1)+p)%p;
	ans+=C(n,i)*t1%p*t2%p;
	ans%=p;
}

T4 DP搬运工1

https://gxyzoj.com/d/hzoj/p/2636

预设性dp

假设从小到大填数,因为新的数时当前最大的,所以当存在与他相邻的数是,直接相加即可

\(dp_{i,j,k}\)表示目前在填i,最左和最右有数两点之间有j段空余,目前和为k

  1. 在两边填
  • 与最左或最右相邻,此时段数不变,答案增加i
dp[i][j][k+i]=(dp[i][j][k+i]+dp[i-1][j][k]*2)%p
  • 不相邻,则另起一段,答案不变
dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*2)%p
  1. 在最左和最右之间
  • 放在两个数之间,且不与任何一个相邻,此时新增一段,答案不变
dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*j)%p
  • 与一个数相邻,此时答案增加i,空余段数不变
dp[i][j][k+i]=(dp[i][j][k+i]+dp[i-1][j][k]*2*j)%p
  • 将两个有数的段连接,段数-1,因为两边都有数,所以答案加2i
dp[i][j-1][k+2*i]=(dp[i][j-1][k+2*i]+dp[i-1][j][k]*j)%p

完整代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=998244353;
int n,K;
ll dp[55][55][2605];
int main()
{
	scanf("%d%d",&n,&K);
	dp[1][0][0]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=K;k++)
			{
				dp[i][j][k+i]=(dp[i][j][k+i]+dp[i-1][j][k]*2)%p;
				dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*2)%p;
				dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*j)%p;
				dp[i][j][k+i]=(dp[i][j][k+i]+dp[i-1][j][k]*2*j)%p;
				if(j) dp[i][j-1][k+2*i]=(dp[i][j-1][k+2*i]+dp[i-1][j][k]*j)%p;
			}
		}
	}
	ll ans=0;
	for(int i=0;i<=K;i++) ans=(ans+dp[n][0][i])%p;
	printf("%d",ans);
	return 0;
}
posted @   wangsiqi2010916  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示