题意:给你一幅图,再给你Q个询问,每个询问为id cost,即如果将id这条边的边权改为cost的话,这条边是否可能是最小生成树中的一条边
解题思路:将第i条边(u,v)的权值修改的话,要判断是否是最小生成树中的一条边,首先要把它加入进去,此时必定会引起原来的生成树成环,所以必定要擦去一条边,擦去的是哪一条边,这就利用到了次小生成树的原理了。
之前写过一个次小生成树的题,现在回过头看,感觉又有点不对了。
这里再总结一次:
首先肯定是构造一颗最小生成树,接下来就是枚举不在生成树里的边,假定为(u,v),此时应该把它加入到生成树中,但这样肯定会形成u->v的环路,此时肯定要删除u->v这条环路里的边,删哪一条呢?肯定是除了边(u,v)外的最大边。
现在是如何找到这条最大边,可以采用dp的思想,即dp[i][j]表示在树上i->j的最大值(注意,由于是树,肯定i->j的路径是唯一的)。我们在做Prim算法时,每次都是加入一个顶点S,我们还应该记录下顶点S加入到生成树里,它与谁相连,即我们在更新low[]数组时要记录的。这样,我们可以得到状态方程:dp[i][j] = dp[j][i] = max(dp[j][pre[i]],low[i]),此时i为即将要加入的点,而j是已经在生成树顶点集合里的点。
完成了一次Prim算法,同样也可以将dp数组更新好了,那么回到最开始的问题,删除的边我们就可以直接用dp[u][v]。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn = 100005; const int inf = 0x3f3f3f3f; struct Edge { int u,v,c; }edge[maxn]; int n,m,q; int map[1005][1005],path[1005][1005]; int pre[1005],low[1005]; bool vis[1005],used[1005][1005]; int Prim() { int k = 1,ans = 0; memset(path,0,sizeof(path)); memset(vis,false,sizeof(vis)); memset(used,false,sizeof(used)); vis[k] = true; for(int i = 1; i <= n; i++) { low[i] = map[k][i]; pre[i] = k; } for(int i = 1; i < n; i++) { int MIN = inf; for(int j = 1; j <= n; j++) if(vis[j] == false && low[j] < MIN) { MIN = low[j]; k = j; } ans += MIN; vis[k] = true; used[k][pre[k]] = used[pre[k]][k] = true; for(int j = 1; j <= n; j++) { if(vis[j] == true && j != k) path[j][k] = path[k][j] = max(path[j][pre[k]],low[k]); if(vis[j] == false && low[j] > map[k][j]) { low[j] = map[k][j]; pre[j] = k; } } } return ans; } int main() { while(scanf("%d%d%d",&n,&m,&q)!=EOF) { memset(map,inf,sizeof(map)); for(int i = 1; i <= m; i++) { scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].c); map[edge[i].u][edge[i].v] = map[edge[i].v][edge[i].u] = min(map[edge[i].u][edge[i].v],edge[i].c); } int ans = Prim(); while(q--) { int id,cost; scanf("%d%d",&id,&cost); if(path[edge[id].u][edge[id].v] >= cost) printf("Yes\n"); else printf("No\n"); } } return 0; }