Skip to content

CSP-2023题解

Posted on:2023年10月24日 at 21:09

游记就不写了,为了防止别人认为我似了所以写一下题解。

赛时挂太惨了。

顺便给订阅我博客的访客们道个歉,我自认为写不出什么有深度的文章,且大多数思路我一般都是记下来自己看的,所以文章更新频率会很低。

在退役之后说不定会有每周/两周杂谈?说不准,有时间的话我会尝试的。(内心os:这玩意最好不要变成我每周放几道数学题上去)

提高组T1 - 密码锁 | 暴力

我们机房的人写这道题的平均用时甚至短于 J 组的第一题。

不过这个倒是不难写,暴力枚举所有可能性然后状压一下扔进 set 去重即可。

代码简单不贴。

提高组T2 - 消消乐 | hash 和简单的计数

原题?但是没做过,考场上一直在想拆成多个回文串这个错误的思路。

这里的做法是与值域无关的做法。

然后我们发现我们本质上就是要找括号匹配的对数。

我们考虑每个字母构成一种括号,贪婪地匹配,也就是说 A..A..A..A(..)..(..) 的匹配方法一定不劣于 (..(..)..) 的方法。

这一部分我们可以用栈模拟,若栈顶元素与栈顶上一个元素相同,则弹出。

那什么时候是合法的呢?栈在每个位置会有一种状态,如果存在两个不同的位置 i,ji,j,且在这两个位置时,栈的状态相同,则就有一对匹配。

为什么?仔细思考一下,栈在每个位置都会加入元素,并且可能弹出元素。我们加入了一些元素,然后弹出了一些元素,栈的状态仍然不变,所以这些元素(加入又弹出的元素)应当构成了一些合法的匹配。

如何快速判断相同的元素呢?考虑对栈进行 hash,最后开个 map 扫一遍统计一下相同就行了。

栈 hash

为了照顾一下大家,这里讲讲我的思考。

对栈进行 hash,我们需要同时维护内部的权值和元素的位置关系,设栈顶到栈底元素依次为 a0,a1,a2,a3,,ana_0,a_1,a_2,a_3,\cdots,a_n,那么我们可以考虑将它的 hash 值设为 a0+a1p+a2p2++anpna_0+a_1p+a_2p^2+\cdots+a_np^npp 可以是你选择的任意数)。

在入栈元素的时候,相当于所有元素都向下移动了一位,所以我们将原先的 hash 值乘上 pp,然后再加上当前的权值。

出栈元素的时候,相当于弹出当前元素,其他元素都向上移动了以为,所以我们将原先的 hash 值减去当前的权值,然后再除 pp(实际上你应该乘上乘法逆元,这个是肯定的)。

然后这道题就做完了,我写了个双 hash 防止被卡。

下面的是 CF1223F 的通过代码,这道题的话,去掉多测,输入更改一下即可,不要直接复制哦。

// Problem: F. Stack Exterminable Arrays
// Contest: Codeforces - Technocup 2020 - Elimination Round 1
// URL: https://codeforces.com/problemset/problem/1223/F
// Memory Limit: 256 MB
// Time Limit: 2000 ms

// we hash like this (stack hash)
// p^k*a_0 + p^{k-1}*a_1 + ......
// add : f*p + a
// del : (f-a)/p

#include<stdio.h>
#include<stack>
#include<map>
#include<random>

const int maxn = 3.2e5;

const int mod1 = 998244353,
          mod2 = 1000000007;

int pow1(int x, int p){
    int res = 1;
    while(p){
        if(p&1)
            res = 1ll * res * x % mod1;
        x = 1ll * x * x % mod1;
        p>>=1;
    }
    return res;
}
int pow2(int x, int p){
    int res = 1;
    while(p){
        if(p&1)
            res = 1ll * res * x % mod2;
        x = 1ll * x * x % mod2;
        p>>=1;
    }
    return res;
}

int inv1(int x) { return pow1(x, mod1-2); }
int inv2(int x) { return pow2(x, mod2-2); }

const int lucky1 = 387, lucky1inv = inv1(387),
            lucky2 = 961, lucky2inv = inv2(961);
int hsh1[maxn], hsh2[maxn];

std::stack<int> st;
std::map<std::pair<int,int>,int> cnts;
int hs1, hs2;

