2020编码大赛题目  javascript:void(0)

半决赛,是32个队伍选出8个队伍。

 

一,需求变更

  • 每个资源区内有多个资源,每个虚拟资源大小不固定,以实际情况为准, 以1024KB为单位大小,如果文件大小为2048KB,则代表两个单位资源,可以兑换两倍的积分与金币。
  • 每个回合每个资源机器人只能进行一次路径调度,可以对该路径上的虚拟资源进行装载,能装载不同类型资源。
  • 资源唯一索引,同时也为该资源位于文件系统的绝对路径。

我的解读:

1,文件大小是1024的倍数
2,可以混合装载
3,调度阶段可以访问文件

 

二,压缩算法

在写了一个很简单的LZW算法之后,经过多日的攻坚,终于把PPMD写出来了。

javascript:void(0)

压缩率:

2:7,也就是说一般情况下可以装载7份

这里列出了1M、2M、3M的文件压缩后的大小:

2020编码大赛(5)半决赛_i++

性能:

压缩1M需要0.4秒,压缩7M需要2S

 

三,变更应对

1,文件大小是1024的倍数

这部分简单,只需要修改计算容量的方式即可

// 矿堆所有矿的大小之和
static int getUnitsSize(vector<ResourceUnit> v)
{
    int ans = 0;
    for (unsigned i = 0; i < v.size(); i++) {
        ans += v[i].sizeOfUnit / 1024;
    }
    return ans;
}
// 矿堆所有矿的大小之和
static int getResourceNum(Pos p)
{
    if (samePos(p, g_posFlag)) {
        return 0;
    }
    return getUnitsSize(g_resourceUnits[p.x][p.y]);
}

2,可以混合装载

可以混合装载的话,就可以衍生出很多策略。

我加入了一个很重要的机制:把选矿和选矿堆2个逻辑进行分离,先选定矿堆,然后把所有矿堆的所有矿放在一起进行挑选

// 这条路径要装载多少M资源是>=col颜色的
static int getPathPower(vector<Pos> p, int col)
{
    int ans = 0;
    for (unsigned int i = 1; i < p.size(); i++) {
        if (avail(p[i], g_mapEntities) && g_mapEntities[p[i].x][p[i].y] >= col) {
            ans += getResourceNum(p[i]);
        }
    }
    if (ans > remainSize()) {
        return remainSize();
    }
    return ans;
}

// 第一次调用,path是worker到基地这2个点
static bool getTarget(vector<Pos> &path, int col)
{
    map<pair<int, int>, int> m;
    Pos tmp;
    for (int i = 1; i <= g_height; i++) {
        for (int j = 1; j <= g_width; j++) {
            tmp.x = i, tmp.y = j;
            if (g_mapEntities[i][j] == col) {
                int num = 0;
                m[make_pair(i, j)] = targetAvail(*(path.end() - 2), tmp, *(path.end() - 1), num) * 8;
                m[make_pair(i, j)] += num;
            }
        }
    }
    tmp = getMinPos(m);
    vector<Pos> temp;
    if (!samePos(tmp, Pos { 0, 0 })) {
        getPath(*(path.end() - 2), tmp, g_mapEntities, temp);
        killMap(temp, g_mapEntities);
        g_myTeam.worker.power -= temp.size() + getPathPower(temp, col);
        path.insert(path.end() - 1, tmp);
        return true;
    }
    return false;
}

static void play(Pos start, Pos end, vector<Pos> &path)
{
    vector<Pos> tar(2);
    tar[0] = start;
    tar[1] = end;
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < M; j++) {
            g_mapEntities2[i][j] = g_mapEntities[i][j];
        }
    }
    g_power2 = g_myTeam.worker.power;
    while (getPathSize(tar) < remainSize() && g_myTeam.worker.power > 0) {
        if (!getTarget(tar, RED)) {
            break;
        }
    }
    while (getPathSize(tar) < remainSize() && g_myTeam.worker.power > 0) {
        if (!getTarget(tar, GREEN)) {
            break;
        }
    }
    while (getPathSize(tar) < remainSize() && g_myTeam.worker.power > 0) {
        if (!getTarget(tar, BLUE)) {
            break;
        }
    }
    g_myTeam.worker.power = g_power2;
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < M; j++) {
            g_mapEntities[i][j] = g_mapEntities2[i][j];
        }
    }
    if (samePos(*(tar.end() - 1), g_posFlag)) {
        tar.erase(tar.end() - 1);
    }
    int ret = getPath(tar, path);
    if (ret == -1) {
        cout << endl << "play1()    ret = -1" << endl;
        return;
    }
}

从矿堆的所有矿中,挑选矿的方法就是,先排序,再按顺序装载

