高二&高一&初三模拟赛17 总结

蛋糕

题目描述

这里写图片描述
这里写图片描述


输入格式

一行两个整数 n 和 P, 意义如题面所示。


输出格式

一行一个整数, 表示有多少种切法。


输入样例

【样例一输入】

6 1000000007

【样例二输入】

20 572541752


输出样例

【样例一输出】

14

【样例二输出】

266161148


题解(卡特兰数+线性筛+快速幂)

题目看上去像个 dp ,设 f[n] n 边形的答案,选一个三角形分割,将问题划分成两边,则f[n]=n1k=2f[k]f[nk+1]。其中 f[2]=f[3]=1 。这是一个 n2 dp ,好像无法优化,但是学过 OI 的我们都知道这就是一个卡特兰数。题目问的就是 F[n2] mod P 的值。

我们知道卡特兰数的计算公式为 F[n]=Cn2nCn+12n=Cn2nn+1 。这样如果算出结果是 O(n) 的,但是 P 不是质数,所以求不了逆元。如果强行分解质因数就是O(nn)的了。线性筛也考虑过但害怕空间炸就没有向下想。其实怀疑数据范围是不是错了,为毛 n 那么大啊。考试时想到这里我就想不出来了(不可能考什么CRT吧,rt。。)。

其实线性筛是可以的,我们预先筛出12n内所有素数,并记住每个数的最小质因子,分解时不断除,这样将每个数分解质因数时的时间就不是 n 的了,而变成了那个数的质因数数量,然而这样做,再加上快速幂都还是过不了,最后kekxy告诉了我一个更快的方法。

我们枚举每个质数,拿去筛掉所有数,求到所有数中有多少个这样的质因子,并记录在数组里。假设当前的素数是 2 ,那么2的倍数的就有 n2 个,每个对 2 所在位置的贡献是1,是 4 的倍数的有n4个,对 2 所在的位置的贡献是2…这样我们每次枚举筛出的质数,然后将质数像 222.. 这样的不断乘,每次 O(1) 求出贡献,假设当前的质数是 i ,那么多出的贡献就是xiv v 1 1 代表相应的位置是加还是减。

这样的时间复杂度降下去了,由每个数质因子个数*总数变成了总质数个数*log级别的倍增计算。于是时间大概是 O(cntlogN+N) cnt 是质数数量,大约有 N/ln(N)

这种方法其实很常见,快速求出一段区间的质因数个数,用线性筛。这种方法其实就是原来是先枚举 i ,然后一个个算,现在直接枚举质因数,然后算区间内有几个。很像求约数个数和,切换枚举次序,将相同的累积一起算,而不是离散地去求和。无奈我记忆力不好,这些套路总是记不住多半是废了。这个计算方法有些类似黎曼积分勒贝格积分的差别。


代码

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#define MAXN 20000010
using namespace std;

typedef long long LL;
LL P, ans = 1LL;
int n, cnt, num[MAXN], prime[MAXN];
bool vis[MAXN];

void Da(){
    for(int i = 2; i <= (n<<1); i++){
        if(!vis[i])  prime[++cnt] = i;
        for(int j = 1; j <= cnt && i*prime[j] <= (n<<1); j++){
            vis[i*prime[j]] = true;
            if(i % prime[j] == 0)  break;
        }
    }
}

void add(int x, int v){
    for(int i = 1; i <= cnt; i++)
        for(LL j = prime[i]; j <= x; j *= prime[i])
            num[i] += x / j * v;
}

LL Pow(LL x, int y){
    LL res = 1LL;
    while(y){
        if(y & 1)  res = res * x % P;
        x = x * x % P;
        y >>= 1;
    }
    return res;
}

int main(){

    scanf("%d%lld", &n, &P);
    n -= 2;

    Da();

    add(n<<1, 1);
    add(n+1, -1);
    add(n, -1);

    for(int i = 1; i <= cnt; i++)  ans = ans * Pow(prime[i], num[i]) % P;

    printf("%lld", ans);
    return 0;
}

解密

