Loading

Noip模拟88 2021.11.2

T1 按位或

考场上刚了两个多小时的,自认为\(T1\)位运算疯狂找规律就能切的\(sb\)神仙题

然而是容斥+dp。。。。。。

话说考场上写想过容斥,当时是容斥有几位和\(t\)不同,但是不会\(dp\)试图\(O(1)\)算出方案但是不会(逃)

不扯了说正解

我们发现\(2^i \mod 3\)只会取到\(1,2\)两种值,那么我们可以把\(t\)二进制拆分后对所有的\(1\)分成两类

设模三余一的位有\(num1\)个,余二的有\(num2\)个,总共\(m\)\(1\)

然后容斥枚举\(t\)有几位上的\(1\)\(0\),设\(S_k\)表示有\(k\)原本是\(1\)而不是\(1\)的方案数

那么答案就是\(\sum\limits_{k=0}^{m}(-1)^kS_k\)

考虑求\(S_k\),使用背包

\(dp_{i,j,0/1/2}\)表示选了\(i\)\(\mod 3=1\)的位,选了\(j\)\(\mod 3=2\)的位,
然后生成的数\(x\mod 3\)\(0/1/2\)的方案数

\(dp[i][j][(u+1)\mod 3]+=dp[i-1][j][u]\)

\(dp[i][j][(u+2)\mod 3]+=dp[i][j-1][u]\)

分这两种情况转移就行

那么每次转移完以后\(dp[i][j][0]\)就是可以使用的容斥的方案数,他的贡献可以累加到\(S_{m-i-j}\)

贡献就是\(C_{num1}^{i}\times C_{num2}^{j}\times (dp[i][j][0])^n\)

后面的乘方就可以理解为每一个数都可以选择这么多种方案,因为他们都是\(3\)的倍数

这样就求出了\(S_k\)

or
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=65,mod=998244353;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
	};
}using namespace AE86;
int n,t,ans,m,pw[NN],cnt[3];
namespace Math{
	auto qmo=[](int a,int b,int ans=1){
		int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
		return ans;
	};
	int h[NN],v[NN];
	auto prework=[](){
		h[0]=h[1]=1; v[0]=v[1]=1;
		for(int i=2;i<NN;i++)h[i]=h[i-1]*i%mod;
		v[NN-1]=qmo(h[NN-1],mod-2);
		for(int i=NN-2;i>=2;i--)v[i]=v[i+1]*(i+1)%mod;
		pw[0]=1;for(int i=1;i<=63;i++)pw[i]=pw[i-1]*2;
		for(int i=0;i<=63;i++) if(t&pw[i]){++cnt[pw[i]%3];++m;}
	};
	auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
}using namespace Math;
int dp[65][65][3],S[NN];
auto getS=[](){
	dp[0][0][0]=1;
	for(int i=0;i<=cnt[1];i++)for(int j=0;j<=cnt[2];j++){
		if(i){
			int k=i-1;for(int u=0;u<3;u++)dp[i][j][u]=dp[k][j][u];
			for(int u=0;u<3;u++)(dp[i][j][(u+1)%3]+=dp[k][j][u])%=mod;
		}
		if(j){
			int k=j-1;for(int u=0;u<3;u++)dp[i][j][u]=dp[i][k][u];
			for(int u=0;u<3;u++)(dp[i][j][(u+2)%3]+=dp[i][k][u])%=mod;
		}
		(S[m-i-j]+=C(cnt[1],i)*C(cnt[2],j)%mod*qmo(dp[i][j][0],n)%mod)%=mod;
	}
};
namespace WSN{
	inline short main(){
		FILE *wsn=freopen("or.in","r",stdin);
		wsn=freopen("or.out","w",stdout);
		n=read();t=read();prework();getS();
		for(int i=0,bs=1;i<=m;i++,bs*=-1)
			ans=(ans+bs*S[i]+mod)%mod;
		write((ans+mod)%mod);
		return 0;
	}
}
signed main(){return WSN::main();}

