京东 & 美团
昨天有牵涉社会面挺大的事儿。
京东外卖宣布给旗下外卖骑手缴纳五险一金,而在京东宣布几个小时之后,美团也跟进了,宣布也给旗下全职 + 稳定兼职外卖骑手缴纳五险一金。
这事儿就很有意思了,可以一谈。
首先要有个基础认知,京东和美团虽然都算传统意义上的互联网大厂,但京东外卖和美团外卖,并不是一个量级的业务。
京东外卖是一个还没满月(正式上线时间 2025-02)的新业务,目前满打满算,旗下外卖骑手人数不会超过 3 万。而美团外卖是一个从曾经红海(百度、饿了么、美团)杀出来的成熟业务,这么多年下来,截止 2024 年,美团外卖旗下外卖骑手人数超过 740 万(预计 2025 年破 800 万)。
资格老,还不是重点,明牌模式下的「绝对效率」,才是美团外卖的恐怖之处。
百度外卖现在没有了,美团外卖的另外一个老对手饿了么,模式和美团高度类似,目前处于亏损状态,2024 年亏损 98 亿,这还是一个相比往年亏损大幅收窄后得到的数字。2025 年如果饿了么能够保证非餐单的增长速率的话,有望将亏损值控制在 50 亿。
那美团呢?
2024 年利润 230+ 亿,2025 年预计破 300 亿。
看出差别了吗?在同样的规则和环境下,没人能赢美团。这也是为什么从抖音做本地生活的时候,我就说,抖音退出围剿是单纯的时间问题。
字节,阿里都没做到的事情,京东能做到吗?
我持和当时看待抖音本地生活进场时,相同的态度。
那再回归到「京东外卖,持"小"逼宫,给外卖骑手缴纳五险一金」这一事儿。
个人的看法没法充当社会的大多数,但看金融市场的反应,可以窥探到一些大方向。
目前美团跌幅在 5%~6% 之间,由此可见,在绝大多数人看来,这事儿对美团来说,是偏利空的。
毕竟要给这么多外卖骑手缴纳五险一金,是一笔不少的费用,对财报肯定有影响。
确实,但也只限于"有影响"罢了,对美团来说,根本称不上"伤根动骨"。
这笔账算算就知道了。
美团 740 万外卖骑手里面,满足缴纳条件(全职 or 稳定兼职)的不超过 10%,我们就按 10% 算好,也就是 74 万外卖骑手。五险一金不同地区标准不同,用下限和上限套算的话,美团每年的花费大概在 60亿~100亿,取平均值的话 80 亿。
看着挺多的,毕竟都占掉 2024 年利润(230+ 亿)的三成有多了。
但如果你知道美团的这 200 多亿,是怎么构成的话,就知道这成本转移,有多简单了。
美团外卖在 2024 年,全年订单量 200 亿单,平均利润 1.17 元/单。
每个外卖上涨 1 毛,会能回血 20 亿,再削掉一些平台补贴,叠加一些税收方面的优惠政策(企业支出增加,企业所得税就能少缴),这 80 亿的支出,收缩到 40 亿,轻轻松松。
再加上美团外卖,本身 2025 年就会有的利润增长,无非就是「从 230 亿增长到 300 亿」变成「从 230 亿增长到 260亿」罢了,看到了吗,连"倒退"都说不上。
那在这故事里,受伤害最大的是谁?
恰恰是我们前面说到的,阿里饿了么。
如果说,京东外卖宣布缴纳五险一金,是初生牛犊不怕虎的一步棋的话。美团的选择跟进,则是直接给外卖行业的待遇标准,增加了新规。
饿了么不可能当没看到。饿了么眼看着要在 2025 要从亏损上 100 亿变为亏损 50 亿,这下直接打回原形了。
把这事儿掰开了聊之后,你会发现将来京东外卖退潮之后,这"五险一金"其实会成为美团外卖比护城河中的一环,不伤根骨,又大大增加后来者的入局成本。
想到这里,我在美团的 -6% 的走势界面上,点了三下鼠标。
对此,你怎么看?你有别的看法吗?欢迎评论区交流。
...
回归主题。
来一道「图论」相关的算法题。
题目描述
平台:LeetCode
题号:1036
在一个 的网格中,每个网格上方格的坐标为 。
现在从源方格 开始出发,意图赶往目标方格 。
数组 是封锁的方格列表,其中每个 表示坐标为 的方格是禁止通行的。
每次移动,都可以走到网格中在四个方向上相邻的方格,只要该方格 不 在给出的封锁列表 上。同时,不允许走出网格。
只有在可以通过一系列的移动从源方格 到达目标方格 时才返回 。否则,返回 。
示例 1:
输入:blocked = [[0,1],[1,0]], source = [0,0], target = [0,2]
输出:false
解释:
从源方格无法到达目标方格,因为我们无法在网格中移动。
无法向北或者向东移动是因为方格禁止通行。
无法向南或者向西移动是因为不能走出网格。
示例 2:
输入:blocked = [], source = [0,0], target = [999999,999999]
输出:true
解释:
因为没有方格被封锁,所以一定可以到达目标方格。
提示:
- 题目数据保证 和 不在封锁列表内
BFS + 给定障碍物所能围成的最大面积
为了方便,我们用 代指 ,用 代指 ,用 来代指 大小。
整理题意为:在一个足够大的空间里,有少数的障碍物,问两点是否连通。
当两点相隔较远时,常规的 BFS
做法可能会搜完整个棋盘,而棋盘大小为 ,会 TLE
。
考虑什么情况下两点会不连通?
当两个点中的任意一点被障碍物围住时,两点将无法连通。
一个很容易想到的思路是:从 跑一遍 BFS
,然后从 跑一遍 BFS
,同时设定一个最大访问点数量 MAX
,若从两者出发能够访问的点数量都能超过 MAX
,说明两点均没有被围住,最终必然会联通。
考虑如何敲定 MAX
的取值范围?直观感受,MAX
应该是一个与 大小相关的数。
但第一反应还是想从单秒计算量上界进行反推,两边 BFS
的复杂度均为 ,因此直接设定 MAX = 1e5
应该是比较合适的。
更小的 MAX
需要证明:在给定数量障碍物的前提下,障碍物所能围成的最大面积为多少。
首先,容易想到:任何一条封闭图形的直边都可以通过调整为斜边来围成更大的面积:
即组成封闭图形的边不可能有直边,同时由于是封闭图形,因此斜边直接必然是单点衔接,而不可能是平行(无法封闭)。
同时,想要达到最大面积,应当尽可能利用边界作为围成图形的某些边。
利用边界所能围成的最大封面图形 可以是「由边界提供两边,障碍物提供一边的三角形」。
如果不是该形状,则可以通过调整障碍物的直边为一条完整的斜边,来组成封闭三角形,围成面积不会变小:
即给定 的情况下,根据「等差数列求和」可知,最大所能围成的面积为 。
因此如果从 和 出发,能够访问的点数超过 个,那么两点并没有被围住,必然联通。
最后,为了在 BFS
过程中记录某些点被访问过,可以通过计算某个位置哈希值(数值)来实现。
Java 代码:
class Solution {
int EDGE = (int)1e6, MAX = (int)1e5;
long BASE = 131L;
Set<Long> set = new HashSet<>();
int[][] dir = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
public boolean isEscapePossible(int[][] blocked, int[] s, int[] t) {
for (int[] p : blocked) set.add(p[0] * BASE + p[1]);
int n = blocked.length;
MAX = n * (n - 1) / 2; // 可直接使用 1e5
return check(s, t) && check(t, s);
}
boolean check(int[] a, int[] b) {
Set<Long> vis = new HashSet<>();
Deque<int[]> d = new ArrayDeque<>();
d.addLast(a);
vis.add(a[0] * BASE + a[1]);
while (!d.isEmpty() && vis.size() <= MAX) {
int[] poll = d.pollFirst();
int x = poll[0], y = poll[1];
if (x == b[0] && y == b[1]) return true;
for (int[] di : dir) {
int nx = x + di[0], ny = y + di[1];
if (nx < 0 || nx >= EDGE || ny < 0 || ny >= EDGE) continue;
long hash = nx * BASE + ny;
if (set.contains(hash)) continue;
if (vis.contains(hash)) continue;
d.addLast(new int[]{nx, ny});
vis.add(hash);
}
}
return vis.size() > MAX;
}
}
C++ 代码:
class Solution {
public:
int EDGE = 1e6, MAX = 1e5;
long long BASE = 13331;
unordered_set<long long> set;
int dir[4][2] = { {1, 0}, {-1, 0}, {0, 1}, {0, -1} };
bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& s, vector<int>& t) {
for(auto& p : blocked) set.insert(p[0] * BASE + p[1]);
int n = blocked.size();
MAX = n * (n - 1) / 2; // 可直接使用 1e5
return check(s, t) and check(t, s);
}
bool check(vector<int>& a, vector<int>& b){
unordered_set<long long> vis;
queue< pair<int,int> > q;
q.push( {a[0], a[1]});
vis.insert(a[0] * BASE + a[1]);
while(q.size() and vis.size() <= MAX){
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
if(x == b[0] and y == b[1]) return true;
for(int i = 0; i < 4; i++){
int nx = x + dir[i][0], ny = y + dir[i][1];
if(nx < 0 or nx >= EDGE or ny < 0 or ny >= EDGE) continue;
if(set.count(nx * BASE + ny)) continue;
if(vis.count(nx * BASE + ny)) continue;
q.push( {nx, ny} );
vis.insert(nx * BASE + ny);
}
}
return vis.size() > MAX;
}
};
Python 代码:
EDGE, MAX, BASE, DIR = int(1e6), int(1e5), 131, [(1, 0), (-1, 0), (0, 1), (0, -1)]
class Solution:
def isEscapePossible(self, blocked: List[List[int]], source: List[int], target: List[int]) -> bool:
block = {p[0] * BASE + p[1] for p in blocked}
n = len(blocked)
MAX = n * (n-1)//2 # 可直接使用 1e5
def check(a, b):
vis = {a[0] * BASE + a[1]}
d = deque([a])
while len(d) and len(vis) <= MAX:
x, y = d.popleft()
if x == b[0] and y == b[1]:
return True
for dx, dy in DIR:
nx, ny = x + dx, y + dy
if nx < 0 or nx >= EDGE or ny < 0 or ny >= EDGE:
continue
h = nx * BASE + ny
if h in block or h in vis:
continue
d.append((nx, ny))
vis.add(h)
return len(vis) > MAX
return check(source, target) and check(target, source)
- 时间复杂度:令 为 大小,两次
BFS
的最大访问点数为 。整体复杂度为 - 空间复杂度:两次
BFS
的最大访问点数为 。整体复杂度为