2019.05.25 【NOIP提高组】模拟 A 组

    xiaoxiao2023-10-16  30

    解题报告

    JZOJ 4786 小a的强迫症题目分析代码 JZOJ 4787 数格子题目分析代码 JZOJ 4788 序列题目分析代码

    JZOJ 4786 小a的强迫症

    题目

    n n n种颜色的珠子,第 i i i种颜色的珠子有 a i a_i ai个,问有多少种排列,满足第 i i i种颜色的最后一个珠子,必然在第 i + 1 i+1 i+1种颜色的最后一个珠子的前面


    分析

    考虑倒序,对于每种排列,把最后一颗珠子放在最后一位,剩下的依照组合随意放置,那么一层层剥开,设 s i = ∑ j = 1 i a i s_i=\sum_{j=1}^i a_i si=j=1iai答案即为 ∑ i = 2 n C s i − 1 a i − 1 \sum_{i=2}^{n}C_{s_i-1}^{a_i-1} i=2nCsi1ai1


    代码

    #include <cstdio> #include <cctype> #define rr register using namespace std; const int mod=998244353; int n,a[100001],sum,inv[500001],fac[500001],ans=1; inline signed iut(){ rr int ans=0; rr char c=getchar(); while (!isdigit(c)) c=getchar(); while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar(); return ans; } signed main(){ n=iut(); inv[0]=fac[0]=inv[1]=fac[1]=1; for (rr int i=1;i<=n;++i) a[i]=iut(),sum+=a[i]; for (rr int i=2;i<=sum;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod; for (rr int i=2;i<=sum;++i) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*inv[i-1]*inv[i]%mod; for (rr int i=n;i>1;--i) ans=1ll*ans*fac[sum-1]%mod*inv[a[i]-1]%mod*inv[sum-a[i]]%mod,sum-=a[i]; return !printf("%d",ans); }

    JZOJ 4787 数格子

    题目

    2 × 1 2\times 1 2×1的骨牌完全覆盖 4 × n 4\times n 4×n的棋盘,问共有多少种方案


    分析

    类比于POJ 2411 Mondriaan’s Dream,可以建立一个状压的矩阵,然而通项公式,可以得到 F n = F n − 1 + 5 × F n − 2 + F n − 3 − F n − 4 F_n=F_{n-1}+5\times F_{n-2}+F_{n-3}-F_{n-4} Fn=Fn1+5×Fn2+Fn3Fn4,然而我证明不出来,但是一波矩阵乘法就可以了


    代码

    #include <cstdio> #include <cstring> #define rr register using namespace std; struct maix{int p[4][4];}A,ANS; int n,mod; inline maix mul(maix A,maix B){ rr maix C; for (rr int i=0;i<4;++i) for (rr int j=0;j<4;++j) C.p[i][j]=0; for (rr int i=0;i<4;++i) for (rr int j=0;j<4;++j) for (rr int k=0;k<4;++k) C.p[i][j]=(1ll*A.p[i][k]*B.p[k][j]+C.p[i][j])%mod; return C; } signed main(){ while (1){ memset(ANS.p,0,sizeof(ANS.p)); memset(A.p,0,sizeof(A.p)); scanf("%d%d",&n,&mod); if (!n&&!mod) return 0; if (n==1) {printf("1\n"); continue;} if (n==2) {printf("5\n"); continue;} if (n==3) {printf("11\n"); continue;} ANS.p[0][0]=ANS.p[1][1]=1,ANS.p[2][2]=1,ANS.p[3][3]=1; A.p[0][3]=-1,A.p[1][0]=1,A.p[2][1]=1,A.p[3][2]=1,A.p[2][3]=5,A.p[1][3]=1,A.p[3][3]=1; for (n-=3;n;n>>=1,A=mul(A,A)) if (n&1) ANS=mul(ANS,A); rr int ans[4]={0,0,0,0},h[4]={1,1,5,11}; for (rr int j=0;j<4;++j) for (rr int k=0;k<4;++k) ans[j]=(1ll*h[k]*ANS.p[k][j]+ans[j])%mod; printf("%d\n",ans[3]); } }

    JZOJ 4788 序列

    题目

    n n n 0 ∼ 3 0\sim 3 03数,可以选取一段区间加1,当变成4时自动变成0,问最少操作多少次使这 n n n个数变成另外 n n n 0 ∼ 3 0\sim 3 03的数


    分析

    类比于洛谷 1969 积木大赛,如果没有变成0的限制,答案即为 ∑ max ⁡ { 0 , a i − a i − 1 } \sum \max\{0,a_i-a_{i-1}\} max{0,aiai1} 为什么呢,根据贪心,假设 a i ≥ a i − 1 a_i\geq a_{i-1} aiai1,那么就要再增加 a i − a i − 1 a_i-a_{i-1} aiai1,倘若小于,那么答案显然是不需要增加的 然而,这道题是加强版,那怎么做呢,区间 [ i ∼ j ] [i\sim j] [ij]需要取模当且仅当 a j − a j − 1 + 4 ≤ a i + 1 − a i a_j-a_{j-1}+4\leq a_{i+1}-a_{i} ajaj1+4ai+1ai,所以说需要统计个数,再求解


    代码

    #include <cstdio> #include <cctype> #define rr register using namespace std; int a[100001]; inline signed iut(){ rr int ans=0; rr char c=getchar(); while (!isdigit(c)) c=getchar(); while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar(); return ans; } signed main(){ for (rr int t=iut();t;--t){ rr int n=iut(),ans=0,lazy[5]={0,0,0,0,0}; for (rr int i=1;i<=n;++i) a[i]=iut(); for (rr int i=1;i<=n;++i) a[i]=(iut()-a[i]+4)&3; for (rr int i=1;i<=n;++i) ans+=a[i]>a[i-1]?a[i]-a[i-1]:0; for (rr int i=2;i<=n;++i){ rr int now=a[i]-a[i-1]; if (now>0){ if (lazy[1]&&now>1) --lazy[1],++lazy[now],ans-=now-1; else if (lazy[2]&&now>2) --lazy[2],++lazy[now],ans-=now-2; }else ++lazy[now+4]; } printf("%d\n",ans); } return 0; }
    最新回复(0)