CCF-CSP第37次认证第三题——模板展开(满分题解)

题解

实现方式:

  1. 将字符串映射为数字值,并将字符串间的嵌套关系视为图结构。对存在间接赋值关系的语句执行加边操作,最终查询操作通过一次DFS完成。

  2. 图结构采用链式前向星存储方式,与常规前向星的区别在于不记录边权值,而是记录点权值。每个节点的长度值等于其自身权值加上所有子节点权值的总和。

  3. 使用getline配合stringstream读取字符串数据,注意在调用getline前需执行getchar或ignore操作以清除缓冲区。

具体流程:

  1. 输入
  2. 直接赋值:
    1. 删除所有连接边(h[x]=-1),点权归零。
    2. 如果为‘$’开头,dfs计算其权重,并将权重加入点权中。
    3. 不为‘$’开头,直接将字符串长度加入点权中。
  3. 间接赋值:
    1. 删除所有接边(h[x]=-1),点权归零。
    2. 如果为‘$’开头,加边。
    3. 不为‘$’开头,点权加上该字符串长度。
  4. 查询:dfs查询即可。

优化:

记忆化搜索,注意是在每一轮内部记忆化,即在每一轮开始的时候将vis数组归零,如果全局记忆化会有神奇的影响导致答案不对。

代码

有注释版本(ai注释):

#include <bits/stdc++.h>
using namespace std;

/* ---------------- 全局常量 ---------------- */
const long long mod = 1e9 + 7;      // 取模值
const int N = 4'001'000;             // 预估最大节点数
const int M = 4'001'000;             // 预估最大边数

/* ---------------- 全局变量 ---------------- */
int n, op, cnt = 0;                 // n: 操作次数  op: 当前操作类型  cnt: 节点计数器
unordered_map<string, int> mp;      // 字符串 -> 节点编号的映射

/* 邻接表存图 */
int idx = 0;                        // 当前边的编号
int h[N], e[M], ne[M];              // h: 头指针  e: 终点  ne: next 边
long long w[N];                     // w[i] 表示节点 i 的“基础值”
long long vis[N];                   // 记忆化数组:vis[i] 表示以 i 为根的答案

/* ---------------- 工具函数 ---------------- */
inline void add(int x, int y) {     // 加一条 x -> y 的边
    e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}

// 把字符串映射成节点编号;不存在则新建
inline int dis(string s) {
    if (!mp.count(s)) return mp[s] = ++cnt;
    return mp[s];
}

// 记忆化搜索:求以 x 为根的“答案”
inline long long query(int x) {
    if (vis[x]) return vis[x];      // 已经算过则直接返回
    long long res = w[x];           // 先累加自己的基础值
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        res += query(j);            // 递归累加所有孩子的答案
        res %= mod;
    }
    vis[x] = res;                   // 记忆化
    return res;
}

/* ---------------- 主程序 ---------------- */
int main() {
    memset(h, -1, sizeof h);        // 初始化邻接表头指针
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    getchar();                      // 吃掉第一行的换行
    while (n--) {
        memset(vis, 0, sizeof vis); // 每组操作前清空记忆化数组
        string s;
        getline(cin, s);            // 读入整行
        stringstream ss(s);

        ss >> op;                   // 解析操作类型
        if (op == 1) {              // 操作 1:重新定义一个 key 的内容
            string key, temp;
            ss >> key;              // 变量名
            int t = dis(key);       // 获取节点编号
            long long res = 0;      // 累加器

            // 逐个解析后面的片段
            while (ss >> temp) {
                if (temp[0] != '$') {               // 普通字符串
                    res += temp.size();
                    res %= mod;
                } else {                            // 引用变量
                    int id = dis(temp.substr(1));   // 去掉 '$' 后拿到变量名
                    res += query(id);               // 递归求值
                    res %= mod;
                }
            }
            w[t] = res;             // 更新基础值
            h[t] = -1;              // 操作 1 不允许孩子,因此清空邻接表
        } else if (op == 2) {       // 操作 2:重新定义一个 key 的内容(允许引用)
            string key, temp;
            ss >> key;
            int p = dis(key);
            w[p] = 0;               // 重置基础值
            h[p] = -1;              // 先清空所有孩子

            while (ss >> temp) {
                if (temp[0] != '$') {               // 普通字符串
                    w[p] += temp.size();
                    w[p] %= mod;
                } else {                            // 引用变量
                    int id = dis(temp.substr(1));
                    add(p, id);                     // 加一条 p -> id 的边
                }
            }
        } else if (op == 3) {       // 操作 3:查询
            string key;
            ss >> key;
            cout << query(dis(key)) << '\n';
        }
    }
    return 0;
}

无注释版本:

#include<bits/stdc++.h>

using namespace std;

const long long mod=1e9+7,N=4001000,M=4001000;
int n,op,cnt;
unordered_map<string,int>mp;
int idx,h[N],e[M],ne[M];
long long w[N];
long long vis[N];

inline void add(int x,int y){
    e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}

inline int dis(string s){
    if(!mp.count(s))return mp[s]=++cnt;
    return mp[s];
}

inline long long query(int x){
    if(vis[x])return vis[x];
    long long res=w[x];
    for(int i=h[x];i!=-1;i=ne[i]){
        int j=e[i];
        res+=query(j);
        res%=mod;
    }
    vis[x]=res;
    return res;
}

int main(){
    memset(h,-1,sizeof h);
    cin>>n;
    getchar();
    while(n--){
        memset(vis,0,sizeof vis);
        string key,value,temp,s;
        getline(cin,s);
        stringstream ss(s);
        ss>>op;
        if(op==1){
            ss>>key;
            int t=dis(key);
            long long res=0;
            while(ss>>temp){
                if(temp[0]!='$'){
                    res+=temp.size();
                    res%=mod;
                }else{
                    int t=dis(temp.substr(1));
                    res+=query(t);
                    res%=mod;
                }
            }
            w[t]=res;
            h[t]=-1;
        }else if(op==2){
            ss>>key;
            int p=dis(key);
            w[p]=0;
            h[p]=-1;
            while(ss>>temp){
                if(temp[0]!='$'){
                    w[p]+=temp.size();
                    w[p]%=mod;
                }else{
                    int t=dis(temp.substr(1));
                    add(p,t);
                }
            }
        }else if(op==3){
            ss>>key;
            cout<<query(dis(key))<<endl;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值