2023 百度之星初赛第一场
写在前面:
非正式题解
公园
我们可以想一下小度和度度熊一定会相遇一起走吗?
会的,最不济的情况到最后小度和度度熊会在终点相遇
为了总消耗最少,他们从一个点到另一个点一定是走最短路
那他们也可能会为了相遇从而不是一口气从原点走到终点的
于是我们可以枚举他们相遇的点,相遇后再一起走到终点
于是就有个思路:
我们求t,f,n分别到各个点的最短路,O(n)
然后枚举相遇的点x,求最短的 t->x + f->x + x->n 的消耗
蛋糕划分
基本思路就是二分,然后要多画图考虑一下细节
首先先二分出来最重中的最轻为u
然后check(u),看下是否有划分的方案能够达到u
最朴素的方法就是2^2N的枚举全部方案
但是会超时,考虑优化
我们枚举横着切的全部方案
然后贪心地切竖着切的方案,即我们枚举每一个竖着切的地方j
如果在j处竖着切没有不合法,那么这一刀可有可无,因为贪心,所以我们不切着一刀
直到不合法了,前面竖着切的那一刀一定要切,我们就记录一下
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 20;
int n, m, g[N][N], sum[N][N];
int getS(int y1, int x1, int y2, int x2)
{
return sum[y2][x2] - sum[y2][x1 - 1] - sum[y1 - 1][x2] + sum[y1 - 1][x1 - 1];
}
bool check(int u)
{
// 枚举横着切,切几刀,在哪里切
for (int i = 0; i < (1 << (n - 1)); i++)
{
// 比如i的二进制下的第1,3,5位为1,则说明形成了1行 2~3行 4~5行 6~n行这个几个行区间
vector<int> row;
int cnt = 0;
while (cnt < n)
{
if ((i >> cnt) & 1)
row.push_back(cnt + 1);
cnt++;
}
if (row.size() > m)
continue;
row.push_back(n);
// 然后开始贪心列的切法:
// 假设我们将蛋糕切成了k个行区间,那么我们在列切的时候就会形成块
// 我们的思想是维护上一次是在哪一列是一定要切的(假设为j1)
// 枚举后面要在第几列切(假设为j)
// 因为现在我们有k个行区间,那么会形成 k个块,这k个块的列坐标范围为j1~j
// 如果切的块没有一个>u,说明这一列是否要切可有可无
// 为了让通过check的机会更大,肯定是切的次数越少越好
// 那么这一次我们就不切了
// 直到遇到枚举到在j2列要切一刀,切出来的某块>u了,说明前面枚举的j-1是有必要要切的
// 于是我们更新j1,然后再计算
// 如何维护列坐标为j1~j的块的大小?开个数组rws[N]
// [down,top]是行的区间,cowN记录一定要切的列数,lts记录上次我们某行的小块大小
int rws[N], cowN = 0, lts[N];
memset(rws, 0, sizeof(rws));
bool flag = true;
for (int j = 1; j <= n; j++)
{
int top, down = 1;
cnt = 1;
memset(lts, 0, sizeof(lts));
for (int v : row)
{
top = v;
// 现在我们求出其中一块出来
int ts = getS(down, j, top, j);
lts[cnt] = ts;
if (ts > u)
{
// 这个说明横切的方法不对要从新来
flag = false;
break;
}
// 以[j1,j]为列区间的第cnt个行区间的块的大小为rws[cnt]
rws[cnt] += ts;
if (rws[cnt] > u)
{
// 说明前面的j-1列是一定要切的
cowN++;
// 这个时候我们需要重新更新下我们的rws[]
for (int k = 1; k <= row.size(); k++)
rws[k] = lts[k];
}
cnt++;
down = top + 1;
}
if (!flag)
break;
}
if (!flag)
continue;
if (row.size() - 1 + cowN <= m)
return true;
}
return false;
}
int main()
{
int l = 0, r = 1000 * N * N, ans = -1;
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
cin >> g[i][j];
l = max(g[i][j], l);
sum[i][j] = g[i][j] + sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1];
}
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
cout << ans << endl;
return 0;
}
第五维度
最晚情况的下的最早时间,二分
写题经验告诉我要二分时间
假设现在我得到了二分出来的时间t是最早时间
那么这个时候智者为了不想让我们成功必然会消灭掉其中一个人类
如果智者讲贡献最大的人类都消灭掉了但是人类还是理解了,那么我们二分出来的答案就是正确的
如果智者消灭掉其中一个人类后,人类没有理解,说明我们二分出来的答案错误了
我们需要增加二分出来的时间
流水线搭积木
当时写的适合超时了,还死磕在那里
不得不说,打习惯了蓝桥杯,PTA的比赛
现在写模拟题不注意细节方面的时间复杂度倒是成了习惯
写的时候不看榜单,一看发现这道题每几个人过了
害,下次打ACM赛制还是注意一下吧
其实卡复杂度方法就在他需要找到符合能够放积木的积木集
这个如果暴力找会超时,要用平衡树来模拟
本文作者:次林梦叶
本文链接:https://www.cnblogs.com/cilinmengye/p/17612804.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步