题目描述

这里写图片描述
这里写图片描述


输入格式

两行, 第一行为字符串 s1, 第二行为字符串 s2。


输出格式

一行两个整数, 表示所有敌方可能传递的信息的最短长度和最长长度; 如果没有, 那么输出两个-1


输入样例

【样例一输入】

apple
pepperoni

【样例二输入】

testsetses
teeptes

【样例三输入】

bidhan
roy


输出样例

【样例一输出】

2 2

【样例二输出】

3 3

【样例三输出】

1 -1


题解(kmp/后缀数组/SAM)

话说这题真是6,kmp正解不难想到,同时也是后缀数组的果题,大神们用SAM也是眨眨眼就切掉。还有,如果你什么都不会,直接上O(n3)暴力,都能拿到绝大部分分!然而考试时我TM还就真没去做这题,想都没去想。我一直在码第三题,想第一题和钓鱼,甚至好像忘了还有第二题的存在,连个暴力都没去动。

不瞎BB了,直接讲两种方法(SAM老子不会)。kmp的话,就直接枚举s1的位置,以其为开头求一次Next,然后拿去和s1和s2匹配。一开始我想这样是不是就能知道枚举位置后的每个长度的前缀在s1和s2中的出现次数了呢?如果是的话不直接看看出现次数是不是都等于1就好了吗?答案是否定的。假如说有一个后缀aa,匹配aaa,长度为1的加1次,长度为2的加1次,长度为2的再加1次,没了。总共1加了1次,2加了2次。很明显,其实这样是算少了的。

但是题目有一个性质:只有出现1次的才能成为答案,而如果出现了,kmp一定会算到。于是我们只需看看是否出现了两次及以上就行了。那这样怎么做呢?如果一个串在另一个串中出现了至少两次的话呢,那它除了本身加了外,一定作为Next被加了,当然可能被当作Next的Next加了。举个栗子,如果aaa匹配成功了,而且是跳Next跳出来的,我们就可能要将a加1次,aa也加一次,Next那些往回跳啊跳都要加。但是我们只关心出现次数是否大于1,只需要往回加1个,因为前面的已经被加过了,如果出现次数大于1就至少是2了,大概是这个样子。我在BB些啥。所以每次就将k和Next[k]那里+1。这样就能保证时间 n2 了。如果每次往回跳到底,暴力也有挺多分吧。

后缀数组的话,就将s1,s2拼在一起,用特殊字符隔开,然后求sa和height。这题都不用用到二分+分块(也用不了),直接枚举某个后缀和匹配长度l,然后找与其的lcp>=l的后缀有几个是s1的,有几个是s2的就行了。我忘了height数组的性质,一开始不会实现,以为是 n3 ,后来后缀数组大神KsCla告诉我这样是 n2 的,因为枚举后缀后,lcp向两边肯定不增,因为lcp是height的最小值,而height的最小值不增(废话),所以每次向上下扫,然后统计满足要求的s1,s2数量,一旦超过1就退出,所以最多向上和下走3步,走的步数是个常数。时间就是 O(n2+nlogn)


代码(kmp)

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#define MAXN 5005

using namespace std;

int times1[MAXN], times2[MAXN];
int Next[MAXN], len1, len2, ans1 = -1, ans2 = -1;
char s1[MAXN], s2[MAXN];

void Kmp(int l){
    Next[l] = Next[l+1] = l;
    int k = l;
    for(int i = l+2; i <= len1; i++){
        while(k != l && s1[i-1] != s1[k])  k = Next[k];
        if(s1[i-1] == s1[k])  k ++;
        Next[i] = k;
    }

    k = l;
    for(int i = 1; i <= len1; i++){
        while(k != l && s1[i-1] != s1[k])  k = Next[k];
        if(s1[i-1] == s1[k])  k ++;
        if(k){
            times1[k-l] ++;
            times1[Next[k]-l] ++;
        }

        if(k+l == len1)  k = Next[k];
    }

    k = l;
    for(int i = 1; i <= len2; i++){
        while(k != l && s2[i-1] != s1[k])  k = Next[k];
        if(s2[i-1] == s1[k])  k ++;
        if(k){
            times2[k-l] ++;
            times2[Next[k]-l] ++;
        }
        if(k+l == len1)  k = Next[k];
    }
}

