模拟88 考试总结

崩陨

考试经过

啥也不会,T1想了半天差点睡着,然后狂打暴力
最后出来T1dp假了,T3模数是993244853,根本无法评价
垫底了,然后自闭一天,啥也没干,旁边人切掉了\(10^9+7\)道题,我啥也不会

T1.按位或

暴力基本和正解关系不大,然而我想了一个极其复杂的\(n^3\)还假了
正解在于容斥,%%%容斥带师WYZG
假如不管3的倍数的限制,那么可以直接按照答案的位考虑,一个\(log\)轻松出结果
然而有了限制就不好玩了,发现2的若干次幂模3余数只能是1或2,那么可以用这个性质搞一下
设\(p_i\)表示至少有\(i\)位的1不选的方案数,那么是一个经典的奇加偶减的形式,答案即恰好\(0\)位不选
注意到这里并不能直接简单的组合数,因为我们还要保证所有数都是3的倍数
发现二进制下奇数位的1余数是1,偶数位的余数是2,那么可以枚举一下这\(i\)位中在奇数和偶数位的1的个数
这里又是一个\(log\),枚举之后剩下的就可以直接随便选/不选了,我们做一个简单的dp来确定最后的方案数
\(f_{i,j}\)表示已经确定了\(i\)位,余数是\(0/1/2\)的方案数,枚举这一位选不选1转移即可
由于\(n\)个都等价所以快速幂,由于从奇数偶数位中分别要选出来,所以乘两个组合数,也很好理解
所以\(log^3\)就做完了,发现其实不难,只是脑子瓦特了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
const int N=65;
int n,t,c[N][N];
inline int ksm(int x,int y)
{
	int s=1;x%=mod;
	for(;y;y>>=1)
	{
		if(y&1)s=s*x%mod;
		x=x*x%mod;	
	}
	return s;
}
int f[N][3];
signed main()
{
	freopen("or.in","r",stdin);
	freopen("or.out","w",stdout);
	cin>>n>>t;
	int s1=0,s2=0,s=0;
	while(t)
	{
		s++;
		if(t&1)
		{
			if(s&1)s1++;
			else s2++;
		}
		t>>=1;
	}
	c[0][0]=1;
	for(int i=1;i<=64;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;
	}
	int ans=0;
	for(int i=0;i<=s1+s2;i++)
	{
		int sum=0;
		for(int j=max(i-s2,(int)0);j<=s1;j++)
		{
			int p1=s1-j,p2=s2-(i-j);
			assert(p1>=0);assert(p2>=0);
			memset(f,0,sizeof(f));
			f[0][0]=1;
			for(int i=0;i<p1;i++)for(int j=0;j<=2;j++) 
			{
				f[i+1][j]=(f[i+1][j]+f[i][j])%mod;
				f[i+1][(j+1)%3]=(f[i+1][(j+1)%3]+f[i][j])%mod;
			}
			for(int i=p1;i<p1+p2;i++)for(int j=0;j<=2;j++)
			{
				f[i+1][j]=(f[i+1][j]+f[i][j])%mod;
				f[i+1][(j+2)%3]=(f[i+1][(j+2)%3]+f[i][j])%mod;
			}
			sum=(sum+ksm(f[p1+p2][0],n)*c[s1][j]%mod*c[s2][i-j]%mod)%mod;
		}
		if(i&1)ans=(ans-sum+mod)%mod;
		else ans=(ans+sum)%mod;
	}
	cout<<ans<<endl;
	return 0;
}

T2.最短路径

