GYM100212B - I Just Called...

发布时间 2023-06-05 23:38:18作者: jucason_xu

大模拟。

首先的难度在于理解题意:

打电话的地点分为镇、地区、超级地区三级。其中,一些地区是被网络连接的。

电话号码的前缀由 地区号+镇号 组成。它们可以是不等长的,但是整个电话号码的长度是 \(d\)。一个镇可能有多个镇号,不同地区的镇可以拥有相同的镇号,但地区号是唯一的。

同时,电话分为四种来源:

  1. 从区域 \(h\),出发
  2. \(h\) 在同一个超级区域 且被网络覆盖的 区域出发。
  3. 从被网络覆盖的区域出发
  4. 从其他区域出发。

电话的距离分为四种:

  1. 从一个镇到自身
  2. 到同区域的镇
  3. 到被网络连接的区域的镇
  4. 到其他区域的镇。

按照来源和距离,我们把所有的电话分成 \(16\) 种,依次给出它们的开销。

然后,给出若干条电话,每一条给出打电话的来源镇和拨打的号码。如果号码无效则电话无效。然后给出打电话的时间,求最终的总开销。

我们发现,可以使用字典树存储所有的号码,看上去字典树的空间会炸,但是因为还有输入量不超过 512KB 的限制,所以字典树的大小也不太会超过 \(512000\),至多多一点常数(因为 1KB=1024B)但是字典树也会省一些空间,还有其他输入部分,所以开 \(500000\) 足够了。

然后我们直接在字典树上插入,插入镇号的时候直接从区域号在字典序上的位置往后插入。最后对于当前号码查询,查询到镇就退出。

然后我们来看 I_love_Hoang_Yen 的程序。

struct Node {
    Node* child[10];
    int townId;
 
    Node() {
        memset(child, 0, sizeof child);
        townId = 0;
    }
} *root;

首先是指针形式的字典树,很明显这道题的时间瓶颈基本在字典树的插入和查询,所以字典树节点的优化非常重要,指针就比普通的数组快了一些。

 
struct Region {
    string code;
    int sup;
 
    void read() {
        cin >> sup >> code;
    }
} regions[222];
bool covered[222];
 
struct Town {
    vector<string> codes;
    int reg;
 
    void read(int id) {
        int k;
        cin >> reg >> k;
        codes.resize(k);
 
        Node*p = root;
        for(char c : regions[reg].code) {
            int t = c - '0';
            if (!p->child[t]) {
                p->child[t] = new Node();
            }
            p = p->child[t];
        }
        if (!k) p->townId = id;
        auto savep = p;
        REP(i,k) {
            cin >> codes[i];
            p = savep;
            for(char c : codes[i]) {
                int t = c - '0';
                if (!p->child[t]) p->child[t] = new Node();
                p = p->child[t];
            }
 
            p->townId = id;
        }
    }
} towns[10111];

分别封装 regionstowns,条例清晰,层次性好,有效的防止了各层次互相混乱导致最后打表挂掉的情况。
封装输入也同样简便了程序的书写,不用开很多的变量表达不同的意思,也不需要在主函数里面分清各个地区的作用。


int getTown(const string& phoneNum) {
    Node*p = root;
    for(char c : phoneNum) {
        p = p->child[c - '0'];
        if (!p) return -1;
        if (p->townId) return p->townId;
    }
    return -1;
}

对于 town 的查询,就体现出各个变量名意义明显的好处。

int main() {
    ios :: sync_with_stdio(false);
    freopen("called.in", "r", stdin);
    freopen("called.out", "w", stdout);
 
    root = new Node();
 
    cin >> nTown >> nRegion >> nSuper >> d;
    FOR(i,1,nRegion) regions[i].read();
    FOR(i,1,nTown) towns[i].read(i);
 
    int k;
    cin >> homeRegion >> k;
    REP(i,k) {
        int reg; cin >> reg;
        covered[reg] = true;
    }
    assert(covered[homeRegion]);
    REP(i,4) REP(j,4) cin >> cost[i][j];
 
    ll sum = 0;
    int q; cin >> q;
    while (q--) {
        int fromTown; string phoneNum; int len;
        cin >> fromTown >> phoneNum >> len;
//        DEBUG(phoneNum);
 
        int a, b;
        if (towns[fromTown].reg == homeRegion) a = 0;
        else if (covered[towns[fromTown].reg]
                && regions[towns[fromTown].reg].sup == regions[homeRegion].sup) a = 1;
        else if (covered[towns[fromTown].reg]) a = 2;
        else a = 3;
 
        int toTown = getTown(phoneNum);
        if (toTown < 0) continue;
//        DEBUG(toTown);
 
        if (toTown == fromTown) b = 0;
        else if (towns[toTown].reg == towns[fromTown].reg) b = 1;
        else if (covered[ towns[toTown].reg ]) b = 2;
        else b = 3;
 
        sum += cost[a][b] * (ll) len;
//        DEBUG(sum);
    }
    cout << sum << endl;
}

可以看到,在读入的大头被封装处理之后,主函数基本只剩下输出部分,简化了很多不必要的部分。

而在最后的部分,有意义的变量名给我们的打表提供了很大的便利,我们的程序基本是照着题面写的,有效避免了因为自己忘记变量含义而出锅的情况。