CSP-S-2023 题解

应该是一年一次的活儿了(

今年的题比去年简单,所以补起来相对容易一点。

T1 lock

暴力,枚举每种密码锁的状态,再判断是否合法,注意出现相同的状态要判掉。

最大计算次数为 $10^{5} \times 8 \times 5 = 4 \times 10^{6}$ 不会炸。

丑陋的考场代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, ans, pos, sub, now[10], a[10][10];
bool flag;
int main() {
    // freopen("lock/lock1.in", "r", stdin);
//  freopen("lock.in", "r", stdin);
//  freopen("lock.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= 5; ++j) {
            scanf("%d", &a[i][j]);
        }
    }
    for(now[1] = 0; now[1] <= 9; ++now[1]) {
        for(now[2] = 0; now[2] <= 9; ++now[2]) {
            for(now[3] = 0; now[3] <= 9; ++now[3]) {
                for(now[4] = 0; now[4] <= 9; ++now[4]) {
                    for(now[5] = 0; now[5] <= 9; ++now[5]) {
                        flag = true;
                        for(int i = 1; i <= n; ++i) {
                            pos = 0, sub = 0;
                            for(int j = 1; j <= 5; ++j) {
                                if(a[i][j] != now[j]) {
                                    if(!pos) pos = j, sub = (now[j] - a[i][j] + 10) % 10;
                                    else {
                                        if(pos != j - 1 || sub != (now[j] - a[i][j] + 10) % 10) {
                                            flag = false;
                                            break;
                                        }
                                    }
                                }
                            }
                            if(!pos) flag = false;
                            if(!flag) break;
                        }
                        if(flag) ++ans;
                    }
                }
            }
        }
    }
    printf("%d", ans);
    return 0;
}
/*
g++ ./lock.cpp -o lock -Wall ‐O2 ‐std=c++14 ‐static
./lock
*/
/*
感觉是道暴力
10^5*8*5=4e6
能过,开敲
15min过大样例
应该没问题,想T2
*/

T2 game

讲个笑话,类似这道题目的一道字符串计数类题目,我在考前做过,还给一个学弟讲过,但是考场上就是没想出来。

$\mathcal{O}(n^{3})$ 的区间dp不讲。

$\mathcal{O}(n^{2})$ 的思路是,把每种字符看成一种括号,枚举左端点来进行括号匹配,如果某一刻栈为空则说明当前子串满足条件,于是统计答案。

$\mathcal{O}(n)/\mathcal{O}(n \log n)$ 的正解其实和 $\mathcal{O}(n^{2})$ 的思路非常接近,我们发现我们枚举每一种情况的左端点非常费时,考虑如何优化掉枚举左端点这一维。

看统计答案的「栈为空」这个条件,如果我们把左端点前面那一段字符串加进去,那么当前栈也会加上同样一段的字符串(可以理解为两个相同的空串被相同地更改后得到的答案是一样的)!

于是可以哈希当前栈的状态,每次答案加上和当前哈希状态相同的位置数量,这样就省掉了枚举左端点那一维!

顺带一提,哈希这个技巧去年T3也考了!

解题思路和解题方法我都知道,为什么没做出来?!

丑陋的补题代码(模板略去):

const int mul1 = 299987, mul2 = 97, mod1 = 998244353, mod2 = 100007;
ll n, ans, top, hsh1, hsh2, power1[2000005], power2[2000005];
char s[2000005], st[2000005];
unordered_map<ll, ll> mp[mod2];
int main() {
    read(n);
    scanf("%s", s + 1);
    power1[0] = power2[0] = 1;
    ++mp[0][0];
    fu(i, 1, n) {
        power1[i] = power1[i - 1] * mul1 % mod1;
        power2[i] = power2[i - 1] * mul2 % mod2;
        if(top && st[top] == s[i]) {
            hsh1 = ((hsh1 - st[top] * power1[top] % mod1) % mod1 + mod1) % mod1;
            hsh2 = ((hsh2 - st[top] * power2[top] % mod2) % mod2 + mod2) % mod2;
            --top;
        }
        else {
            st[++top] = s[i];
            hsh1 = (hsh1 + s[i] * power1[top] % mod1) % mod1;
            hsh2 = (hsh2 + s[i] * power2[top] % mod2) % mod2;
        }
        if(mp[hsh2].count(hsh1)) ans += mp[hsh2][hsh1];
        ++mp[hsh2][hsh1];
    }
    write(ans);
    flush();
    return 0;
}

T3 struct

大模拟,先把代码放上来,最后写思路。

还上台讲了这道题,太社死了qwq

我主要讲几个易错点:

  • 地址是否属于当前内存区间的时候左端点或右端点忘判。
  • 分配内存的时候没有对齐,直接连续分配。
  • 地址偏移量算错。
  • 少输出了空地址的情况。

还有!我要吐槽的是!CCF你的题面能安排得不要这么阴间吗?最重要的定义你放提示里去???

代码里有考场注释,应该不难理解吧qwq

