作者推荐
本文涉及知识点
树上倍增 树 图论 并集查找 换根法 深度优先 割点
LeetCode3067. 在带权树网络中统计可连接服务器对数目
给你一棵无根带权树,树中总共有 n 个节点,分别表示 n 个服务器,服务器从 0 到 n - 1 编号。同时给你一个数组 edges ,其中 edges[i] = [ai, bi, weighti] 表示节点 ai 和 bi 之间有一条双向边,边的权值为 weighti 。再给你一个整数 signalSpeed 。
如果两个服务器 a ,b 和 c 满足以下条件,那么我们称服务器 a 和 b 是通过服务器 c 可连接的 :
a < b ,a != c 且 b != c 。
从 c 到 a 的距离是可以被 signalSpeed 整除的。
从 c 到 b 的距离是可以被 signalSpeed 整除的。
从 c 到 b 的路径与从 c 到 a 的路径没有任何公共边。
请你返回一个长度为 n 的整数数组 count ,其中 count[i] 表示通过服务器 i 可连接 的服务器对的 数目 。
示例 1:
输入:edges = [[0,1,1],[1,2,5],[2,3,13],[3,4,9],[4,5,2]], signalSpeed = 1
输出:[0,4,6,6,4,0]
解释:由于 signalSpeed 等于 1 ,count[c] 等于所有从 c 开始且没有公共边的路径对数目。
在输入图中,count[c] 等于服务器 c 左边服务器数目乘以右边服务器数目。
示例 2:
输入:edges = [[0,6,3],[6,5,3],[0,3,1],[3,2,7],[3,1,6],[3,4,2]], signalSpeed = 3
输出:[2,0,0,0,0,0,2]
解释:通过服务器 0 ,有 2 个可连接服务器对(4, 5) 和 (4, 6) 。
通过服务器 6 ,有 2 个可连接服务器对 (4, 5) 和 (0, 5) 。
所有服务器对都必须通过服务器 0 或 6 才可连接,所以其他服务器对应的可连接服务器对数目都为 0 。
提示:
2 <= n <= 1000
edges.length == n - 1
edges[i].length == 3
0 <= ai, bi < n
edges[i] = [ai, bi, weighti]
1 <= weighti <= 10^6^
1 <= signalSpeed <= 10^6^
输入保证 edges 构成一棵合法的树。
树上倍增
本题有三个考点:
一,如何计算树上两个节点x1,x2的距离。
假定这两个节点的最早公共祖先是pub。以任意节点root为根,f(x)表示节点x到root的距离。
x1到x2的距离:f(x1)+f(x2)-2*f(pub)。
二,如何找到最早公共祖先:树上倍增。
记录各节点的1级祖先(父节点)、2级祖先、4级祖先...
三,如果判断ac和bc有公共边。
树是连通无向无环图,因为无环,所以两个节点的路径唯一。
假设公共边是x3x4。则x3到c的路径唯一,假定x3到c的倒数第二个端点是x5,则ab和bc的最后一条边都是 x 3 c → \overrightarrow{x3c} x3c 。断开所以和c相连的边,如果a和b在同一个连通区域,则有公共边。用并查集看是否在同一个连通区域。
时间复杂度: O(nnlogn)。 枚举c,时间复杂度O(n);枚举ab,时间复杂度O(n)。查公共路径O(logn)。
并集查找
cpp
class CNeiBo
{
public:
static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<int>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
}
}
return vNeiBo;
}
static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<std::pair<int, int>>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
}
}
return vNeiBo;
}
};
class CUnionFind
{
public:
CUnionFind(int iSize) :m_vNodeToRegion(iSize)
{
for (int i = 0; i < iSize; i++)
{
m_vNodeToRegion[i] = i;
}
m_iConnetRegionCount = iSize;
}
CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
{
for (int i = 0; i < vNeiBo.size(); i++) {
for (const auto& n : vNeiBo[i]) {
Union(i, n);
}
}
}
int GetConnectRegionIndex(int iNode)
{
int& iConnectNO = m_vNodeToRegion[iNode];
if (iNode == iConnectNO)
{
return iNode;
}
return iConnectNO = GetConnectRegionIndex(iConnectNO);
}
void Union(int iNode1, int iNode2)
{
const int iConnectNO1 = GetConnectRegionIndex(iNode1);
const int iConnectNO2 = GetConnectRegionIndex(iNode2);
if (iConnectNO1 == iConnectNO2)
{
return;
}
m_iConnetRegionCount--;
if (iConnectNO1 > iConnectNO2)
{
UnionConnect(iConnectNO1, iConnectNO2);
}
else
{
UnionConnect(iConnectNO2, iConnectNO1);
}
}
bool IsConnect(int iNode1, int iNode2)
{
return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
}
int GetConnetRegionCount()const
{
return m_iConnetRegionCount;
}
vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
{
const int iNodeSize = m_vNodeToRegion.size();
vector<int> vRet(iNodeSize);
for (int i = 0; i < iNodeSize; i++)
{
vRet[GetConnectRegionIndex(i)]++;
}
return vRet;
}
std::unordered_map<int, vector<int>> GetNodeOfRegion()
{
std::unordered_map<int, vector<int>> ret;
const int iNodeSize = m_vNodeToRegion.size();
for (int i = 0; i < iNodeSize; i++)
{
ret[GetConnectRegionIndex(i)].emplace_back(i);
}
return ret;
}
private:
void UnionConnect(int iFrom, int iTo)
{
m_vNodeToRegion[iFrom] = iTo;
}
vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
int m_iConnetRegionCount;
};
class CParents
{
public:
CParents(vector<int>& vParent, const vector<int>& vLeve):m_vLeve(vLeve)
{
const int iMaxLeve = *std::max_element(vLeve.begin(), vLeve.end());
int iBitNum = 0;
for (; (1 << iBitNum) < iMaxLeve; iBitNum++);
const int n = vParent.size();
m_vParents.assign(iBitNum+1, vector<int>(n, -1));
m_vParents[0] = vParent;
//树上倍增
for (int i = 1; i < m_vParents.size(); i++)
{
for (int j = 0; j < n; j++)
{
const int iPre = m_vParents[i - 1][j];
if (-1 != iPre)
{
m_vParents[i][j] = m_vParents[i - 1][iPre];
}
}
}
}
int GetParent(int iNode, int iLeve)const
{
int iParent = iNode;
for (int iBit = 0; iBit < m_vParents.size(); iBit++)
{
if (-1 == iParent)
{
return iParent;
}
if (iLeve & (1 << iBit))
{
iParent = m_vParents[iBit][iParent];
}
}
return iParent;
}
int GetPublicParent(int iNode1, int iNode2)const
{
int leve0 = m_vLeve[iNode1];
int leve1 = m_vLeve[iNode2];
if (leve0 < leve1)
{
iNode2 = GetParent(iNode2, leve1 - leve0);
leve1 = leve0;
}
else
{
iNode1 = GetParent(iNode1, leve0 - leve1);
leve0 = leve1;
}
//二分查找
int left = -1, r = leve0;
while (r - left > 1)
{
const auto mid = left + (r - left) / 2;
const int iParent0 = GetParent(iNode1, mid);
const int iParent1 = GetParent(iNode2, mid);
if (iParent0 == iParent1)
{
r = mid;
}
else
{
left = mid;
}
}
return GetParent(iNode1, r);
}
protected:
vector<vector<int>> m_vParents;
const vector<int> m_vLeve;
};
class Solution {
public:
vector<int> countPairsOfConnectableServers(vector<vector<int>>& edges, int signalSpeed) {
m_c = edges.size() + 1;
m_vDisToRoot.resize(m_c);
m_vParent.resize(m_c);
m_vLeve.resize(m_c);
auto neiBo = CNeiBo::Three(m_c, edges, false, 0);
DFS(neiBo, 0, -1, 0,0);
CParents par(m_vParent,m_vLeve);
vector<int> vRet(m_c);
for (int c = 0; c < m_c; c++)
{
CUnionFind uf(m_c);
for (const auto& v : edges)
{
if ((v[0] == c) || (v[1] == c))
{
continue;
}
uf.Union(v[0], v[1]);
}
vector<int> vRegionCnt(m_c);
for (int ab = 0; ab < m_c; ab++)
{
if (ab == c )
{
continue;
}
const int pub = par.GetPublicParent(ab, c);
const int len = m_vDisToRoot[ab] + m_vDisToRoot[c] - 2 * m_vDisToRoot[pub];
if (0 != len % signalSpeed)
{
continue;
}
vRegionCnt[uf.GetConnectRegionIndex(ab)]++;
}
int&iRet = vRet[c];
const int total = std::accumulate(vRegionCnt.begin(), vRegionCnt.end(), 0);
for (int c1 = 0; c1 < m_c; c1++)
{
iRet += vRegionCnt[c1] * (total - vRegionCnt[c1]);
}
iRet /= 2;
}
return vRet;
}
void DFS(vector<vector<std::pair<int, int>>>& neiBo, int cur, int par,int leve,int dis)
{
m_vDisToRoot[cur] =dis;
m_vParent[cur] = par;
m_vLeve[cur] = leve;
for (const auto& [next,len] : neiBo[cur])
{
if (next == par)
{
continue;
}
DFS(neiBo, next, cur,leve+1,dis+len);
}
}
vector<int> m_vDisToRoot,m_vParent,m_vLeve;
int m_c;
};
测试用例
cpp
template<class T,class T2>
void Assert(const T& t1, const T2& t2)
{
assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
Assert(v1[i], v2[i]);
}
}
int main()
{
vector<vector<int>> edges;
int signalSpeed;
{
Solution sln;
edges = { }, signalSpeed = 1;
auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
Assert({ 0 }, res);
}
{
Solution sln;
edges = { {0,1,1} }, signalSpeed = 1;
auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
Assert({ 0,0 }, res);
}
{
Solution sln;
edges = { {0,1,1},{1,2,1} }, signalSpeed = 1;
auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
Assert({ 0,1,0 }, res);
}
{
Solution sln;
edges = { {0,1,1},{1,2,5},{2,3,13},{3,4,9},{4,5,2} }, signalSpeed = 1;
auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
Assert({ 0,4,6,6,4,0 } , res);
}
{
Solution sln;
edges = { {0,6,3},{6,5,3},{0,3,1},{3,2,7},{3,1,6},{3,4,2} }, signalSpeed = 3;
auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
Assert({ 2,0,0,0,0,0,2 }, res);
}
}
换根法DFS
树中删除一个节点,则各孩子各一个连通区域,除自己及后代外一个区域。如果这个节点是根,则简单得多。各孩子一个连通区域。
DSF(cur) 返回自己及子孙到当前根节点距离是signalSpeed 倍的节点数量。令当前根节点各孩子的返回值是{i1,i2, ⋯ \cdots ⋯,im} 。i1*i2+(i1+i2)*i3 ⋯ \cdots ⋯ +(I1+i2+ ... \dots ... + i m − 1 _{m-1} m−1)*im。这样不必除以二。
a<b ,表示a !=b ,(a,b)和(b,a)只取一个。
cpp
class CNeiBo
{
public:
static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<int>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
}
}
return vNeiBo;
}
static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<std::pair<int, int>>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
}
}
return vNeiBo;
}
};
class Solution {
public:
vector<int> countPairsOfConnectableServers(vector<vector<int>>& edges, int signalSpeed) {
m_c = edges.size() + 1;
m_iSignalSpeed = signalSpeed;
auto neiBo = CNeiBo::Three(m_c, edges, false, 0);
vector<int> vRet(m_c);
for (int c = 0; c < m_c; c++)
{
int& iRet = vRet[c];
int left = 0;
for (const auto& [next, len] : neiBo[c])
{
int cur = DFS(neiBo, next, c, len);
iRet += left * cur;
left += cur;
}
}
return vRet;
}
int DFS(vector<vector<std::pair<int, int>>>& neiBo, int cur, int par,int dis)
{
int iRet = (0 ==dis % m_iSignalSpeed);
for (const auto& [next,len] : neiBo[cur])
{
if (next == par)
{
continue;
}
iRet +=DFS(neiBo, next, cur,dis+len);
}
return iRet;
}
int m_iSignalSpeed;
int m_c;
};
割点
本解法过于复杂,除非用了提前封装好的割点扩展类,否则被使用。
cpp
class CNeiBo
{
public:
static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<int>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
}
}
return vNeiBo;
}
static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<std::pair<int, int>>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
}
}
return vNeiBo;
}
};
class CUnionFind
{
public:
CUnionFind(int iSize) :m_vNodeToRegion(iSize)
{
for (int i = 0; i < iSize; i++)
{
m_vNodeToRegion[i] = i;
}
m_iConnetRegionCount = iSize;
}
CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
{
for (int i = 0; i < vNeiBo.size(); i++) {
for (const auto& n : vNeiBo[i]) {
Union(i, n);
}
}
}
int GetConnectRegionIndex(int iNode)
{
int& iConnectNO = m_vNodeToRegion[iNode];
if (iNode == iConnectNO)
{
return iNode;
}
return iConnectNO = GetConnectRegionIndex(iConnectNO);
}
void Union(int iNode1, int iNode2)
{
const int iConnectNO1 = GetConnectRegionIndex(iNode1);
const int iConnectNO2 = GetConnectRegionIndex(iNode2);
if (iConnectNO1 == iConnectNO2)
{
return;
}
m_iConnetRegionCount--;
if (iConnectNO1 > iConnectNO2)
{
UnionConnect(iConnectNO1, iConnectNO2);
}
else
{
UnionConnect(iConnectNO2, iConnectNO1);
}
}
bool IsConnect(int iNode1, int iNode2)
{
return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
}
int GetConnetRegionCount()const
{
return m_iConnetRegionCount;
}
vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
{
const int iNodeSize = m_vNodeToRegion.size();
vector<int> vRet(iNodeSize);
for (int i = 0; i < iNodeSize; i++)
{
vRet[GetConnectRegionIndex(i)]++;
}
return vRet;
}
std::unordered_map<int, vector<int>> GetNodeOfRegion()
{
std::unordered_map<int, vector<int>> ret;
const int iNodeSize = m_vNodeToRegion.size();
for (int i = 0; i < iNodeSize; i++)
{
ret[GetConnectRegionIndex(i)].emplace_back(i);
}
return ret;
}
private:
void UnionConnect(int iFrom, int iTo)
{
m_vNodeToRegion[iFrom] = iTo;
}
vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
int m_iConnetRegionCount;
};
class CParents
{
public:
CParents(vector<int>& vParent, const int iMaxLeve)
{
int iBitNum = 0;
for (; (1 << iBitNum) < iMaxLeve; iBitNum++);
const int n = vParent.size();
m_vParents.assign(iBitNum+1, vector<int>(n, -1));
m_vParents[0] = vParent;
//树上倍增
for (int i = 1; i < m_vParents.size(); i++)
{
for (int j = 0; j < n; j++)
{
const int iPre = m_vParents[i - 1][j];
if (-1 != iPre)
{
m_vParents[i][j] = m_vParents[i - 1][iPre];
}
}
}
}
int GetParent(int iNode, int iLeve)const
{
int iParent = iNode;
for (int iBit = 0; iBit < m_vParents.size(); iBit++)
{
if (-1 == iParent)
{
return iParent;
}
if (iLeve & (1 << iBit))
{
iParent = m_vParents[iBit][iParent];
}
}
return iParent;
}
protected:
vector<vector<int>> m_vParents;
};
class C2Parents : CParents
{
public:
C2Parents(vector<int>& vParent, const vector<int>& vLeve) :m_vLeve(vLeve)
, CParents(vParent,*std::max_element(vLeve.begin(), vLeve.end()))
{
}
int GetPublicParent(int iNode1, int iNode2)const
{
int leve0 = m_vLeve[iNode1];
int leve1 = m_vLeve[iNode2];
if (leve0 < leve1)
{
iNode2 = GetParent(iNode2, leve1 - leve0);
leve1 = leve0;
}
else
{
iNode1 = GetParent(iNode1, leve0 - leve1);
leve0 = leve1;
}
//二分查找
int left = -1, r = leve0;
while (r - left > 1)
{
const auto mid = left + (r - left) / 2;
const int iParent0 = GetParent(iNode1, mid);
const int iParent1 = GetParent(iNode2, mid);
if (iParent0 == iParent1)
{
r = mid;
}
else
{
left = mid;
}
}
return GetParent(iNode1, r);
}
protected:
vector<vector<int>> m_vParents;
const vector<int> m_vLeve;
};
class CCutPointEx
{
public:
CCutPointEx(const vector<vector<int>>& vNeiB) : m_iSize(vNeiB.size())
{
m_vNodeToTime.assign(m_iSize, -1);
m_vChildFirstEnd.resize(m_iSize);
m_vNodeToRegion.assign(m_iSize, -1);
m_vCut.assign(m_iSize, false);
for (int i = 0; i < m_iSize; i++)
{
if (-1 != m_vNodeToTime[i])
{
continue;
}
dfs(i, -1, vNeiB);
m_iRegionCount++;
}
m_vTimeToNode.resize(m_iSize);
for (int i = 0; i < m_iSize; i++)
{
m_vTimeToNode[m_vNodeToTime[i]] = i;;
}
}
bool Visit(int src, int dest, int iCut)const
{
if (m_vNodeToRegion[src] != m_vNodeToRegion[dest])
{
return false;//不在一个连通区域
}
if (!m_vCut[iCut])
{
return true;
}
const int r1 = GetCutRegion(iCut, src);
const int r2 = GetCutRegion(iCut, dest);
return r1 == r2;
}
vector<vector<int>> GetSubRegionOfCut(const int iCut)const
{//删除iCut及和它相连的边后,iCut所在的区域会分成几个区域:父节点一个区域、各子节点一个区域
//父节点所在区域可能为空,如果iCut所在的连通区域只有一个节点,则返回一个没有节点的区域。
const auto& v = m_vChildFirstEnd[iCut];
vector<vector<int>> vRet(1);
int j = 0;
for (int iTime=0;iTime < m_iSize; iTime++ )
{
const int iNode = m_vTimeToNode[iTime];
if ((j < v.size()) && ( iTime >= v[j].first ))
{
j++;
vRet.emplace_back();
}
if ((iCut != iNode) && (m_vNodeToRegion[iNode] == m_vNodeToRegion[iCut]))
{
if (v.size()&&(iTime >= v.back().second))
{
vRet[0].emplace_back(iNode);
}
else
{
vRet.back().emplace_back(iNode);
}
}
}
return vRet;
}
protected:
int dfs(int cur, int parent, const vector<vector<int>>& vNeiB)
{
auto& curTime = m_vNodeToTime[cur];
m_vNodeToRegion[cur] = m_iRegionCount;
curTime = m_iTime++;
int iCutChild = 0;
int iMinTime = curTime;
for (const auto& next : vNeiB[cur])
{
if (-1 != m_vNodeToTime[next])
{
iMinTime = min(iMinTime, m_vNodeToTime[next]);
continue;
}
int iChildBeginTime = m_iTime;
const int iChildMinTime = dfs(next, cur, vNeiB);
iMinTime = min(iMinTime, iChildMinTime);
if (iChildMinTime >= curTime)
{
iCutChild++;
m_vChildFirstEnd[cur].push_back({ iChildBeginTime,m_iTime });
};
}
m_vCut[cur] = (iCutChild + (-1 != parent)) >= 2;
return iMinTime;
}
int GetCutRegion(int iCut, int iNode)const
{
const auto& v = m_vChildFirstEnd[iCut];
auto it = std::upper_bound(v.begin(), v.end(), m_vNodeToTime[iNode], [](int time, const std::pair<int, int>& pr) {return time < pr.first; });
if (v.begin() == it)
{
return v.size();
}
--it;
return (it->second > m_vNodeToTime[iNode]) ? (it - v.begin()) : v.size();
}
int m_iTime = 0;
const int m_iSize;//时间戳
int m_iRegionCount = 0;
vector<int> m_vNodeToTime;//各节点到达时间,从0开始。 -1表示未处理
vector<bool> m_vCut;//是否是割点
vector<int> m_vNodeToRegion;//各节点所在区域
vector<vector<pair<int, int>>> m_vChildFirstEnd;//左闭右开空间[0,m_vChildFirstEnd[0].first)和[m_vChildFirstEnd.back().second,iSize)是一个区域
vector<int> m_vTimeToNode;
};
DFS代替树上倍增
时间复杂度的瓶颈在 树上倍增。
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关
下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 **C+
+17**
如无特殊说明,本算法用**C++**实现。