T2 最短路径

考试的时候搞错最短路径的求法了,于是只有\(20pts\)

考虑如果以选出的\(k\)个点建立“虚树”(是假的别害怕),那么他们之间的最短距离是边数总和\(\times 2-\)虚树直径

比较好理解(但是考场上就是想不出来,太菜)

所求的期望就可以转化为求虚树边长之和的期望虚树直径的期望
求边长之和的期望只要枚举每条边,它出现在虚树的概率就是这条边两边都有小饼干的概率

补充两句求法就是记录一个\(num[x]\)表示以\(x\)为根的子树中关键点的个数

那么枚举边\((u,v)\),记录他的两边的关键点分别为\(a,b\)个,保证\(dep[u]>dep[v]\)

这样\(a=num[u],b=m-num[u]\),这条边的期望值就是\(\frac{C_{m}^{k}-C_{a}^{k}-C_{b}^{k}}{C_{m}^{k}}\)

求虚树直径的期望可以暴力一点
钦定一棵树中的某个点对\((u,v)\)为直径,当且仅当\(dis(u,v)\)最长且字典序最小,并且钦定\(u<v\),那么直径是唯一的
枚举所有点对\((u,v)(u<v)\),考虑这条路径是直径的概率
首先,考虑点对\((u,w)(w!=v)\)\((w,v)(w!=u)\)的影响
即先保证不会出现其他以\(u\)\(v\)的端点的路径为直径
那么我们发现,对于一个点 ,它不能出现当且仅当下面几个条件之一被满足
\(dis(u,w)>dis(u,v)\)
\(dis(u,w)=dis(u,v) \And w<v\)
\(dis(v,w)>dis(u,v)\)
\(dis(v,w)=dis(u,v) \And w<u\)
我们数出能出现的点数\(cnt\),算概率就是算其他\(k-2\)个小饼干都落在这\(cnt\)个里面的概率

确实很清楚,直接放代码了

tree
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=2005,mod=998244353,MM=305;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
	};
}using namespace AE86;
namespace Math{
	auto qmo=[](int a,int b,int ans=1){
		int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
		return ans;
	};
	int h[NN],v[NN];
	auto prework=[](){
		h[0]=h[1]=1; v[0]=v[1]=1;
		for(int i=2;i<NN;i++)h[i]=h[i-1]*i%mod;
		v[NN-1]=qmo(h[NN-1],mod-2);
		for(int i=NN-2;i>=2;i--)v[i]=v[i+1]*(i+1)%mod;
	};
	auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
}using namespace Math;
int n,m,k,id[MM],ans,P,PP;
bool po[NN];
#define fo(i,x,y) for(int (i)=(x);(i)<=(y);++(i))
#define star(i,x) for(int i=head[x],y=e[i].to;i;i=e[i].next,y=e[i].to)
struct SNOW{int to,next;}e[NN<<1];int head[NN],rp;
struct edge{int u,v;}s[NN<<1];
auto add=[](int x,int y){e[++rp]=SNOW{y,head[x]};head[x]=rp;};
int dfn[NN],top[NN],son[NN],dep[NN],siz[NN],fa[NN],cnt,num[NN];
inline void dfs1(int f,int x){
	fa[x]=f; siz[x]=1; dep[x]=dep[f]+1; num[x]=po[x];
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].to; if(y==f) continue;
		dfs1(x,y); siz[x]+=siz[y];
		num[x]+=num[y];
		if(siz[son[x]]<siz[y]) son[x]=y;
	}
}
inline void dfs2(int x,int t){
	top[x]=t; dfn[x]=++cnt;
	if(son[x]) dfs2(son[x],t);
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].to;
		if(y!=fa[x]&&y!=son[x]) dfs2(y,y);
	}
}
auto LCA=[](int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}if(dfn[x]>dfn[y]) swap(x,y);
	return x;
};
int dis[NN][NN];