int main(){

    scanf("%s%s", &s1, &s2);
    len1 = strlen(s1);
    len2 = strlen(s2);

    for(int i = 0; i < len1; i++){
        memset(times1, 0, sizeof(times1));
        memset(times2, 0, sizeof(times2));
        Kmp(i);
        for(int j = 1; j <= len1; j++)
            if(times1[j] == 1 && times2[j] == 1){
                ans1 = max(ans1, j);
                ans2 = (ans2 == -1) ? j : min(ans2, j);
            }
    }

    printf("%d %d\n", ans2, ans1);
    return 0;
}

代码(SA)

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define MAXN 10010

using namespace std;

char s[MAXN], s1[MAXN], s2[MAXN];
int cnt[MAXN], tmp1[MAXN], tmp2[MAXN], sa[MAXN], height[MAXN];
int *x = tmp1, *y = tmp2, ans1 = -1, ans2 = -1;
int n, m = 1000;

bool Cmp(int *r, int a, int b, int l){
    return r[a] == r[b] && r[a + l] == r[b + l];
}

void Da(){
    int i, j, p;
    for(i = 0; i < m; i++)  cnt[i] = 0;
    for(i = 0; i < n; i++)  cnt[x[i] = s[i]] ++;
    for(i = 1; i < m; i++)  cnt[i] += cnt[i-1];
    for(i = n-1; i >= 0; i--)  sa[--cnt[x[i]]] = i;

    for(j = 1, p = 1; p < n; j <<= 1, m = p){
        for(p = 0, i = n-j; i < n; i++)  y[p++] = i;
        for(i = 0; i < n; i++)  if(sa[i] >= j)  y[p++] = sa[i] - j;
        for(i = 0; i < m; i++)  cnt[i] = 0;
        for(i = 0; i < n; i++)  cnt[x[y[i]]] ++;
        for(i = 1; i < m; i++)  cnt[i] += cnt[i-1];
        for(i = n-1; i >= 0; i--)  sa[--cnt[x[y[i]]]] = y[i];
        for(i = 1, p = 1, swap(x, y), x[sa[0]] = 0; i < n; i++)
            x[sa[i]] = Cmp(y, sa[i], sa[i-1], j) ? p-1 : p++;
    }
}

void Calc_height(){
    int i, j, k = 0;
    for(i = 0; i < n; i++){
        if(x[i]){
            for(k?k--:0, j = sa[x[i]-1]; s[i+k] == s[j+k]; k++);
            height[x[i]] = k;
        }
        else  height[x[i]] = 0;
    }
}

int main(){

    scanf("%s%s", &s1, &s2);

    int len1 = strlen(s1), len2 = strlen(s2);
    for(int i = 0; i < len1; i++)  s[n++] = s1[i];
    s[n++] = '$';
    for(int i = 0; i < len2; i++)  s[n++] = s2[i];
    s[n++] = char(0);
    Da();
    Calc_height();

    for(int i = 0; i < n; i++){
        for(int s = 1; s <= n-sa[i]; s++){
            int Min = 1e6, cnt1 = 0, cnt2 = 0;
            if(sa[i] < len1)  cnt1 ++;
            else  cnt2 ++;
            for(int l = i-1; l+1 < n && l >= 0; l--){
                Min = min(Min, height[l+1]);
                if(Min < s || cnt1 > 1 || cnt2 > 1)  break;
                if(sa[l] < len1)  cnt1 ++;
                else  cnt2 ++;
            }
            Min = 1e6;
            for(int r = i+1; r < n; r++){
                Min = min(Min, height[r]);
                if(Min < s || cnt1 > 1 || cnt2 > 1)  break;
                if(sa[r] < len1)  cnt1 ++;
                else  cnt2 ++;
            }
            if(cnt1 == 1 && cnt2 == 1){
                ans1 = max(ans1, s);
                ans2 = ans2 == -1 ? s : min(ans2, s);
            }
        }
    }

    printf("%d %d\n", ans2, ans1);

    return 0;
}