static bool cmp(ResourceUnit a, ResourceUnit b)
{
    if (a.gemType != b.gemType) {
        return a.gemType > b.gemType;
    }
    return a.sizeOfUnit > b.sizeOfUnit;
}
// 从矿堆里面选矿
static vector<ResourceUnit> take(vector<Pos> p, int num, int &power)
{
    vector<ResourceUnit> ans, tmp;
    for (unsigned i = 0; i < p.size(); i++) {
        Pos po = p[i];
        for (unsigned i = 0; i < g_resourceUnits[po.x][po.y].size(); i++) {
            tmp.push_back(g_resourceUnits[po.x][po.y][i]);
        }
    }
    sort(tmp.begin(), tmp.end(), cmp);
    int rm = g_myTeam.worker.maxLoad - g_myTeam.worker.load, s = 0;
    num = power = 0;
    for (unsigned i = 0; i < tmp.size(); i++) {
        cout << endl << "begin " << tmp[i].sizeOfUnit << " " << clock() << " ";
        int ret = compress(tmp[i].index);
        cout << " end " << clock() << endl;
        if (ret + s > rm) {
            continue;
        }
        ans.push_back(tmp[i]);
        s += ret, num++, power += tmp[i].sizeOfUnit / 1024 * tmp[i].gemType;
    }
    return ans;
}
static vector<ResourceUnit> take(vector<Pos> p)
{
    int num, power;
    return take(p, num, power);
}

static void play()
{
    Pos workerPos = g_myTeam.worker.pos;
    if (!startAtCamp() && canGotoCamp()) {
        play(workerPos, g_myTeam.campPos, g_path);
        int num, pow;
        vector<ResourceUnit> vans = take(g_path, num, pow);
        setCollectCommands(vans);
        g_myTeam.worker.power -= g_path.size() - 1 + vans.size() - pow;
        killMap(g_path, g_mapEntities);
        workerPos = *(g_path.end() - 1), g_myTeam.worker.load = 0;
        g_myTeam.worker.loadInfo.clear();
        vector<Pos> path;
        play(workerPos, g_posFlag, path);
        setCollectCommands(take(path));
        if (path.size()) {
            path.erase(path.begin());
        }
        g_path = join(g_path, path);
        return;
    }
    play(workerPos, g_posFlag, g_path);
    setCollectCommands(take(g_path));
}

cmp函数里面蕴含了一个策略:先装大的再装小的,因为我的压缩算法压缩大文件压缩率更高

PS:我没有仔细研究这一点,或许是和待压缩文件有关,和算法无关。真相不重要,结果都一样。

 

3,调度阶段可以访问文件

我的压缩算法没有别人的压缩率高,但是压缩很快,所以这个优势要利用起来。

经过简单的计算,发现10秒完全可以胜任16M的压缩任务,所以这里有个重要的策略:

在选出矿堆并把所有矿排序之后,按顺序一边装载一边调用压缩接口,实时计算剩余空间,这样才能尽量装满。

为了加速,我加了标记,在调度的时候调用压缩接口的话,就不需要输出,从而加速程序运行。

static void normalize(Cpd *p)
{
    if (g_flag) {
        while (CON1) {
            p->range <<= 8, p->low <<= 8, g_com++;
        }
    }
    while (CON1) {
        cout << (Byte)(p->low >> 24);
        p->range <<= 8, p->low <<= 8;
    }
}

int compress(string s)
{
    freopen(s.c_str(), "r", stdin);
    g_com = 0, g_flag = true;
    if (clock() - g_start > 6 * CLOCKS_PER_SEC) { // 这个分支应该走不到
        return COMSIZE;
    }
    compress();
    return g_com / 1024 + 1;
}

上面的take函数里面调用了这个compress重载函数。

为了保证不超时,我还加入了定时机制,只在压缩入口处加了定时。

在最后的决赛中,我发现这里写的有个问题,当时间超过阈值时,也不应该直接返回常数COMSIZE,而应该根据历史压缩记录去评估。(这个函数本来就已经有了)

 

四,未上线策略

有个策略我想到了,而且写的差不多了,但最终还是没上线,主要是来不及调试。

在选矿堆的时候,我是先选红矿,再选绿矿,再选蓝矿,矿堆被选取的顺序和被走到的顺序是一样的,即走过的路径肯定是红红红绿绿蓝蓝蓝这种的。

然而实际上,如果在从一个红矿走到另外一个红矿的过程中经过了绿矿,当我决定开始选绿矿的时候,应该先把这些经过的绿矿加入到待选列表中

// 添加经过的矿堆
static vector<Pos> extend(vector<Pos> v, int col)
{
    if (v.size() < 2) {
        return v;
    }
    Pos tmp = *(v.end() - 1);
    v.erase(v.end() - 1);
    vector<Pos> ans, path;
    getPath(v, path);
    ans.push_back(path[0]);
    for (int i = 0; i < path.size() - 1; i++) {
        if (g_mapEntities[path[i].x][path[i].y] >= col) {
            ans.push_back(path[i]);
        }
    }
    if (path.size() - 1 > 0) {
        ans.push_back(path[path.size() - 1]);
    }
    ans.push_back(tmp);
    return ans;
}