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=2∑nCsi−1ai−1
代码
#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=Fn−1+5×Fn−2+Fn−3−Fn−4,然而我证明不出来,但是一波矩阵乘法就可以了
代码
#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 0∼3数,可以选取一段区间加1,当变成4时自动变成0,问最少操作多少次使这 n n n个数变成另外 n n n个 0 ∼ 3 0\sim 3 0∼3的数
分析
类比于洛谷 1969 积木大赛,如果没有变成0的限制,答案即为
∑
max
{
0
,
a
i
−
a
i
−
1
}
\sum \max\{0,a_i-a_{i-1}\}
∑max{0,ai−ai−1}
为什么呢,根据贪心,假设
a
i
≥
a
i
−
1
a_i\geq a_{i-1}
ai≥ai−1,那么就要再增加
a
i
−
a
i
−
1
a_i-a_{i-1}
ai−ai−1,倘若小于,那么答案显然是不需要增加的
然而,这道题是加强版,那怎么做呢,区间
[
i
∼
j
]
[i\sim j]
[i∼j]需要取模当且仅当
a
j
−
a
j
−
1
+
4
≤
a
i
+
1
−
a
i
a_j-a_{j-1}+4\leq a_{i+1}-a_{i}
aj−aj−1+4≤ai+1−ai,所以说需要统计个数,再求解
代码
#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;
}