namespace WSN{
	inline short main(){
		FILE *wsn=freopen("tree.in","r",stdin);wsn=freopen("tree.out","w",stdout);
		n=read(); m=read(); k=read(); prework();
		for(int i=1;i<=m;i++) id[i]=read(),po[id[i]]=1;
		for(int i=1,u,v;i<n;i++)
			u=read(),v=read(),add(u,v),add(v,u),s[i]=edge{u,v};
		dfs1(0,1); dfs2(1,1);
		for(int i=1,a,b;i<n;i++){
			if(dep[s[i].u]<dep[s[i].v])swap(s[i].u,s[i].v);
			a=num[s[i].u],b=m-a;
			P=(P+(C(m,k)-C(a,k)-C(b,k)+mod)%mod*qmo(C(m,k),mod-2)%mod)%mod;
		}
		sort(id+1,id+m+1);
		for(int i=1;i<=m;i++)for(int j=1;j<=m;j++)
			dis[i][j]=dep[id[i]]+dep[id[j]]-dep[LCA(id[i],id[j])]*2;
		for(int i=1;i<m;i++) for(int j=i+1;j<=m;j++){
			int cnt=0;
			for(int k=1;k<=m;k++)if(k!=i&&k!=j){
				if(dis[i][k]>dis[i][j]||dis[k][j]>dis[i][j])continue;
				if(dis[i][k]==dis[i][j]&&k<j)continue;
				if(dis[k][j]==dis[i][j]&&k<i)continue;
				++cnt;
			}
			PP=(PP+dis[i][j]*C(cnt,k-2)%mod*qmo(C(m,k),mod-2)%mod)%mod;
		}
		ans=(P*2%mod-PP+mod)%mod;
		write(ans);
		return 0;
	}
}
signed main(){return WSN::main();}

T3 仙人掌

太难了线性代数+仙人掌上dp,咕咕咕

T4 对弈

原题链接

可以进行双倍经验

image

image

证明很详细,结论比较淦,仔细看就好

\(\color{white}{其实是拍的。。。。}\)

chess
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=1e4+5,mod=1e9+7;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
	};
}using namespace AE86;
int n,k,m,dp[21][NN],ans;
namespace Math{
	int h[NN],v[NN];
	auto qmo=[](int a,int b,int ans=1){
		int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
		return ans;
	};
	auto prework=[](){
		h[0]=h[1]=1; v[0]=v[1]=1;
		for(int i=2;i<NN;i++)h[i]=h[i-1]*i%mod;
		v[NN-1]=qmo(h[NN-1],mod-2);
		for(int i=NN-2;i>=2;i--)v[i]=v[i+1]*(i+1)%mod;
	};
	auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
}using namespace Math;
inline int dfs(int pos,int num){
	if(dp[pos][num]!=-1) return dp[pos][num];
	if(!pos&&!num) return dp[pos][num]=1;
	if(!pos) return dp[pos][num]=0;
	dp[pos][num]=0;
	for(int i=0;i<=k;i+=m+1){
		int tmp=(1<<pos-1)*i;
		if(tmp>num) continue;
		(dp[pos][num]+=dfs(pos-1,num-tmp)*C(k,i)%mod)%=mod;
	}
	return dp[pos][num];
}
namespace WSN{
	inline short main(){
		FILE *wsn=freopen("chess.in","r",stdin);
		wsn=freopen("chess.out","w",stdout);
		n=read();k=read()/2;m=read();prework();
		ans=C(n,k*2);memset(dp,-1,sizeof(dp));
		for(int i=n-2*k;~i;i--)
			ans=(ans-dfs(20,i)*C(n-i-k,k)%mod+mod)%mod;
		write(ans);
		return 0;
	}
}
signed main(){return WSN::main();}
posted @ 2021-11-02 20:21  雪域亡魂  阅读(109)  评论(0编辑  收藏  举报