Preface
神秘沟槽 Counting 大赛,十个题全是模 \(998244353\) 有点逆天了
开场发现 G 是去年暑假前集训的原,然后坐牢了大半天看榜发现包大爷切了 B,然后跟了一手
接下来慢慢把所有题都看了一遍,每个题都属于有点思路但不多
中间和祁神把诈骗题 I 玩出来了,然后对着 H 硬套 「PKUWC2018」猎人杀 的做法发现也很好写,写完后 4h 下班
B. Bit Operation
需要观察到重要转化,由于操作两个 \(0\) 或者两个 \(1\) 得到的结果是固定的,而一个 \(0\) 一个 \(1\) 可以任意选中保留其中一个
因此不妨将操作看作每次选中相邻的两个数,选中其中一个删去
直接枚举最后留下的数是哪个,此时左右两边的数都要删完
考虑记 \(f_i\) 表示在某个数一侧有 \(i\) 个要删去的数时的方案数,注意到第一步操作有 \(2i-1\) 种方案,而操作一次后会变为 \(f_{i-1}\) 这个状态,因此很容易得到转移
最后注意操作可以在左右交替进行,再乘上一个组合数即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5,mod=998244353;
int n,a[N],fact[N],ifac[N],f[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline int C(CI n,CI m)
{
if (n<0||m<0||n<m) return 0;
return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
scanf("%d",&n);
fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
ifac[n]=quick_pow(fact[n]); for (RI i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
f[0]=1; for (RI i=1;i<=n;++i) f[i]=1LL*f[i-1]*(2*i-1)%mod;
int ans=0; for (RI i=1;i<=n;++i)
{
scanf("%d",&a[i]); if (!a[i]) continue;
(ans+=1LL*C(n-1,i-1)*f[i-1]%mod*f[n-i]%mod)%=mod;
}
return printf("%d",ans),0;
}
G. Games
在 去年暑假前集训数学专题 中出现过,很经典的 K-FWT 题
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=823543,mod=998244353;
int n,x,c[N],d[7],w[7]; long long m;
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,long long p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void K_FWT(int *f,CI opt)
{
static int t[7]; RI i,l,j,a,b;
for (i=1;i<N;i*=7) for (l=0;l<N;l+=i*7) for (j=l;j<l+i;++j)
{
for (a=0;a<7;++a) for (b=t[a]=0;b<7;++b)
inc(t[a],1LL*f[j+b*i]*w[b*(7+opt*a)%7]%mod);
for (a=0;a<7;++a) f[j+a*i]=t[a];
}
if (!~opt)
{
int Inv=quick_pow(N); for (i=0;i<N;++i) f[i]=1LL*f[i]*Inv%mod;
}
}
int main()
{
RI i,j; for (scanf("%d%lld",&n,&m),i=1;i<=n;++i)
{
for (scanf("%d",&x),j=0;j<7;++j) d[j]=x&1,x>>=1;
for (j=6;j>=0;--j) x=x*7+d[j]; ++c[x];
}
for (w[0]=1,w[1]=quick_pow(3,(mod-1)/7),i=2;i<7;++i) w[i]=1LL*w[i-1]*w[1]%mod;
for (K_FWT(c,1),i=0;i<N;++i) c[i]=quick_pow(c[i],m);
return K_FWT(c,-1),printf("%d",(c[0]+mod)%mod),0;
}
H. Harsh Comments
猎人杀重现,但这题数据范围很小可以大力背包
首先这类题有一个经典结论,当一个人被选中后我们不改变分母,而是允许再次选中这个人,只不过选中之前选过的人需要重新 roll 一次
考虑如果游戏一直不结束会进行 \(n+m\) 轮,考虑用期望的线性性,枚举每个 \(b_i\) 算出它在所有 \(a_i\) 之后的概率,这个概率即是期望,将其减去即可得到最后的答案
但直接计算也不好处理,不妨考虑容斥,枚举 \(\{a_i\}\) 的一个子集,钦定它在 \(b_i\) 后出现
令选中子集的概率为 \(x\),选中 \(b_i\) 的概率为 \(y\),则这种局面出现的概率为 \(\sum_{i=0}^{\infty} y\times (1-x-y)^i=\frac{y}{x+y}\)
因此我们只需要知道不同的 \(x\) 对应的容斥系数即可,直接用背包跑出容斥系数即可
总复杂度 \(O(n^2\times |a_i|)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,mod=998244353;
int n,m,f[N*N],a[N],b[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
if ((x-=y)<0) x+=mod;
}
int main()
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
for (RI i=1;i<=m;++i) scanf("%d",&b[i]);
f[0]=1; for (RI i=1;i<=n;++i)
{
for (RI j=100*i;j>=a[i];--j) dec(f[j],f[j-a[i]]);
}
int ans=n+m;
for (RI i=1;i<=m;++i)
{
int ret=0;
for (RI j=0;j<=100*n;++j)
inc(ret,1LL*b[i]*quick_pow((j+b[i])%mod)%mod*f[j]%mod);
dec(ans,ret);
}
return printf("%d",ans),0;
}
I. Inverse Problem
感觉想清楚了就不难的一个题,但硬是摸到了快 3h 才开出来
考虑如果存在某个位置 \(x_i<x_{i-1}\),则 \(x_i\) 必须放在 \(n-m+i\) 的位置上,且后面的所有元素都唯一确定了
否则手玩下会发现如果 \(x_i\) 不在 \(n-m+i\) 的位置上,则它一定可以替换 \(x_{i-1}\) 得到字典序更小的子序列
接下来考虑将未出现的数插空放在前缀的那段递增段中,从小到大插入并动态更新能放的位置数量即可
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=250005,mod=998244353;
int n,m,x[N],vis[N];
int main()
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=m;++i) scanf("%d",&x[i]),vis[x[i]]=1;
vector <int> vec;
for (RI i=2;i<=m+1;++i)
if (x[i]<x[i-1])
{
for (RI j=1;j<i;++j) vec.push_back(x[j]);
break;
}
vector <int> unused;
for (RI i=1;i<=n;++i) if (!vis[i]) unused.push_back(i);
int ans=1,pos=0,len=0;
for (auto x:unused)
{
if (pos<vec.size())
{
while (pos<vec.size()&&x>vec[pos]) ++pos,++len;
if (pos==vec.size()) ++len;
}
if (x<vec[0]) { ans=0; break; }
//printf("x = %d; len = %d\n",x,len);
ans=1LL*ans*len%mod; ++len;
}
return printf("%d",ans),0;
}
Postscript
沟槽的 Counting 是一个也不想补,直接白兰了
标签:CI,Cup,int,Tokyo,Prix,mul,include,RI,mod From: https://www.cnblogs.com/cjjsb/p/18355397