void in_stack(int x){
    hs1 = (1ll * hs1 * lucky1 + hsh1[x])%mod1;
    hs2 = (1ll * hs2 * lucky2 + hsh2[x])%mod2;
    st.push(x);
}

void pop_stack(){
    hs1 = 1ll * (hs1 + mod1 - hsh1[st.top()])%mod1 * lucky1inv %mod1;
    hs2 = 1ll * (hs2 + mod2 - hsh2[st.top()])%mod2 * lucky2inv %mod2;
    st.pop();
}

int T, n, li[maxn];

std::mt19937 rng(std::random_device{}());

long long ans;

int main(){
    for(int i=0 ; i<maxn ; i++)
        hsh1[i] = rng() % mod1,
        hsh2[i] = rng() % mod2;

    scanf("%d", &T);
    while(T--){
        hs1 = hs2 = ans = 0;
        while(!st.empty())
            st.pop();
        cnts.clear();

        scanf("%d", &n);
        for(int i=1;i<=n;i++)
            scanf("%d",li+i);

        cnts[{0,0}]++;
        for(int i=1;i<=n;i++){
            in_stack(li[i]);
            while(st.size() >= 2){
                int v = st.top();
                pop_stack();
                if(st.top() == v)
                    pop_stack();
                else{
                    in_stack(v);
                    break;
                }
            }
            cnts[{hs1,hs2}]++;
        }

        for(auto x:cnts)
            ans += 1ll * (x.second) * (x.second-1) / 2;
        printf("%lld\n", ans);
    }
    return 0;
}

提高组T3 - 结构体 | 超级大模拟

提前声明一下:本文中间的代码片段均是从最后的代码中截取,没有经过测试,若出现错误请及时联系我更正,并结合最后的代码理解。

这道题题意不难,我们主要是要优化模拟的思路,降低码量,让自己更有可能在考场上写出来。

我的思路是这样的:「结构体」之间有依赖关系,构成一个 DAG,我们可以用一个 struct 加上指针来存。

// 简单的工具函数,用于内存对齐,为了防止下文突然出现,所以放在这里
i64 justify(i64 siz, i64 alignment)          { while(siz % alignment) siz++; return siz; }
i64 assign (i64 siz, i64 mem, i64 alignment) { return justify(siz, alignment)+mem; }
struct structure{
    i64 alignment, size;
    vector<pair<structure*, std::pair<string, i64>>> sons; // 三项分别是 指向成员的指针,成员的名称,成员相对于自己的偏移量
    structure(i64 alignment_, i64 size_) : alignment(alignment_), size(size_) {}
};

为了方便根据结构体类型名查询结构体对象,开一个 map<string, structure*> 来存。

我们为结构体添加成员的代码可以这么写:

map<string, structure* > pool;
struct structure{
    i64 alignment, size;
    vector<pair<structure*, std::pair<string, i64>>> sons;
    structure(i64 alignment_, i64 size_) : alignment(alignment_), size(size_) {}
    void append_child_type(const string& name, const string& prop_name){
        structure* typ = pool[name];
        sons.push_back({typ, {prop_name, justify(size, typ->alignment)}});
        size = assign(size, typ->size, typ->alignment);
        alignment = max(alignment, typ->alignment);
    }
    void final_append(){ // 结束插入的时候按题意需要对齐
        size = justify(size, alignment);
    }
};
structure* register_basic_type(const string& name, i64 size) { return pool[name] = new structure(size, size); }
structure* register_structure_type(const string& name)       { return pool[name] = new structure(0, 0); }

最后声明一个结构体的代码是这样的:

void register_structure(string name, int count){
    typed::structure* typ = typed::register_structure_type(name);
    string type_name, prop;
    for(int i=0;i<count;i++)
        cin >> type_name >> prop,
        typ->append_child_type(type_name, prop);
    typ->final_append();
    cout << typ->size << " " << typ->alignment << endl;
}

然后我们发现,「变量」和「结构体」二者是有区别的,且名字可能重复,但是二者又有关联——「变量」依赖于「结构体」的类型,所以我们最好的方法是对「变量」再开一个 struct,存储所属的结构体和它的成员。

同样开一个 map<string, variable*> var_pool; 用于快速查询。

我们发现一个性质:变量和它的成员们的关系构成一棵树,所以还应该有指向其儿子的指针。

