高二&高一模拟赛12 总结

本文总结了三道竞赛编程题的解题思路与实现方法,包括贪心算法、线段树及矩阵运算等高级数据结构应用,并反思了比赛策略与代码实践的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

总结的顺序什么的不重要了,各种模拟赛的总结能补就补吧。


数学课

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


输入样例

3
2 2 3


输出样例

15


题解(贪心)

比较水的一个贪心。明显大数放在前面会更好,不然只会乘更大的数是最后结果更大。写成数学语言就是:假设只有a,b,c三个数,先合并a,b,然后再与c合并,就得到(ab+1)c+1=abc+c+1,明显c越小越好,数多起来也是一样的道理。于是我们从大到小排序并直接做就行了。别忘了取模。

时间O(nlogn)


代码

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <iostream>
#define MAXN 222
#define Mod 1000000007

using namespace std;

typedef long long LL;

int n;
LL a[MAXN], ans;

bool cmp(LL A, LL B){
    return A > B;
}

int main(){

    scanf("%d", &n);

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

    sort(a+1, a+n+1, cmp);

    ans = (a[1] * a[2] % Mod + 1) % Mod;

    for(int i = 3; i <= n; i++)  ans = (ans * a[i] % Mod + 1) % Mod;

    printf("%lld\n", ans);

    return 0;
}

莎夏和数列

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


输入样例

5 4
1 1 2 1 1
2 1 5
1 2 4 2
2 2 4
2 1 5


输出样例

5
7
9


题解(线段树+矩阵乘法+快速幂)

本题是一道数据结构题。作为一道货真价实的数据结构题,这道题思维难度不高但比较难写。首先对于先通过矩阵快速幂求出斐波那契数列并将它的矩阵形式保存在线段树中并维护矩阵的和。对于修改操作就是将斐波那契数列的一些下标同时向右移固定的一段。那么求出斐波那契的变换矩阵将同时自乘同样的幂。于是我们直接在修改的区间打一个懒惰标记,记录一个矩阵代表变换矩阵(1110),然后区间的每个矩阵都将乘这个矩阵并加起来。由于矩阵乘法神奇的有分配律,所以可以先求和,直接用和去乘。然后由于乘法结合律,懒惰标记也可以合并。查询就直接询问矩阵的和,询问(an+1an)的和矩阵的(1,0)就行了。题目就完全变成线段树的基本操作了。

这题我很快就想到了,但编了2h+,最合还是没有调出来,最后无奈交了暴力(QAQ)。问题主要在于我前面浪费了一些时间,编程习惯不好,一开始直接将线段树开成了矩阵,值和标记搞来搞去,很烦。最后我改变写的方法,在结构体里套结构体,看上去清楚多了(我一开始忘了结构体能套结构体了。。)。最后发现自己超时6个点因为懒惰标记记了数字,多了一个log,改成矩阵后,加法又忘了改成乘法,最后求答案又忘了取模,一波三折才通过此题,我对自己的线段树很自信,没想到诸多细节却没有处理好,这是很大的一个教训,严重影响了我的得分,浪费了做第三题的时间,搞的分数完全不像样。明明是会做的题,为什么分数却拿不到?这是十分致命的!

时间复杂度O(nlog(ain)8)


代码

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

using namespace std;

typedef long long LL;

int n, m;

LL num[MAXN];

struct Mat{
    LL a[3][3];
    int Row, Line;
    void Clear(){
      memset(a, 0, sizeof(a));
    }

    friend Mat operator + (Mat A, Mat B){
      for(int i = 0; i < A.Row; i++)
       for(int j = 0; j < A.Line; j++)  A.a[i][j] = (A.a[i][j] + B.a[i][j]) % Mod;
      return A;
    }

    friend Mat operator * (Mat A, Mat B){
      Mat C;  C.Clear();
      C.Row = A.Row;  C.Line = B.Line;
      for(int i = 0; i < A.Row; i++)
       for(int j = 0; j < A.Line; j++)
        for(int k = 0; k < B.Line; k++)
          C.a[i][k] = (C.a[i][k] + A.a[i][j] * B.a[j][k] % Mod) % Mod;
      return C;
    }
};

struct Tnode{
    Mat val, add;
}tree[MAXN<<2];


Mat Pow(Mat A, LL x){
    Mat B;
    B.Row = B.Line = 2;
    B.a[0][0] = B.a[0][1] = B.a[1][0] = 1;  B.a[1][1] = 0;

    while(x){
      if(x & 1)  A = B * A;
      B = B * B;
      x >>= 1;
    }
    return A;
}

