2020编码大赛题目 javascript:void(0)
半决赛,是32个队伍选出8个队伍。
一,需求变更
- 每个资源区内有多个资源,每个虚拟资源大小不固定,以实际情况为准, 以1024KB为单位大小,如果文件大小为2048KB,则代表两个单位资源,可以兑换两倍的积分与金币。
- 每个回合每个资源机器人只能进行一次路径调度,可以对该路径上的虚拟资源进行装载,能装载不同类型资源。
- 资源唯一索引,同时也为该资源位于文件系统的绝对路径。
我的解读:
1,文件大小是1024的倍数
2,可以混合装载
3,调度阶段可以访问文件
二,压缩算法
在写了一个很简单的LZW算法之后,经过多日的攻坚,终于把PPMD写出来了。
压缩率:
2:7,也就是说一般情况下可以装载7份
这里列出了1M、2M、3M的文件压缩后的大小:
性能:
压缩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;
}