暑假集训CSP提高模拟2

暑假集训CSP提高模拟2

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

11/35...... 挂了 200 的样子?

T1 活动投票

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

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

我们发现只有答案出现次数大于一半,我们的空间不够全部读入,尝试舍弃部分信息,对于任意数 p,求出所有数 modp 之后得到次数最多的结果,之后我们就知道了 ansmodp 直接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 的个数,因为每个减法后面一定有对应的加法,所以这个后缀和一定 0,线段树维护区间 min 即可

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

T3 Legacy

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

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

T4 DP搬运工1

好题,难题。

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

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

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

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

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

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. 加到中间,不和任何区间相邻,没贡献

方程自然有了

dpi,j,d=2dpi1,j,di×j+dpi1,j+1,d2i×j+dpi1,j1,d×j

大力dp之后输出 i=0kdpn,1,i 即可,记得取模,初始化可以 dp1,1,0=1,复杂度 O(n2k)

贴个代码:

#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 @   wang54321  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示