XXI Open Cup, Grand Prix of Tokyo

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 是一个也不想补,直接白兰了

posted @ 2024-08-12 17:21  空気力学の詩  阅读(72)  评论(0编辑  收藏  举报