丑陋的考场代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, k, op, hs, addr, o, b, now_add = -1;
string t, name;
ll str_idx, str_mx[110], str_size[110];//str_idx表示当前有多少已定义的结构体,str_mx表示当前结构体最大的元素大小,str_size表示结构体大小
string str_name[110];//str_name表示结构体名称
struct meb {
    ll typ, name, add;
    meb(ll Typ = 0, ll Name = 0, ll Add = 0): typ(Typ), name(Name), add(Add) {}
};//成员,typ表示类型,name表示名称,add表示起始位置
vector<meb> str[110];//结构体
map<string, ll> str_id;//结构体对应编号
string temp_name[110];//temp_name表示元素名称
ll temp_idx, temp_type[110], temp_add[110];//temp_idx表示当前有多少元素,temp_tpye表示元素类型,temp_add表示起始地址
map<string, ll> temp_id;//元素对应编号
bool have_found;
ll get_hsh(string s) {//得到名称对应的哈希值
    ll ret = 0;
    for(const auto& i : s) {
        ret = ret * 27 + (i - 'a' + 1);
    }
    return ret;
}
string rehsh(ll hs) {//解哈希
    string ret = "";
    while(hs) {
        ret = (char)(hs % 27 - 1 + 'a') + ret;
        hs /= 27;
    }
    return ret;
}
void find_temp(ll now, ll typ, string pth) {
    // cout << now << " " << typ << " " << pth << '\n';
    if(pth.find('.') != pth.npos) name = pth.substr(0, pth.find('.'));
    else name = pth;
    hs = get_hsh(name);
    for(const auto& i : str[typ]) {
        if(i.name == hs) {
            if(pth.find('.') != pth.npos) {//继续访问子成员
                find_temp(now + i.add, i.typ, pth.substr(pth.find('.') + 1));
            }
            else {//已访问到目标成员
                cout << now + i.add << '\n';
            }
            return;
        }
    }
}
void find_addr(ll now, ll typ, string pth) {
    // cout << now << " " << typ << " " << pth << '\n';
    if(typ <= 4) {
        if(addr < now + str_size[typ]) cout << pth << '\n';
        else cout << "ERR\n";
        return;
    }
    pth += '.';
    for(const auto& i : str[typ]) {
        if(now + i.add <= addr && addr < now + i.add + str_size[i.typ]) return find_addr(now + i.add, i.typ, pth + rehsh(i.name));
    }
    cout << "ERR\n";
}
int main() {
    // freopen("struct/struct3.in", "r", stdin);
//  freopen("struct.in", "r", stdin);
//  freopen("struct.out", "w", stdout);
    // ios::sync_with_stdio(0);
    // cin.tie(0), cout.tie(0);
    // 为了保险还是不关同步流了罢
    cin >> n;
    str_id["byte"] = ++str_idx;
    str_mx[str_idx] = str_size[str_idx] = 1;
    str_id["short"] = ++str_idx;
    str_mx[str_idx] = str_size[str_idx] = 2;
    str_id["int"] = ++str_idx;
    str_mx[str_idx] = str_size[str_idx] = 4;
    str_id["long"] = ++str_idx;
    str_mx[str_idx] = str_size[str_idx] = 8;
    while(n--) {
        cin >> op;
        // cout << n << ":\n";
        // cout << op << '\n';
        switch(op) {
            case 1: {//定义一个结构体类型
                cin >> t >> k;
                str_id[t] = ++str_idx;
                str_name[str_idx] = t;
                o = 0, b = 0;
                for(int i = 1; i <= k; ++i) {
                    cin >> t >> name;
                    str_mx[str_idx] = max(str_mx[str_idx], str_mx[str_id[t]]);
                    if(i > 1) {
                        o = ((o + str_size[str[str_idx].back().typ] + str_mx[str_id[t]] - 1) / str_mx[str_id[t]]) * (str_mx[str_id[t]]);
                        b = ((b + str_size[str[str_idx].back().typ] + str_mx[str_id[t]] - 1) / str_mx[str_id[t]]) * (str_mx[str_id[t]]);
                    }
                    // cout << o << " " << b << '\n';
                    str[str_idx].push_back(meb(str_id[t], get_hsh(name), b));
                }
                str_size[str_idx] = ((o + str_size[str[str_idx].back().typ] + str_mx[str_idx] - 1) / str_mx[str_idx]) * (str_mx[str_idx]);
                cout << str_size[str_idx] << ' ' << str_mx[str_idx] << '\n';
                break;
            }
            case 2: {//定义一个元素
                cin >> t >> name;
                temp_id[name] = ++temp_idx;
                temp_name[temp_idx] = name;
                temp_type[temp_idx] = str_id[t];
                temp_add[temp_idx] = ((now_add + str_mx[str_id[t]]) / str_mx[str_id[t]]) * str_mx[str_id[t]];
                now_add = temp_add[temp_idx] + str_size[str_id[t]] - 1;
                cout << temp_add[temp_idx] << '\n';
                break;
            }
            case 3: {//访问某个元素
                cin >> t;
                if(t.find('.') == t.npos) {//没有成员
                    cout << temp_add[temp_id[t]] << '\n';
                }
                else {//有成员
                    name = t.substr(0, t.find('.'));
                    t = t.substr(t.find('.') + 1);
                    find_temp(temp_add[temp_id[name]], temp_type[temp_id[name]], t);
                }
                break;
            }
            case 4: {//访问某个内存地址
                cin >> addr;
                have_found = false;
                for(int i = 1; i <= temp_idx; ++i) {
                    if(temp_add[i] <= addr && addr < temp_add[i] + str_size[temp_type[i]]) {
                        find_addr(temp_add[i], temp_type[i], temp_name[i]);
                        have_found = true;
                        break;
                    }
                }
                if(!have_found) cout << "ERR\n";
                break;
            }
        }
    }
    return 0;
}
/*
g++ ./struct.cpp -o struct -Wall ‐O2 ‐std=c++14 ‐static
./struct
*/
/*
int->short
0~3->0~1
short->int
4~5->4~7
*/
/*
感觉像大模拟,不是很难的感觉
应该比T2更可做
15:25来打的
【】!过大样例了!
17:35
还有不到1h,先把T4暴力打了
*/

