1. 给一颗多叉树,求 从一个节点出发到其它所有节点的距离之和 的最小值。
树形 dp。一般两遍 dfs 就能解决。
第一遍 dfs 用 son[i] 记录每个节点多少个子孙,用 dis[i] 记录 i 点到其所有子孙的距离之和。 son[i]和 dis[i]都在回溯的过程进行维护。假设 v 是 u 的孩子节点,\(son[u]+=son[v]+1\), \(dis[u] += dis[v]+son[v]+1\),也就是说 v 的每个子孙到 u 的距离是他们到 v 的距离+1,然后再加上 v 到 u 的距离1。
第二遍 dfs,维护 dis[i] 为到所有点的距离之和。节点 v 到其它所有节点的距离之和可以用 u 到其它所有节点的距离之和计算出来。因为v和v 的子孙到 v 的距离要比到 u 的距离少1,就减去了son[v]+1,然后剩下 n-son[v]-1个点到 v 的距离要比到 u 的距离多1,就加上了 n-son[v]-1,所以就是 \(dis[u]+n-2\times son[v]-2\)。
手写代码,大概是下面这样。
#include <bits/stdc++.h>
using namespace std;
const int N=201000;
struct edge{
int to,next;
}e[N];
int head[N],cnt;
void add_edge(int u,int v){
e[++cnt]=(edge){v,head[u]};
head[u]=cnt;
}
int son[N],dis[N];
void dfs1(int u,int fa){
for(int i=head[u];i;i=e[i].next){
int v = e[i].to;
if(v == fa)continue;
dfs1(v, u);
son[u] += son[v]+1;
dis[u] += dis[v]+son[v]+1;
}
}
int ans=1000000009;
int n,m;
void dfs2(int u,int fa){
ans=min(ans,dis[u]);
for(int i=head[u];i;i=e[i].next){
int v = e[i].to;
if(v == fa)continue;
dis[v]=dis[u]+n-2;
dfs2(v, u);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(u, v);
add_edge(v, u);
}
dfs1(1, 0);
dfs2(1, 0);
printf("%d",ans);
return 0;
}
2. 现有 n 个木条,第 i 个长度为 a[i],切割这 n 个木条得到 k 个长度为 L 的木条,L最长是多少?
二分 L,每个木条就能得到 n/L 个,计算总共能否得到至少 k 个木条。复杂度是O(n)。
3. k个有序数组怎么归并
维护一个大小为 k 的小根堆。先把每个数组第一个放入小根堆。每次把最小的取出来放入答案,并且把它所属数组的下一个放入小根堆。
const int N=205;
vector <int> mergeKArray(int k, vector<int> arr[N]){
vector<int> ans;
int now[N];
priority_queue<pair<int, int>, vector<pair<int,int> >, greater<pair<int,int> > > heap;
for(int i=0;i<k;i++){
heap.push(make_pair(arr[i][0], i));
}
while(heap.size()){
pair<int,int> u=heap.top(); heap.pop();
ans.push_back(u.first);
int i=u.second;
now[i]++;
if(now[i]<arr[i].size()){
heap.push(make_pair(arr[i][now[i]],i));
}
}
return ans;
}
4. 求长度为 2n 的字典序第 k 大的合法括号序列,合法是符合下面两个要求:
1)空串
2)若 s 合法,()s、(s)也合法
思路是长度为2i的合法序列有\(2^{i-1}\)个,所以可以递归构造。如果\(2^{n-2} > k\),说明一定是()开头,再去构造长度为2(n-1)的第 k 大序列。否则,一定是(s)构成,再去构造长度为2(n-1)的第 \(k-2^{n-2}\)大的序列。
5. 平面有 n 个 x 坐标不同的点。求斜率最大的两个点。
按 x 坐标排序,然后斜率最大的两个点一定是相邻的,所以再两个两个判断一遍即可。
6. 有序数组找出出现超过一半的数。如果不知道有没有超过一半的,怎么判断。如果是判断超过1/3的呢?
中位数。用中位数的 lowerbound 和 upperbound 判断。用1/3和2/3位置的数的上下界判断。
7. 给定某单link链表,输出这个链表中倒数第k个结点。链表的倒数第0个结点为链表
的尾指针。链表结点定义如下:
struct LinkNode { int m_nKey; LinkNode* m_pNext; };
LinkNode* kthNode(LinkNode* head, int k){
LinkNode* p = head;
for(int i = 0; i < k; i++){
if (p->m_pNext == nullptr) {
return nullptr; // 不存在倒数第 k 个
}
p = p->m_pNext;
}
LinkNode* ans = head;
for(; p->m_pNext != nullptr; p = p->m_pNext) {
ans = ans->m_pNext;
}
return ans;
}
8. 给定一个二叉树中的两个节点,返回它们的最近公共祖先节点。
struct Node {
int key;
Node* Fa;
Node* Lson, *Rson;
};
Node* LeastCommonAncestors(Node* p, Node* q){
int pDep = 0, qDep = 0;
for(Node* now = p; now->Fa != nullptr; now = now->Fa) {
pDep++;
}
for(Node* now = q; now->Fa != nullptr; now = now->Fa) {
qDep++;
}
Node* nowp = p, *nowq = q;
for(; nowp != nowq; ) {
if(pDep >= qDep) {
pDep--;
nowp = nowp->Fa;
}
if(pDep <= qDep){
qDep--;
nowq = nowq->Fa;
}
}
return nowp;
}
To be continued...