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 对弈
可以进行双倍经验
证明很详细,结论比较淦,仔细看就好
\(\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();}