考场上根本没好好想,其实也不算很难
考虑选出\(k\)个点的最优策略,发现如果朴素一点是任选一个起点,每次走到一个点回到起点
发现不一定要每次回去,事实上应该是选择一条主路走到头,别的分叉往回走,感性理解一下
显然这个主路只走一遍,别的走两遍,那么应该选直径
因此就是任意两点距离之和2倍减去直径长度,虚树啥的咱也不懂
但题目算的期望,所以直接枚举肯定不行,考虑拆开算每条路径的贡献
对于前者,枚举每条路径,他贡献的概率就是点在两边的概率,可以用总的方案数减去不合法(点在一边)的概率
后者直接枚举直径端点,考虑另外的点能否合法从而使它成为直径
合法的条件就是不存在距离小于他,或者距离相等但是标号小
然后直接枚举就好了,乘上\(cnt-2\)选\(k-2\)的概率,\(cnt\)是合法点数,复杂度\(m^3\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2050;
const int mod=998244353;
struct node{
	int from,to,next;
}a[2*N];
int head[N],mm=1;
inline void add(int x,int y)
{
	a[mm].from=x;a[mm].to=y;
	a[mm].next=head[x];head[x]=mm++;
}
inline int ksm(int x,int y)
{
	int s=1;x%=mod;
	for(;y;y>>=1)
	{
		if(y&1)s=s*x%mod;
		x=x*x%mod;
	}
	return s;
}
inline int ny(int x){return ksm(x,mod-2);}
int jc[N],inv[N],jcny[N],n,m,k;
inline void pre()
{
	jc[0]=jc[1]=inv[1]=jcny[0]=jcny[1]=1;
	for(int i=2;i<=n;i++)
	{
		jc[i]=jc[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		jcny[i]=jcny[i-1]*inv[i]%mod;
	}	
}
inline int C(int x,int y)
{
	if(x<y)return 0;
	if(!y)return 1;
	return jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}
int d[N],fa[N],size[N],son[N],ss[N];
int top[N],ans,pp[N];bool v[N];
void dfs1(int x)
{
	v[x]=1;
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(v[y])continue;
		d[y]=d[x]+1;fa[y]=x;
		dfs1(y);size[x]+=size[y];ss[x]+=ss[y];
		ans=(ans+(1ll-((C(ss[y],k)+C(m-ss[y],k))%mod*ny(C(m,k))%mod)+mod)%mod*2%mod)%mod;
		if(size[y]>size[son[x]])son[x]=y;
	}
	size[x]++;
	ss[x]+=pp[x];
}
void dfs2(int x,int h)
{
	if(!x)return;
	v[x]=1;top[x]=h;
	dfs2(son[x],h);
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(v[y]||y==son[x])continue;
		dfs2(y,y);
	}
}
inline int getlca(int x,int y)
{
	int fx=top[x],fy=top[y];
	while(fx!=fy)
	{
		if(d[fx]<d[fy])swap(x,y),swap(fx,fy);
		x=fa[fx],fx=top[x];
	}
	if(d[x]<d[y])swap(x,y);
	return y;
}
inline int getd(int x,int y)
{
	int lca=getlca(x,y);
	return d[x]+d[y]-2*d[lca];
}
int p[N];
signed main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++)scanf("%lld",&p[i]),pp[p[i]]=1;
	sort(p+1,p+1+m);
	for(int i=1;i<n;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		add(x,y);add(y,x);
	}
	pre();d[1]=1;dfs1(1);
	memset(v,0,sizeof(v));dfs2(1,1);
	for(int i=1;i<=m;i++)for(int j=i+1;j<=m;j++)
	{
		int x=p[i],y=p[j];if(x>y)swap(x,y);
		int dis=getd(x,y),sum=0;
		for(int k=1;k<=m;k++)
		{	
			int z=p[k];if(z==x||z==y)continue;
			int d1=getd(z,x),d2=getd(z,y);
			if(d1>dis||(d1==dis&&z<y))continue;
			if(d2>dis||(d2==dis&&z<x))continue;
			sum++;
		}
		ans=(ans-C(sum,k-2)*ny(C(m,k))%mod*dis%mod+mod)%mod;
	}
	cout<<ans<<endl;
	return 0;
}

T3.仙人掌

RT,不可做,咕

T4.对弈

原题P2490
发现A只会向右走,B只会向左走,那么把两个棋子看成一堆数量为\(b_i-a_i-1\)的石子,那么通过颓题解可以看出这是一个k-nim模型
结论是先手必败当且仅当所有\(a_i\)变成二进制之后每一位上的1的个数都模\(m+1\)余0,证明见下
模拟88 考试总结
所以可以dp,设\(f_{i,j}\)表示考虑第\(i\)位,总和为\(j\)方案数,枚举是\(m+1\)的多少倍即可

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
const int mod=1e9+7;
int jc[N],inv[N],jcny[N],n,k,m;
int f[20][N];
inline void pre()
{
	jc[0]=jc[1]=inv[1]=jcny[0]=jcny[1]=1;
	for(int i=2;i<=n;i++)
	{
		jc[i]=jc[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		jcny[i]=jcny[i-1]*inv[i]%mod;
	}	
}
inline int C(int x,int y)
{
	if(x<y)return 0;
	if(!y)return 1;
	return jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}
signed main()
{
	freopen("chess.in","r",stdin);freopen("chess.out","w",stdout);
	cin>>n>>k>>m;pre();
	f[0][0]=1;int ans=0;
	for(int i=0;i<=12;i++)for(int j=0;j<=n-k;j++)
	{	
		for(int kk=0;j+kk*(1<<i)*(m+1)<=n-k&&kk*(m+1)<=k/2;kk++)
			f[i+1][j+kk*(1<<i)*(m+1)]=(f[i+1][j+kk*(1<<i)*(m+1)]+f[i][j]*C(k/2,kk*(m+1))%mod)%mod;
	}
	for(int i=0;i<=n-k;i++)ans=(ans+f[13][i]*C(n-i-k/2,k/2)%mod)%mod;
	cout<<(C(n,k)-ans+mod)%mod<<endl;
	return 0;
}

考试总结

这场又挂没,原因是多方面的,开始节奏就乱了,到后面也没回来
希望可以尽快走出来,毕竟联赛没几天了,考场上心态一定要把握好

上一篇:css琐碎知识


下一篇:P1220 关路灯