为什么不一上来就开完呢?看看数据范围,addr1018addr \le 10^{18},这个空间量不允许开完,所以我们使用指针来维护变量构成的树,并且动态地为变量分配空间。

因为 n,k100n,k \le 100,所以这没有问题。

struct variable;
map<string, variable*> var_pool;
struct variable{
    i64 mem_begin;
    structure* type;
    variable* subnum[maxn];
    variable(structure* type_ = nullptr) : type(type_) { for(int i=0; i<maxn; i++) subnum[i] = nullptr; }
    variable* get_prop(const string& s){ // 获取变量的属性(变量对应的结构体的成员)
        for(int i=0; i<type->sons.size(); i++)
            if(type->sons[i].second.first == s){
                if(subnum[i] == nullptr)
                    subnum[i] = new variable(type->sons[i].first),
                    subnum[i]->mem_begin = mem_begin + type->sons[i].second.second;
                return subnum[i];
            }
    }
}root;

那么声明变量的时候,我们就可以这么写:

void register_variable(string type, string name){
    typed::variable* res = new typed::variable(typed::pool[type]);
    typed::var_pool[name] = res;
    typed::root.subnum[typed::cntroot++] = res;
    typed::root.type->append_child_type(type, name);
    res->mem_begin = typed::justify(typed::varbegin, res->type->alignment);
    typed::varbegin = res->mem_begin + res->type->size;
    cout << res->mem_begin << endl;
}

然后我们看看剩下的操作:根据变量访问内存和根据内存访问变量,本质上就是在变量树上进行寻址,代码不难。

void assign_variable(){
    cin.get();
    string buffer, buf2;
    bool jumped = 0;
    typed::variable* v = nullptr;
    std::getline(cin, buffer);
    for(int i=0;i<buffer.size();i++){
        if(buffer[i] == '.'){
            if(not jumped)
                jumped = 1,
                v = typed::var_pool[buf2];
            else
                v = v->get_prop(buf2);
            buf2 = "";
        }
        else
            buf2 += buffer[i];
    }
    if(not jumped)
        v = typed::var_pool[buf2];
    else
        v = v->get_prop(buf2);
    cout << v->mem_begin << endl;
}

void assign_memory(typed::variable* v, i64 delta_remain, string prefix){
    if(v->type->sons.size() == 0) {
        if(prefix.size() == 0)
            cout << "ERR" << endl;
        else
            cout << prefix.substr(0, prefix.size()-1) << endl;
        return;
    }

    for(int i=0; i<v->type->sons.size(); i++){
        i64 l = v->type->sons[i].second.second;
        i64 r = l + v->type->sons[i].first->size;
        if(l <= delta_remain and delta_remain < r){
            if(v->subnum[i] == nullptr){
                v->subnum[i] = new typed::variable(v->type->sons[i].first);
                v->subnum[i]->mem_begin = v->mem_begin + v->type->sons[i].second.second;
            }
            return assign_memory(v->subnum[i], delta_remain-l, prefix + v->type->sons[i].second.first+'.');
        }
    }
    cout << "ERR" << endl;
}

最后将它们拼凑在一起,你就得到了本题的 AC 代码。

码量不算小,但写起来比较简洁,且方便调试。

// Problem: P9754 [CSP-S 2023] 结构体
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P9754?contestId=140859
// Memory Limit: 512 MB
// Time Limit: 1000 ms

#include<iostream>
#include<stdio.h>
#include<string>
#include<map>
#include<vector>

using namespace std;

const int maxn = 107;

typedef long long i64;

namespace typed{
    i64 justify(i64 siz, i64 alignment)          { while(siz % alignment) siz++; return siz; }
    i64 assign (i64 siz, i64 mem, i64 alignment) { return justify(siz, alignment)+mem; }

    struct structure;
    map<string, structure* > pool;
    struct structure{
        i64 alignment, size;
        vector<pair<structure*, std::pair<string, i64>>> sons;
        structure(i64 alignment_, i64 size_) : alignment(alignment_), size(size_) {}
        void append_child_type(const string& name, const string& prop_name){
            structure* typ = pool[name];
            sons.push_back({typ, {prop_name, justify(size, typ->alignment)}});
            size = assign(size, typ->size, typ->alignment);
            alignment = max(alignment, typ->alignment);
        }
        void final_append(){
            size = justify(size, alignment);
        }
    };