漏洞

题目描述

这里写图片描述
这里写图片描述


输入格式

这里写图片描述
这里写图片描述


输出格式

这里写图片描述


输入样例

【样例一输入】

5 5
38 43 4 12 70
1 1 3 4 8
2 2 4
1 4 5 0 8
1 2 5 8 7
2 1 5

【样例二输入】

5 5
25 36 39 40 899
1 1 3 2 7
2 1 2
1 3 5 9 1
1 4 4 0 9
2 1 5


输出样例

【样例一输出】

103
207

【样例二输出】

111
1002


题解(线段树)

本题是线段树的果题,我一看到题目就以为自己会了,结果爆零了(代码一堆漏洞)。

线段树上每个节点开一个数组num[]记此区间内的每个数的权值和。假设某个区间有两个数124和42,那么对应的num[1]=100,num[2]=11,num[4]=11。然后我们build()出这个信息,然后顺便求和sum。

然后我们先看简单的query(),明显是询问一段区间的和。

然和看看update(),要求将一段区间的x改成y,一开始我想每一个点记一个二维bool数组change[x][y],代表将x改成y。然而这样空间很吃紧,时间也有100倍常数,那时naive的我以为已经没什么其他好害怕的了,然而这样合并标记,就是down()的时候,无法知道先后顺序,假如是将x改成y,y改成z,我先改那个呢?于是我就光荣爆0。我们可以开一个链表链一下,然而这样时间还是100倍,非常蠢。其实我们多维护的懒惰标记是可以使常数变为10的。

我们考虑记mark[]代表这个区间内的所有的i被改成了mark[i],这样标记是可以合并的,而且下面可以说明常数为10。不考虑down(),先处理update(),现在区间[L, R]又来了一个标记是将u改成v,我们要更新答案sum,num[],以及懒惰标记mark[]。一开始的所有的mark[i]=i,现在mark[i]=j。如果u=j的话,代表现在我们要合并mark[i]=j和mark[j]=v。于是我们直接将mark[i]=v,更新好了mark[]。由于之前已经将mark[i]=i和mark[i]=j合并,答案已经算过。于是此时的贡献与更新就是sum+=num[u]*(v-u),num[v]+=num[u],num[u]=0。
于是update()我们也搞定了,就是说我们处理好了一个点的先后标记的合并与答案的更新,而且常数是10。

换到down(),我们需要处理父亲的标记与儿子标记的合并与儿子答案的更新。注意这时我们已经处理好父亲标记的先后顺序了,就是说能合并的已经在update()的时候或上一次down()搞定了。举个栗子,如果父亲有标记1->3,3->5,明显是先3->5再1->3的,所以我们不能直接看儿子的3然后改成5,因为儿子之前可能因为父亲的标记1->3而使3多了,这样标记就会冲突。所以我们开一个临时数组now[]保存儿子的num[],将儿子的num[]清0。然后将要改的部分累记到儿子的num[]里去,个数转移时还是用now[],这样最后剩下的那些now[]就再累加回儿子的num[]里去,常数也是10,sum也是用now[]去更新。mark[]的话向update()时一样改,最后记得清掉父亲的标记。

至此我们处理好了所有部分,时间复杂度为 O(10qlogN)

总结:线段数的题目就是在合并标记与更新答案,还要知道线段树里记些啥,这些信息要满足区间加法,要能够直接维护,如果不能就要借助其他的信息来维护。

不行,太严肃了,还是讲讲其他的吧。我发现线段树码错了后,最后一分钟将暴力交上去,结果暴力也爆零了,变量打错了QAQ。真是菜出强大。


代码

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <iostream>
#define MAXN 100010

using namespace std;

typedef long long LL;
int n, q;
LL a[MAXN];
struct Tnode{
    LL sum, num[10];
    int mark[10];
}tree[MAXN<<2];