void build(int root, int L, int R){
    if(L == R){  
      Mat A;
      A.a[0][0] = A.a[1][0] = 1;
      A.Row = 2;  A.Line = 1;
      tree[root].val = Pow(A, num[L]-1);

      A.a[0][0] = A.a[1][1] = 1;  A.a[0][1] = A.a[1][0] = 0;
      A.Row = 2;  A.Line = 2;

      tree[root].add = A;
      return;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
    build(Lson, L, mid);
    build(Rson, mid+1, R);
    tree[root].val = tree[Lson].val + tree[Rson].val;
    tree[root].add.Row = tree[root].add.Line = 2;
    tree[root].add.a[0][0] = tree[root].add.a[1][1] = 1;
    tree[root].add.a[0][1] = tree[root].add.a[1][0] = 0;    
}

void Down(int root, int Lson, int Rson){
    tree[Lson].val = tree[root].add * tree[Lson].val;
    tree[Lson].add = tree[root].add * tree[Lson].add;
    tree[Rson].val = tree[root].add * tree[Rson].val;
    tree[Rson].add = tree[root].add * tree[Rson].add;
    tree[root].add.a[0][0] = tree[root].add.a[1][1] = 1;
    tree[root].add.a[0][1] = tree[root].add.a[1][0] = 0;    
}


void update(int root, int L, int R, int x, int y, Mat v){
    if(x > R || y < L)  return;
    if(x <= L && y >= R){
      tree[root].val = v * tree[root].val;
      tree[root].add = v * tree[root].add;
      return;
    }

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

    Down(root, Lson, Rson);

    update(Lson, L, mid, x, y, v);
    update(Rson, mid+1, R, x, y, v);
    tree[root].val = tree[Lson].val + tree[Rson].val;
}


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

    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) % Mod;
}
int main(){

    scanf("%d%d", &n, &m);

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

    build(1, 1, n);

    int op, l, r;  LL x;

    for(int i = 1; i <= m; i++){
      scanf("%d", &op);
      if(op == 1){
        scanf("%d%d%lld", &l, &r, &x);
        Mat A;
        A.Row = A.Line = 2;
        A.a[0][0] = A.a[0][1] = A.a[1][0] = 1;  A.a[1][1] = 0;
        A = Pow(A, x-1);
        update(1, 1, n, l, r, A);
      }
      else{
        scanf("%d%d", &l, &r);
        printf("%lld\n", query(1, 1, n, l, r));
      }
    }

    return 0;
}

巡逻

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


输入样例

8 2
1 2
3 1
3 4
5 3
7 5
8 5
5 6


输出样例

10


题解(找树的直径+树形dp)

这题30分超简单,第二题实在弃疗了我就花了5min左右打了第三题的暴力。比赛完后发现满分也不难。首先题目跟出发点没啥关系,就是直接搞个最长的环最省时间,因为环上走一次,其他边要走两次。首先K=1直接找树的直径,统计长的为d,答案就是2*n-d,然后多了一个环,我一开始就想分类为相交与不相交分开处理。其实不用,我们将第一次选的直径上的边标记为-1就可以同时处理两种情况了。因为第一次取了第二次去相当于抵消了就没取,最后一样的方法算答案即可。这种取反的思想在数据结构中很常见。

树的直径众所周知有两种求法,一种是两遍dfs,另一种是一遍dfs树形dp,记录最长的向下路径和次长的。一开始可以采用第一种方法并记前驱标记那条直径,后面由于出现了负权边就只能树形dp了,因为有些边是可以不取的,而前者搞不定这种情况(坑点),然后这题的时间复杂度O(n)

话说这题的80分和90分是什么鬼呢。。


代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#define N 100010
#define oo 0x7fffffff
using namespace std;
int n, K, ans, head_p[N], cur = -1, dis1[N], dis2[N], pre_v[N], pre_e[N], f[N][2];
struct Tadj{int next, obj, len;} Edg[N<<1];
void Insert(int a, int b){
    Edg[++cur].next = head_p[a];
    Edg[cur].obj = b;
    Edg[cur].len = 1;
    head_p[a] = cur;
}
void dfs1(int root, int fa){
    for(int i = head_p[root]; ~ i; i = Edg[i].next){
      int v = Edg[i].obj;
      if(v == fa)  continue;
      dis1[v] = dis1[root] + 1;
      dfs1(v, root);
    }
}
void dfs2(int root, int fa){
    for(int i = head_p[root]; ~ i; i = Edg[i].next){
      int v = Edg[i].obj;
      if(v == fa)  continue;
      dis2[v] = dis2[root] + 1;
      pre_v[v] = root;
      pre_e[v] = i;
      dfs2(v, root);
    }
}

void Dp(int root, int fa){
    for(int i = head_p[root]; ~ i; i = Edg[i].next){
      int v = Edg[i].obj, l = Edg[i].len;
      if(v == fa)  continue;
      Dp(v, root);
      if(f[v][0] + l > f[root][0]){
        f[root][1] = f[root][0];
        f[root][0] = f[v][0] + l;
      }
      else if(f[v][0] + l > f[root][1])  f[root][1] = f[v][0] + l;
    }
}

int main(){
    scanf("%d%d", &n, &K);
    for(int i = 1; i <= n; i++)  head_p[i] = -1;
    for(int i = 1, a, b; i < n; i++){
      scanf("%d%d", &a, &b);
      Insert(a, b);  Insert(b, a);
    }
    int M1, p1, M2, p2;  M1 = M2 = 0;
    dfs1(1, 0);
    for(int i = 1; i <= n; i++)  if(dis1[i] > M1)  M1 = dis1[i], p1 = i;
    dfs2(p1, 0);
    for(int i = 1; i <= n; i++)  if(dis2[i] > M2)  M2 = dis2[i], p2 = i;
    ans += M2 + 1;
    if(K == 2){
      for(int i = p2; i != p1; i = pre_v[i])  Edg[pre_e[i]].len = Edg[pre_e[i]^1].len = -1;
      Dp(1, 0);
      M2 = 0;
      for(int i = 1; i <= n; i++)  M2 = max(M2, f[i][0] + f[i][1]);
      ans += M2 + 1;
    }
    printf("%d\n", ((n+K-1) << 1) - ans);
    return 0;
}

总结

这次考得真是一大败笔。主要败在第二题上。由于我一开始想到了方法所以感到不紧不慢,结果最后一刻也搞不定。遇到考试时策略很重要,应该看完所有题目,都想一想在动手。遇到题目,特别是像第二题这种码农题,一定要先想好框架,不要犯因为代码写得挫搞的调不出来的错误,不能单步跟踪就更要注意代码风格了。总之题目应该想清楚再编,像忘了Mod这种错误再犯就去死吧。Think twice, code once。平时打好基础,自己脑子不好没办法,平时又不勤奋,没有量变哪来的质变呢。拼一把吧。


旋转吧!雪月花。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值