    structure* register_basic_type(const string& name, i64 size) { return pool[name] = new structure(size, size); }
    structure* register_structure_type(const string& name)       { return pool[name] = new structure(0, 0); }

    struct variable;
    map<string, variable*> var_pool;
    struct variable{
        i64 mem_begin;
        structure* type;
        variable* subnum[maxn];
        variable(structure* type_ = nullptr) : type(type_) { for(int i=0; i<maxn; i++) subnum[i] = nullptr; }
        variable* get_prop(const string& s){
            for(int i=0; i<type->sons.size(); i++)
                if(type->sons[i].second.first == s){
                    if(subnum[i] == nullptr)
                        subnum[i] = new variable(type->sons[i].first),
                        subnum[i]->mem_begin = mem_begin + type->sons[i].second.second;
                    return subnum[i];
                }
        }
    }root;
    i64 cntroot, varbegin;

    void prework(){
        register_basic_type("byte" , 1),
        register_basic_type("short", 2),
        register_basic_type("int"  , 4),
        register_basic_type("long" , 8),
        root.type = new structure(1, 0);
    }
}

namespace operations{
    void register_structure(string name, int count){
        typed::structure* typ = typed::register_structure_type(name);
        string type_name, prop;
        for(int i=0;i<count;i++)
            cin >> type_name >> prop,
            typ->append_child_type(type_name, prop);
        typ->final_append();
        cout << typ->size << " " << typ->alignment << endl;
    }
    void register_variable(string type, string name){
        typed::variable* res = new typed::variable(typed::pool[type]);
        typed::var_pool[name] = res;
        typed::root.subnum[typed::cntroot++] = res;
        typed::root.type->append_child_type(type, name);
        res->mem_begin = typed::justify(typed::varbegin, res->type->alignment);
        typed::varbegin = res->mem_begin + res->type->size;
        cout << res->mem_begin << endl;
    }
    void assign_variable(){
        cin.get();
        string buffer, buf2;
        bool jumped = 0;
        typed::variable* v = nullptr;
        std::getline(cin, buffer);
        for(int i=0;i<buffer.size();i++){
            if(buffer[i] == '.'){
                if(not jumped)
                    jumped = 1,
                    v = typed::var_pool[buf2];
                else
                    v = v->get_prop(buf2);
                buf2 = "";
            }
            else
                buf2 += buffer[i];
        }
        if(not jumped)
            v = typed::var_pool[buf2];
        else
            v = v->get_prop(buf2);
        cout << v->mem_begin << endl;
    }

    void assign_memory(typed::variable* v, i64 delta_remain, string prefix){
        if(v->type->sons.size() == 0) {
            if(prefix.size() == 0)
                cout << "ERR" << endl;
            else
                cout << prefix.substr(0, prefix.size()-1) << endl;
            return;
        }

        for(int i=0; i<v->type->sons.size(); i++){
            i64 l = v->type->sons[i].second.second;
            i64 r = l + v->type->sons[i].first->size;
            if(l <= delta_remain and delta_remain < r){
                if(v->subnum[i] == nullptr){
                    v->subnum[i] = new typed::variable(v->type->sons[i].first);
                    v->subnum[i]->mem_begin = v->mem_begin + v->type->sons[i].second.second;
                }
                return assign_memory(v->subnum[i], delta_remain-l, prefix + v->type->sons[i].second.first+'.');
            }
        }
        cout << "ERR" << endl;
    }
}

i64 n, op, u, v;
string s, typ, nam;

int main(){
    typed::prework();
    scanf("%d", &n);

    for(int i=1;i<=n;i++){
        cin >> op;
        if(op == 1)
            cin >> s >> u, operations::register_structure(s, u);
        else if(op == 2)
            cin >> typ >> nam, operations::register_variable(typ, nam);
        else if(op == 3)
            operations::assign_variable();
        else if(op == 4)
            cin >> u, operations::assign_memory(&typed::root, u, string());
    }
    return 0;
}

对于那些要求极致码量的人,我这里有一份压行版,共 1.78K,但是你考场上肯定打的不是这个

提高组T4 | 二分答案 贪心

先留个坑。



在 Rickyxrc's blog 出现的文章,若无特殊注明,均采用 CC BY-NC-SA 4.0 协议共享,也就是转载时需要注明本文章的地址,并且引用本文章的文章也要使用相同的方式共享。