确实,今天仍然是数学,仍然是心态爆炸,虽然通过了一番痛苦的思考把第二题给切了然后顺手拿了个机房第一(当然是因为其他大佬都爆掉了,不然也轮不到我)。
T1 数列 Sequence
题目描述
我们称一个长度为 2n 的数列是有趣的,当且仅当该数列满足以下三个条件:
(1)它是从 1 到 2n 共 2n个整数的一个排列{ai};
(2)所有的奇数项满足 a1<a3<…<a2n-1,所有的偶数项满足 a2<a4<…<a2n;
(3)任意相邻的两项 a2i-1与 a2i(1≤i≤n)满足奇数项小于偶数项,即:a2i-1<a2i。 现在的任务是:对于给定的n,请求出有多少个不同的长度为 2n 的有趣的数列。因为 最后的答案可能很大,所以只要求输出答案 mod P 的值。
输入格式
输入文件只包含用空格隔开的两个整数 n 和 P。
输出格式
仅含一个整数,表示不同的长度为 2n 的有趣的数列个数 mod P 的值。
样例输入
3 6
样例输出
5
数据范围与约定
对于 30%的数据满足 n≤1000;
对于另外 30%的数据满足 P 为质数;
对于100%的数据满足 n≤1000000 且 P≤1000000000。
洛谷题号P3200
分析
对于这道题,我们打表之后,我们不难发现,这是一道标标准准的Catalan数列。
我们可以单独看奇数和偶数相,我们可以把它看作是两个队列,按照1~n的顺序填入两个队列中,其中奇数项队列的长度不能超过偶数项长度,否则就会导致我们要求的序列出现偶数项小于它前面的奇数项的情况。这个过程我们可以形象地看成是再偶数项填为进栈,填奇数项为出栈,保证有东西可以出栈。于是乎就很显而易见的可以看出这是Catalan数列了。
那么怎么求呢?
由于这里并不保证 P 是质数,所以我们不能使用求逆元的方法来求组合数。因此我们选用拆分质因子约分的方式来求解组合数。也就是对于式子我们对 n! , m!, (n - m)! 分别进行质因数分解然后约分。
同样的道理,由于不保证 p 是质数,因而我们不能使用这个公式来求取卡特兰数,我们转而使用来求解,至于证明吗你只需要把这两个数用阶乘的方式表示出来合并就可以了。
代码
又是我丑陋的代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e7 + 5;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return f * x;
}
int n, p;
int v[MAXN], pri[MAXN], tot = 0;
void EuPri()
{
memset(v, 0, sizeof(v));
for(int i = 2; i <= MAXN - 5; i++){
if(v[i] == 0){
v[i] = i;
pri[++tot] = i;
}
for(int j = 1; j <= tot && i * pri[j] <= MAXN - 5 && pri[j] <= v[i]; j++){
v[i * pri[j]] = pri[j];
}
}
}
int QPow(int x, int y)
{
int val = 1;
while(y){
if(y & 1){
val = 1ll * val * x % p;
}
x = 1ll * x * x % p;
y >>= 1;
}
return val;
}
int calc(int N, int k)//计算某个质因子的数量,原理可以翻阅lyd的书
{
long long val = 0, kk = k;//记得一定要开long long,别问我怎么知道的
while(kk <= N){
val += N / kk;
kk *= k;
}
return val;
}
int C(int M, int N)//组合数
{
int val = 1;
for(int i = 1; i <= tot && pri[i] <= N; i++){
// printf("%d\n",i);//看着诡异的调试语句你就知道这个死循环我调的多痛苦了
// if(i == 6542){
// int t = 0;
// }
int x = calc(N, pri[i]), y = calc(M, pri[i]), z = calc(N - M, pri[i]);
int res = x - y - z;//约分
val = 1ll * val * QPow(pri[i], res) % p;
}
return val;
}
int main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = inpt(), p = inpt();
EuPri();
int x = C(n, 2 * n), y = C(n - 1, 2 * n);//计算非质数的Catalan数
printf("%d", ((x - y) % p + p) % p);
fclose(stdin);
fclose(stdout);
return 0;
}
T2 乘法 Mul
题目描述
输入一个 n ∗ n 的矩阵 A,请求出对 m 取模的结果。
输入格式
第一行为三个正整数 n, k, m,含义如上所述;
接下来 n 行,每行输入 n 个非负整数,用于描述矩阵 A。
输出格式
输出 n 行,每行 n 个整数,表示答案矩阵。
样例输入
2 2 5
2 1
0 3
样例输出
1 1
0 2
数据范围与约定
对于前 30% 的数据,k<=30;
对于另外 30% 的数据,n=1;
对于 100%的数据,n<=30, k<=1e9 , m<=1e4,输入矩阵的数在 0~m-1 范围内
解法一:
分析
这道题也是我考场上唯一做出来的题,花了一个小时构思,一个半小时编码,剩下半个小时摆烂,我现在只想摊着。
简单讲一下我的思路,通过高一数学课我们可以知道,在等差数列中(记公差为 q ,前n项和为), 也成等比数列且公比为。于是我们可以把这一点运用到这道题上。
观察下列式子
我们可以用一个数组 sum 来储存前项的和,表示。再用一个数组来储存,这样我们就可以求出整的2的次方项数的和了。
那么剩下的不是整的2的次方数和怎么办呢?
我们可以类比于快速幂的思想,将后几项拆分成长度为 的几段也就是下面这样(用截图做的图片,有个水印懒得取了,将就看一下把):
细心的小伙伴发现了,我们分成的小区段只需要乘上他开头的(假设为)前一个次方就可以了,至于怎么分区段嘛,类比快速幂就行了。
代码
献上我丑陋的代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 35;
const int LOG = 31;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return f * x;
}
int n, k, p;//p是原题中的m
struct Matrix{
int mat[MAXN][MAXN];
int l, h;
Matrix operator *(Matrix qwq)const{
Matrix val;
memset(val.mat, 0, sizeof(val.mat));
val.l = qwq.l;
val.h = h;
if(l == qwq.h){
for(int i = 1; i <= h; i++){
for(int j = 1; j <= qwq.l; j++){
int res = 0;
for(int k = 1; k <= l; k++){
res = (res + (1ll * mat[i][k] * qwq.mat[k][j]) % p) % p;
}
val.mat[i][j] = res;
}
}
return val;
}
}
Matrix operator %(int qwq)const{
Matrix val;
memset(val.mat, 0, sizeof(val.mat));
val.l = l;
val.h = h;
for(int i = 1; i <= l; i++){
for(int j = 1; j <= h; j++){
val.mat[i][j] = mat[i][j] % qwq;
}
}
return val;
}
Matrix operator +(Matrix qwq)const{
Matrix val;
memset(val.mat, 0, sizeof(val.mat));
val.l = l;
val.h = h;
if(l == qwq.l && h == qwq.h){
for(int i = 1; i <= l; i++){
for(int j = 1; j <= h; j++){
val.mat[i][j] = (mat[i][j] + qwq.mat[i][j]) % p;
}
}
return val;
}
}
}a, ans, sum[35], pw[35];
long long pw2[35];
void init()
{
pw2[0] = 1;
for(int i = 1; i <= LOG; i++){
pw2[i] = pw2[i - 1] * 2;//初始化2的次方
}
n = inpt(), k = inpt(), p = inpt();
a.l = n;
a.h = n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
a.mat[i][j] = inpt();
}
}
}
int main()
{
// freopen("data.out", "r", stdin);
freopen("mul.in", "r", stdin);
freopen("mul.out", "w", stdout);
init();
int cnt = 0;
pw[0] = sum[0] = a;
while(pw2[cnt] <= k){
pw[++cnt] = pw[cnt - 1] * pw[cnt - 1] % p;//初始化A的次方及次方和
sum[cnt] = (sum[cnt - 1] + (sum[cnt - 1] * pw[cnt - 1]) % p) % p;
}
cnt--;
ans = sum[cnt];
int del = k - pw2[cnt];
int nw = pw2[cnt] + 1;
while(del){//处理剩下的部分
int x = upper_bound(pw2, pw2 + cnt, del) - pw2 - 1;//分成小块
// int y = upper_bound(pw2, //这个是错的别被误导 pw2 + 32, nw) - pw2 - 1;
int y = nw - 1;//A^i-1
Matrix qwq = sum[x];
int z = 0;
while(y){
if(y & 1){
qwq = qwq * pw[z] % p;
}
z++;
y >>= 1;
}
ans = (ans + qwq) % p;
del -= pw2[x];
nw += pw2[x];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
printf("%d ", ans.mat[i][j]);
}
puts("");
}
fclose(stdin);
fclose(stdout);
return 0;
}
/*
2 2 5
2 1
0 3
2 2 30
2 1
0 3
3 7 17
2 1 4
3 1 0
7 4 2
2 1000000000 1027
2 1
0 3
*/
我这份代码是我考场上做的,xyh巨佬为我们提供了一份更加简洁的代码,详情可见他的博客(他暂时还没写,等他写了我会换链接的)。
解法二
我不想分析了
这个方法我还没来得及仔细看,这里把yyr学长的题解粘过来(我不知道他的博客链接,也不知道他发过没有TAT)
代码
这个方法的代码我也没来得及打,这里贴上杰哥的博客链接他也暂时还没写博客,等他写了我再换链接。
T3 生物 Creature
题目描述
在一个无限长的一维空间中,存在着一个奇特的生物,它的身体上顺次有着 n + 1 个 刻印,每个刻印可以用一个正整数来表示。已知它最后一个刻印的值为 m,而其它 n 个刻 印的值均不超过 m,并且两个刻印的值可以相同。 这个生物每次可以选中它的任意一个刻印,并且按照这个刻印的值 k,选择向它所在位 置的前或后闪烁 k 个单位。我们称可以使得这个生物能够通过若干次闪烁,到达一维空间 任何一个位置的刻印序列为超刻印序列。 现在刻印序列显然一共有 种,为了研究这个生物,请你求出其中超刻印序列的数 目。
输入格式
仅一行两个整数,分别为 n, m。
输出格式
输出一行一个整数,表示超刻印序列的数目对 1e9 +7 取模的结果。
样例输入
2 4
样例输出
12
数据范围与约定
对于前 20%的数据,保证 n,m <= 5;
对于 100%的数据,保证 1<=n<=15,1<=m<=1e8。
分析
因为他要求再这个 m 结尾的数列任意组合后可以到达该空间内任意一个点,所以我们可以知道,只要这个数列中的数通过任意倍数的组合可以组成 1 就可以了,通过裴蜀定理我们可以知道,满足这个条件只需要保证这n个数的 gcd 为 1 就可以了。因为直接处理 n 个数的 gcd 不好处理,于是我们选择正难则反,只需要再所有排列情况减去 的情况即可。
由于我们知道这个数列中一定有数字 m 的存在,所以只要则说明前面的数中包含 m 的质因子的组合产生的数。也就是(假设 m 的质因子为)的大小(A表示满足该条件的序列),而这个并集的大小也是十分难以求得的,于是我们反而求出的大小也就是 的大小,运用容斥原理求得即可。而再 时,每个数的不同的质因子数目不会超过10个(最小的10个质数相乘已经超过了2e9)(而且指数之和不会超过31()虽然这道题用不到)。所以我们运用dfs计算容斥复杂度时完全符合的。
代码
献上我丑陋的代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e4 + 5;
const int MOD = 1e9 + 7;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return f * x;
}
int n, m;
int v[MAXN], pri[MAXN], tot = 0;
void EuPri()
{
memset(v, 0, sizeof(v));
for(int i = 2; i <= MAXN - 5; i++){
if(v[i] == 0){
v[i] = i;
pri[++tot] = i;
}
for(int j = 1; j <= tot && i * pri[j] <= MAXN - 5 && pri[j] <= v[i]; j++){
v[i * pri[j]] = pri[j];
}
}
}
int p[MAXN], cnt = 0;
void Divide(int x)//质因数分解
{
for(int i = 1; i <= tot && pri[i] <= x; i++){
if(x % pri[i] == 0){
p[++cnt] = pri[i];
while(x % pri[i] == 0){
x /= pri[i];
}
}
}
if(x != 1){
p[++cnt] = x;
}
}
int QPow(int x, int y)
{
int val = 1;
while(y){
if(y & 1){
val = 1ll * val * x % MOD;
}
x = 1ll * x * x % MOD;
y >>= 1;
}
return val;
}
int s[30], ans = 0;//s储存当前情况下的质因子组合
void dfs(int Num, int Lst, int Lim, int op)//求解容斥原理
{//Num表示当前已经用了多少个质因子,Lst表示上一个用的质因子是谁
//Lim表示当前最多可以使用多少个质因子,op表示当前计算的符号
if(Num >= Lim){
int val = 1;
for(int i = 1; i <= Num; i++){
val *= s[i];
}
ans = ((ans + (QPow(m / val, n) * op) )% MOD + MOD) % MOD;//n个位置放m/val个数
return ;
}
for(int i = Lst + 1; i <= cnt; i++){
s[Num + 1] = p[i];
dfs(Num + 1, i, Lim, op);
}
}
int main()
{
freopen("creature.in", "r", stdin);
freopen("creature.out", "w", stdout);
n = inpt(), m = inpt();
EuPri();
Divide(m);
for(int i = 1, op = 1; i <= cnt; i++, op *= -1){
dfs(0, 0, i, op);
}
printf("%d", ((QPow(m, n) - ans) % MOD + MOD) % MOD);//记得一定要减
fclose(stdin);
fclose(stdout);
return 0;
}
充实而精彩的五一三天集训终于是结束了,接下来的时间就是疯狂的补作业了,但我现在只想摊着。。。