【树的分治统计点对距离<=k】POJ 1741

本文介绍了一种基于图算法的分治策略,通过寻找最优分治点来减少问题规模,并利用树形DP统计子树节点数量。文章详细解释了如何通过标记根节点实现对子树的有效遍历和统计,同时提供了完整的代码示例。

虽然是看别人做的,但是有点不明白他那个标记数组vis,因为他标记的是反向边,而我习惯标记点,因此搞了很久,终于让我想明白了,如果想要标记点的话,因为是双向邻接表,只需要标记分治出来的根节点就行了,然后一层层分治下去,好好体会http://hi.baidu.com/yy17yy/blog/item/865dee006f8663147aec2cee.html

#define N  10010

struct edge{
    int v;
    int next;
    int w;
}e[N*2];
int head[N];
bool vis[N];
int ecnt;
int k;
int ans;
int q1[N],q2[N];
int e1,e2;
void init(){
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    ecnt=0;
}
void add(int u,int v,int w){
    e[ecnt].v = v;
    e[ecnt].w = w;
    e[ecnt].next = head[u];
    head[u] = ecnt++;

    e[ecnt].v = u;
    e[ecnt].w = w;
    e[ecnt].next = head[v];
    head[v] = ecnt++;
}
void cal(int u,int fa,int w){
    int i,j;
    q1[++e1] = w;
    q2[++e2] = w;
    for(i=head[u];i!=-1;i=e[i].next){
        int v = e[i].v;
        if(v==fa || vis[v])continue;
        cal(v,u,e[i].w+w);
    }
}
int sum(int *q,int t){
    int i,j;
    sort(q+1,q+t+1);
    int tot=0;
    for(i=1,j=t;;i++){
        while(q[i]+q[j]>k){
            j--;
        }
        if(j<=i)break;
        tot += j-i;
    }
    return tot;
}
int tot;
int dp[N];
void DP(int u,int fa){
    int i,j;
    tot++;
    dp[u] = 1;
    for(i=head[u];i!=-1;i=e[i].next){
        int v = e[i].v;
        if(v==fa || vis[v])continue;
        DP(v,u);
        dp[u]+=dp[v];
    }
}
int minm,tag;
void dfs(int u,int fa){
    int tmp = tot - dp[u];
    int i;
    for(i=head[u];i!=-1;i=e[i].next){
        int v = e[i].v;
        if(v==fa || vis[v])continue;
        tmp = max(tmp,dp[v]);
        dfs(v,u);
    }
    if(tmp<minm){
        minm = tmp;
        tag = u;
    }
}
int find(int u){
    tot = 0;
    DP(u,-1);//树形dp,统计以u为根结点的子树的结点数
    if(tot==1)return -1;
    minm = 1<<30;
    dfs(u,-1);
    return tag;
}

void work(int u){
    u = find(u);//找最优分治点,要求将其删去后,结点最多的树的结点个数最小
    if(u == -1)return ;
    int i,j;
    e1 = 0;
    q1[++e1] = 0;
    for(i=head[u];i!=-1;i=e[i].next){
        int v = e[i].v;
        int w = e[i].w;
        if(vis[v])continue;
        e2 = 0;
        cal(v,u,w);//统计子树每点到子树根的距离
        ans -= sum(q2,e2);//sum计算q中<=k的结点对数
    }
    ans += sum(q1,e1);
    vis[u] = 1;//因为用双向邻接表记录,所以只需要标记分治的根结点!!!
    for(i=head[u];i!=-1;i=e[i].next){
        int v = e[i].v;
        if(vis[v])continue;
        //vis[v] = 1;
        work(v);
    }
}

int main(){
    int n;
    while(scanf("%d%d",&n,&k) && (n+k)){
        int i,j;
        int u,v,w;
        init();
        for(i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
        }
        ans=0;
        work(1);
        printf("%d\n",ans);
    }
    return 0;
}





















### 问题分析 要找出连通无向图中最小生成(MST)的关键边。关键边指的是如果移除这条边,最小生成的权值就会增加。可以先使用 Kruskal 算法求出最小生成的权值,然后依次尝试移除每条边,再次使用 Kruskal 算法计算生成的权值,若权值增加,则该边为关键边。 ### 代码实现 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; // 边的结构体 struct Edge { int u, v, w; Edge(int u, int v, int w) : u(u), v(v), w(w) {} bool operator<(const Edge& other) const { return w < other.w; } }; // 并查集查找函数 int find(vector<int>& parent, int x) { if (parent[x] != x) { parent[x] = find(parent, parent[x]); } return parent[x]; } // 并查集合并函数 void unite(vector<int>& parent, int x, int y) { int rootX = find(parent, x); int rootY = find(parent, y); if (rootX != rootY) { parent[rootX] = rootY; } } // Kruskal 算法求最小生成的权值 int kruskal(int n, vector<Edge>& edges, int exclude = -1) { sort(edges.begin(), edges.end()); vector<int> parent(n + 1); for (int i = 1; i <= n; ++i) { parent[i] = i; } int mstWeight = 0; int edgeCount = 0; for (int i = 0; i < edges.size(); ++i) { if (i == exclude) continue; int u = edges[i].u; int v = edges[i].v; int w = edges[i].w; if (find(parent, u) != find(parent, v)) { unite(parent, u, v); mstWeight += w; edgeCount++; } if (edgeCount == n - 1) { break; } } return edgeCount == n - 1 ? mstWeight : -1; } // 找出关键边 vector<int> findCriticalEdges(int n, vector<Edge>& edges) { int originalMST = kruskal(n, edges); vector<int> criticalEdges; for (int i = 0; i < edges.size(); ++i) { int newMST = kruskal(n, edges, i); if (newMST == -1 || newMST > originalMST) { criticalEdges.push_back(i); } } return criticalEdges; } int main() { int n, m; cin >> n >> m; vector<Edge> edges; for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; edges.emplace_back(u, v, w); } vector<int> criticalEdges = findCriticalEdges(n, edges); // 输出关键边的编号 for (int edgeIndex : criticalEdges) { cout << edgeIndex + 1 << endl; } return 0; } ``` ### 代码解释 1. **Edge 结构体**:用于表示图中的边,包含边的两个端 `u`、`v` 和权值 `w`,并重载了 `<` 运算符用于按权值排序。 2. **并查集函数**:`find` 和 `unite` 函数用于实现并查集的查找和合并操作。 3. **kruskal 函数**:实现了 Kruskal 算法,用于计算最小生成的权值。`exclude` 参数用于指定要排除的边。 4. **findCriticalEdges 函数**:首先计算原始最小生成的权值,然后依次尝试排除每条边,再次计算生成的权值,若权值增加,则该边为关键边。 5. **main 函数**:读取输入的的数量 `n` 和边的数量 `m`,以及每条边的信息,调用 `findCriticalEdges` 函数找出关键边并输出。 ### 测评数据获取方法 - **在线评测平台**:可以使用一些知名的在线评测平台,如 LeetCode、POJ(北京大学在线评测系统)、HDU(杭州电子科技大学在线评测系统)等。这些平台上有很多图论相关的题目,可能会有关于最小生成关键边的题目,并且会提供测试数据和评测功能。 - **竞赛网站**:一些算法竞赛网站,如 Codeforces、AtCoder 等,也会有相关的题目和测试数据。 - **自己生成**:可以编写脚本自己生成测试数据,例如随机生成和边,确保图是连通的,然后计算最小生成和关键边,作为测试数据。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值