暑假集训CSP提高模拟2

暑假集训CSP提高模拟2

唐完了哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

11/35...... 挂了 \(200\) 的样子?

T1 活动投票

  • 正解
    摩尔投票板子,用投票方式对抗出次数大于一半的数

赛时想了个crt做法,结果质数选太小全挂了,乐

我们发现只有答案出现次数大于一半,我们的空间不够全部读入,尝试舍弃部分信息,对于任意数 \(p\),求出所有数 \(\mod p\) 之后得到次数最多的结果,之后我们就知道了 \(ans \mod p\) 直接crt求解即可,赛时唐了两小时crt,我再也不D 数据删除 因为不会写crt挂分了

因为素数选小,常数巨大,全TLE,捆绑测试,遂挂 \(100\) 分,赛后随便换了几个大质数就过了

贴个代码先

#include<bits/stdc++.h>
#define llt long long
const llt N=101000;
using namespace std;
llt now,us[40][1054],n,l,ans[40],p[40],maxx[40],u,v,m;
vector<llt> prime;
llt exgcd(llt x,llt y,llt &a,llt &b)
{
    if(x%y==0)  {a=0,b=1;return y;}  
    llt gcd=exgcd(y,x%y,a,b),c=a;
    a=b,b=c-a*(x/y);
    return gcd;
}
void crt()
{
    llt o;
    for(int i=0;i<l-1;i++)
    {
        u=v=0;
        o=exgcd(prime[i],prime[i+1],u,v);
        u=(u%prime[i+1]+prime[i+1])%prime[i+1];
        u=u*((ans[i+1]-ans[i])%prime[i+1]);
        u=(u%prime[i+1]+prime[i+1])%prime[i+1];
        m=u*prime[i]+ans[i];
        ans[i+1]=m,prime[i+1]*=prime[i];
        m%=prime[i+1];
    }
    printf("%lld\n",m);
}
int main()
{
    #ifdef LOCAL
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    prime.push_back(1009);prime.push_back(1019);prime.push_back(1021);
    l=prime.size();
    prime.resize(100);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&now);
        for(int j=0;j<l;j++)
            us[j][now%prime[j]]++;
    }
    for(int i=0;i<l;i++)
        for(int j=0;j<prime[i];j++)
            if(us[i][j]>maxx[i])
                maxx[i]=us[i][j],ans[i]=j;
    crt();
    return 0;
}

T2 序列

感觉是一眼套路题,赛时想到的一个做法是扫描序列,对于一个数,如果是第一次出现,将权值加上 \(1\),否则加上权值的同时将上一个数的权值修改成 \(-1\),记得将上上个数清空,每次查询当前后缀等于 \(0\) 的个数,因为每个减法后面一定有对应的加法,所以这个后缀和一定 \(\ge 0\),线段树维护区间 \(\min\) 即可

场上莫名其妙写挂掉了,挂掉 \(70\)

T3 Legacy

线段树优化建图板子,简单来说就是建棵线段树,通过中转点来减少连边数,注意出树和入树只有叶子联通

赛时不会,写了 \(60\) 暴力,但实际得分为 \(20\)

T4 DP搬运工1

好题,难题。

这个好像叫预设型dp,我不会啊

首先发现枚举每一位复杂度是巨错无比的,这玩意因为优秀的阶乘复杂度荣获 \(20\)

我们发现可以dp,从小到大填数,你在填了这个数之后如果它的两端每有一个已经填过的数,当前数就会对那个 \(\max\) 产生贡献,这里不能从大到小的原因是两端不会产生贡献,如果从大到小做会多算两边的数

有一个显然的状压做法对吧,每一位填了就是 \(1\),否则是 \(0\)

之后我们发现我们其实关心的只有每个 \(a\)\(b\) 之间的位置关系的方案数,所以考虑优化,对于这样几种情况

1 1 1 0 1 1 1 0
1 1 1 0 0 1 1 1
1 1 1 1 0 0 1 1

他们的贡献相同时,合法方案数都是一样的(因为在端点处的数个数相同),两边互不影响,所以我们将类似这种的情况都当作一个状态

所以状态设计就是 当前填到哪个数 分了几个段 最终贡献是多少

现在开始写转移,新加入的数有以下几种可能

  1. 加到某个区间端点,但不接上另一个区间,贡献是其权值
  2. 加到某个区间端点,同时接上另一个区间,贡献是其权值 \(2\)
  3. 加到中间,不和任何区间相邻,没贡献

方程自然有了

\[dp_{i,j,d}=2dp_{i-1,j,d-i} \times j+dp_{i-1,j+1,d-2i} \times j+dp_{i-1,j-1,d} \times j \]

大力dp之后输出 \(\sum_{i=0}^k dp_{n,1,i}\) 即可,记得取模,初始化可以 \(dp_{1,1,0}=1\),复杂度 \(O(n^2k)\)

贴个代码:

#include<bits/stdc++.h>
#define llt long long
const llt mod=998244353;
using namespace std;
llt n,k,dp[60][60][2600],ans;
int main()
{
    #ifdef LOCAL
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    scanf("%lld%lld",&n,&k);
    dp[1][1][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            for(int d=0;d<=k;d++)
            {
                if(d>=i) dp[i][j][d]+=dp[i-1][j][d-i]*2%mod*j%mod;
                if(d>=2*i) dp[i][j][d]+=dp[i-1][j+1][d-2*i]*j%mod;
                if(j>=1) dp[i][j][d]+=dp[i-1][j-1][d]*j%mod;
                dp[i][j][d]%=mod;
            }
        }
    }   
    for(int j=0;j<=k;j++)
        ans+=dp[n][1][j],ans%=mod;
    printf("%lld\n",ans);
    return 0;
}
posted @ 2024-07-21 07:30  wang54321  阅读(7)  评论(0编辑  收藏  举报