第十九次CCF计算机软件能力认证

本文探讨了线性分类器的实现,通过ACwing题目实例展示如何利用双指针解决稀疏向量问题,并深入解析Markdown渲染器和复杂的状态转移方程,涉及算法如转移矩阵和递归。适合理解算法和数据结构在实际问题中的应用。
摘要由CSDN通过智能技术生成

1、线性分类器

ACwing 3287

#include <iostream>
#include <algorithm>
#include <set>
using namespace std;

typedef long long LL;
const int N = 1010;

int n, m;
struct Node {
    int x, y;
    char type;
} node[N];
int p0, p1, p2;
set<char> st_u, st_d;

bool get(int x, int y) {
    return p0 + (LL) x * p1 + (LL) y * p2 > 0;
}

int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> node[i].x >> node[i].y >> node[i].type;
    while (m--) {
        cin >> p0 >> p1 >> p2;
        st_u.clear(), st_d.clear();
        for (int i = 0; i < n; i++) {
            int res = get(node[i].x, node[i].y);
            if (res > 0) st_u.insert(node[i].type);
            else st_d.insert(node[i].type);
        }
        if (st_d.size() > 1 || st_u.size() > 1) puts("No"); else puts("Yes");
    }
    return 0;
}

2、稀疏向量(双指针)

ACwing 3288

#include <iostream>
#include <algorithm>
using namespace std;

#define x first
#define y second

typedef long long LL;
typedef pair<int, int> PII;
const int N = 5e5 + 10;

int n, a, b;
PII A[N], B[N];

int main() {
    scanf("%d%d%d", &n, &a, &b);
    for (int i = 0; i < a; i++) scanf("%d%d", &A[i].x, &A[i].y);
    for (int i = 0; i < b; i++) scanf("%d%d", &B[i].x, &B[i].y);
    sort(A, A + a);
    sort(B, B + b);
    LL res = 0;
    for (int i = 0, j = 0; i < a && j < b;)
        if (A[i].x < B[j].x) i++;
        else if (A[i].x > B[j].x) j++;
        else res += (LL) A[i].y * B[j].y, i++, j++;
    printf("%lld", res);
    return 0;
}

3、Markdown渲染器

ACwing 3289

4、1246

ACwing 3290

首先,由题目可知:

  • 1:2
  • 2:4
  • 4:1,6
  • 6:6,4

也就是说, 2 2 2 可以由 1 1 1 转换过来, 4 4 4 可以由 2 、 6 2、6 26 转换过来…

S = 1 S= 1 S=1 的时候,可以发现时刻 n n n 的状态是完全取决于时刻 n − 1 n-1 n1 的状态。也就是有转移方程:

  • f ( n , 1 ) = f ( n − 1 , 4 ) f(n, 1) = f(n-1, 4) f(n,1)=f(n1,4)
  • f ( n , 2 ) = f ( n − 1 , 1 ) f(n, 2) = f(n- 1, 1) f(n,2)=f(n1,1)
  • f ( n , 4 ) = f ( n − 1 , 2 ) + f ( n − 1 , 6 ) f(n, 4) = f(n - 1, 2) + f(n-1, 6) f(n,4)=f(n1,2)+f(n1,6)
  • f ( n , 6 ) = f ( n − 1 , 4 ) + f ( n − 1 , 6 ) f(n, 6) = f(n-1,4) + f(n-1,6) f(n,6)=f(n1,4)+f(n1,6)

S = 2 S = 2 S=2 的时候,这两位数可能存在两种构造情况:

  • 由上一个状态的一个数产生,比如 4 4 4 可以产生 16 16 16 6 6 6 可以产生 64 64 64
  • 由上一个状态的两个相邻的数产生。

那么就存在如下规律:

  • 1:2
  • 2:4
  • 4:1,6,16
  • 6:6,4,64
  • 16:26 (1产生2,6产生64,取前两位)
  • 26:46
  • 41:62
  • 42:64
  • 44:61
  • 46:66
  • 61:42
  • 62:44
  • 64:41
  • 66:46

可见可以产生的两位数总共仅有 14 14 14 总,且第 n n n 行的两位数完全取决于第 n − 1 n-1 n1 行两位数的状态。

分析到这里可以考虑使用矩阵快速幂进行状态转移(根据数据,处理出 S = 1 、 S = 2 S= 1、S = 2 S=1S=2 的情况,已经可以拿到96分!)。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long LL;
const int N = 14, MOD = 998244353;

int n;
string S;
int id[100]; // 将下面14个数映射成从0开始的id
vector<int> vers{1, 2, 4, 6, 16, 26, 41, 42, 44, 46, 61, 62, 64, 66};
vector<vector<int>> g{{2}, {4}, {1, 6, 16}, {6, 4, 64}, {26}, {46}, {62}, {64}, {61}, {66}, {42}, {44}, {41}, {46}};
int tr[N][N]; // 转移矩阵