void build(int root, int L, int R){
    if(L == R){
        LL temp = a[L];
        int t = 1;
        while(temp){
            tree[root].num[temp%10] += t;
            t *= 10;
            temp /= 10;
        }
        tree[root].sum = a[L];
        for(int i = 0; i <= 9; i++)
            tree[root].mark[i] = i;
        return;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    build(Lson, L, mid);
    build(Rson, mid+1, R);

    tree[root].sum = tree[Lson].sum + tree[Rson].sum;
    for(int i = 0; i <= 9; i++)
        tree[root].num[i] = tree[Lson].num[i] + tree[Rson].num[i];
    for(int i = 0; i <= 9; i++)
        tree[root].mark[i] = i;
}

void Down(int root, int Lson, int Rson){

    LL now[10];

    memcpy(now, tree[Lson].num, sizeof(now));
    memset(tree[Lson].num, 0, sizeof(now));

    for(int i = 0; i <= 9; i++){
        int &temp = tree[Lson].mark[i];
        if(tree[root].mark[temp] == temp)  continue;
        tree[Lson].sum += (tree[root].mark[temp] - temp) * now[temp];
        tree[Lson].num[tree[root].mark[temp]] += now[temp];
        now[temp] = 0LL;
        temp = tree[root].mark[temp];
    }

    for(int i = 0; i <= 9; i++)  tree[Lson].num[i] += now[i];

    memcpy(now, tree[Rson].num, sizeof(now));
    memset(tree[Rson].num, 0, sizeof(now));

    for(int i = 0; i <= 9; i++){
        int &temp = tree[Rson].mark[i];
        if(tree[root].mark[temp] == temp)  continue;
        tree[Rson].sum += (tree[root].mark[temp] - temp) * now[temp];
        tree[Rson].num[tree[root].mark[temp]] += now[temp];
        now[temp] = 0LL;
        temp = tree[root].mark[temp];
    }

    for(int i = 0; i <= 9; i++)  tree[Rson].num[i] += now[i];

    for(int i = 0; i <= 9; i++)
        tree[root].mark[i] = i;
}

void update(int root, int L, int R, int x, int y, int u, int v){
    if(x > R || y < L)  return;
    if(x <= L && y >= R){
        tree[root].sum += (v - u) * tree[root].num[u];
        tree[root].num[v] += tree[root].num[u];
        tree[root].num[u] = 0LL;
        for(int i = 0; i <= 9; i++)
            if(tree[root].mark[i] == u)  tree[root].mark[i] = v;
        return;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    Down(root, Lson, Rson);

    update(Lson, L, mid, x, y, u, v);
    update(Rson, mid+1, R, x, y, u, v);

    tree[root].sum = tree[Lson].sum + tree[Rson].sum;
    for(int i = 0; i <= 9; i++)
        tree[root].num[i] = tree[Lson].num[i] + tree[Rson].num[i];
}

LL query(int root, int L, int R, int x, int y){
    if(x > R || y < L)  return 0LL;
    if(x <= L && y >= R)  return tree[root].sum;

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    Down(root, Lson, Rson);

    LL temp1 = query(Lson, L, mid, x, y);
    LL temp2 = query(Rson, mid+1, R, x, y);

    return temp1 + temp2;
}

int main(){

    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);

    build(1, 1, n);

    int op, l, r, x, y;
    for(int i = 1; i <= q; i++){
        scanf("%d%d%d", &op, &l, &r);
        if(op == 1){
            scanf("%d%d", &x, &y);
            if(x == y)  continue;
            update(1, 1, n, l, r, x, y);
        }
        else
            printf("%lld\n", query(1, 1, n, l, r));
    }

    return 0;
}

总结

这次考试的时间过得好快,时间分配不对,起码第二题还是能拿很多分的。第三题码不出,想的太少,没想好就码。最后暴力错了,真是不可饶恕。第一题数论不过关,快速分解质因数统计个数都不会,要总结经验,争取以后多拿点分。


这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值