T4 tree

二分套二分再加贪心。

要下课了,先把代码贴上来。

首先我们可以发现满足要求答案区间是一个 $\left[ans, +\infty\right)$,这个区间具有一个可以二分的性质(具体是什么性质我忘了,有无好心人指出),考虑二分答案。

现在来想 check 怎么写(假设当前检查是否合法的答案是 ck),令树种下的时间为 $d_{i}$,那么这棵树长高的时间就是 $[d_{i}, ck]$,如果要这段时间树长高的高度正好大于 $a_{i}$,我们同样可以二分 $d_{i}$。

二分出最大的 $d_{i}$ 后,我先是想了一个简单树形dp,求出每个节点最晚在多久种树,然后挂了。。。

改成普通贪心就过了,每次找到一个 $d_{i}$ 最小的节点,一直往父亲节点跳,用栈记录经过了哪些节点,从上往下依次设置种植时间,每次时间变化量为 $+1$。

直到跳到一个被设置过时间的节点就退出(设置的时候不包含这个节点)。

如果发现一个节点设置的时间大于了最晚种植时间,那么肯定无解。

于是 check 就打完了。

至于二分套二分的内层 check 怎么打,有点抽象,我也不好说,可以配合代码yy一下。

卡常!luogu上会T一个点,但是把 long double(或是 __int128) 换成 long long 可以过,但这样可以卡!

丑陋的补题代码(模板略去):

typedef long double lxd;
int n, u, v, l, r, mid, ans, cnt, top, L, R, Mid, st[100001], fa[100001];
ll a[100001], b[100001], c[100001], d[100001];
vector<int> g[100001];
bool vis[100001];
pair<ll, int> p[100001];
bool calc(ll s, ll t, int i) {
    lxd hight = 0;
    if(c[i] >= 0) hight = b[i] * (t - s + 1) + (lxd)(s + t) * (t - s + 1) / 2 * c[i];
    else {
        ll m = min(max((b[i] + -c[i] - 1) / -c[i] - 1, s - 1), t);
        if(m >= s) hight = (m - s + 1) * b[i] + (lxd)(s + m) * (m - s + 1) / 2 * c[i];
        hight += t - m;
    }
    return hight >= a[i];
}
void dfs(int now) {
    for(const auto& i : g[now]) {
        if(i != fa[now]) {
            fa[i] = now;
            dfs(i);
        }
    }
}
bool check(int ck) {
    for(int i = 1; i <= n; ++i) {
        if(!calc(1, ck, i)) return false;
        vis[i] = false;
        L = 1, R = ck;
        while(L <= R) Mid = (L + R) >> 1, (calc(Mid, ck, i)) ? (d[i] = Mid, L = Mid + 1) : (R = Mid - 1);
        p[i].first = d[i], p[i].second = i;
    }
    stable_sort(p + 1, p + 1 + n);
    cnt = 0;
    for(int i = 1; i <= n; ++i) {
        u = p[i].second;
        if(!vis[u]) {
            top = 0;
            while(!vis[u]) {
                st[++top] = u;
                u = fa[u];
            }
            while(top) {
                vis[st[top]] = true;
                d[st[top]] = ++cnt;
                --top;
            }
        }
        if(d[p[i].second] > p[i].first) return false;
    }
    return true;
}
int main() {
    read(n);
    for(int i = 1; i <= n; ++i) {
        read(a[i], b[i], c[i]);
    }
    for(int i = 2; i <= n; ++i) {
        read(u, v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1);
    l = 1, r = 1000000000;
    vis[0] = true;
    while(l <= r) mid = (l + r) >> 1, check(mid) ? (ans = mid, r = mid - 1) : (l = mid + 1);
    printf("%d", ans);
    return 0;
}
posted @ 2023-10-23 16:56  A_box_of_yogurt  阅读(14)  评论(0编辑  收藏  举报  来源
Document