void init() {
    memset(id, -1, sizeof id);
    for (int i = 0; i < N; i++) id[vers[i]] = i;
    for (int i = 0; i < N; i++)
        for (auto x: g[i])
            tr[i][id[x]]++;
}

// 矩阵乘法
void mul(int c[][N], int a[][N], int b[][N]) {
    static int tmp[N][N];
    memset(tmp, 0, sizeof tmp);
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                tmp[i][j] = (tmp[i][j] + (LL) a[i][k] * b[k][j]) % MOD;
    memcpy(c, tmp, sizeof tmp);
}

int qmi(int k, int id) {
    if (id == -1) return 0;
    int res[N][N] = {0}, w[N][N];
    memcpy(w, tr, sizeof w);
    res[0][0] = 1;

    while (k) {
        if (k & 1) mul(res, res, w);
        mul(w, w, w);
        k >>= 1;
    }
    return res[0][id];
}

int main() {
    init();
    cin >> n >> S;
    cout << qmi(n, id[stoi(S)]) << endl;
    return 0;
}

考虑 S > 2 S > 2 S>2 的情况,因为有如下关系:

  • 1:2
  • 2:4
  • 4:16
  • 6:64

对于任意一串数据,可以根据第一个数的开头数字确定它是由哪个数转移过来的,比如: 64162641664 64162641664 64162641664
在这里插入图片描述
对于任一个一数,它的构成只可能存在两种情况,比如 164 164 164在这里插入图片描述
对于中间的 6 6 6 而言,它可能和前面的 1 1 1 一起,是由上一个状态的 4 4 4 产生,也可能是和后面的 4 4 4 一起,是由上一个状态的 6 6 6 产生的。

所以对于一串数的首位而言,可能存在三种情况:

  • 首位直接匹配;
  • 首位是 4 4 4,可以在前面添加一个 6 6 6
  • 首位是 6 6 6,可以在前面添加一个 1 1 1

而对于末位,可能存在位不足,需要特判。比如单独留下一个 6 6 6,这种情况可以认为它的上一个状态是 6 6 6;如果单独留下一个 1 1 1,那么可以认为它的上一个状态是 4 4 4

可以暴力枚举它的所有情况,一层层往上迭代就转换为了上面的 S = 1 、 S = 2 S = 1、S = 2 S=1S=2 的问题。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long LL;
const int N = 14, MOD = 998244353;

int n;
string S;
int id[100]; // 将下面14个数映射成从0开始的id
vector<int> vers{1, 2, 4, 6, 16, 26, 41, 42, 44, 46, 61, 62, 64, 66};
vector<vector<int>> g{{2}, {4}, {1, 6, 16}, {6, 4, 64}, {26}, {46}, {62}, {64}, {61}, {66}, {42}, {44}, {41}, {46}};
int tr[N][N]; // 转移矩阵

void init() {
    memset(id, -1, sizeof id);
    for (int i = 0; i < N; i++) id[vers[i]] = i;
    for (int i = 0; i < N; i++)
        for (auto x: g[i])
            tr[i][id[x]]++;
}

void mul(int c[][N], int a[][N], int b[][N]) {
    static int tmp[N][N];
    memset(tmp, 0, sizeof tmp);
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                tmp[i][j] = (tmp[i][j] + (LL) a[i][k] * b[k][j]) % MOD;
    memcpy(c, tmp, sizeof tmp);
}

int qmi(int k, int id) {
    if (id == -1) return 0;
    int res[N][N] = {0}, w[N][N];
    memcpy(w, tr, sizeof w);
    res[0][0] = 1;

    while (k) {
        if (k & 1) mul(res, res, w);
        mul(w, w, w);
        k >>= 1;
    }
    return res[0][id];
}

string get(string str) {
    string res;
    for (int i = 0; i < str.size(); i++)
        if (str[i] == '2') res += '1';
        else if (str[i] == '4') res += '2';
        else if (str[i] == '1') {
            if (i + 1 == str.size() || str[i + 1] == '6') res += '4', i++; else return "";
        } else {
            if (i + 1 == str.size() || str[i + 1] == '4') res += '6', i++; else return "";
        }
    return res;
}

int dfs(int k, string &str) {
    if (str.size() <= 2) return qmi(k, id[stoi(str)]);
    int res = 0;
    for (string s: {"", "1", "6"}) {
        auto t = get(s + str);
        if (t.size()) res = (res + dfs(k - 1, t)) % MOD;
    }
    return res;
}

int main() {
    init();
    cin >> n >> S;
    cout << dfs(n, S) << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值