//无向图求割点 int pre[maxn], low[maxn], dfs_clock; vector<int> G[maxn]; int iscut[maxn]; int dfs(int u, int fa) //求出所有点i是否为割点iscut[i] { int lowu = pre[u] = ++dfs_clock; int child = 0; for(int i=0; i<G[u].size(); i++) { int v = G[u][i]; if(!pre[v]) { child++; int lowv = dfs(v, u); lowu = min(lowu, lowv); if(lowv >= pre[u]) iscutu] = true; //iscut[u] ++; } else if(pre[v] < pre[u] && v != fa) lowu = min(lowu, pre[v]); } //if(fa < 0) iscut[u]--; if(fa < 0 && child == 1) iscut=false; //iscut[u] = 0; low[u] = lowu; return lowu; } void solve() { memset(pre, 0, sizeof(pre)); memset(iscut, 0, sizeof(iscut)); REP(i, n) REP(i, n) if(!pre[i]) dfs(i, -1); //iscut若为int数组 使用注释中的代码 //iscut[i]+1为删除i点后原图中连通分量个数 } //无向图的双连通分量 int pre[maxn], iscut[maxn], bccno[maxn], dfs_clock, bcc_cnt; // 割顶的bccno无意义 struct Edge { int u, v; }; vector<int> G[maxn], bcc[maxn]; stack<Edge> S; int dfs(int u, int fa) { int lowu = pre[u] = ++dfs_clock; int child = 0; for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; Edge e = (Edge){u, v}; if(!pre[v]) { // 没有访问过v S.push(e); child++; int lowv = dfs(v, u); lowu = min(lowu, lowv); // 用后代的low函数更新自己 if(lowv >= pre[u]) { iscut[u] = true; bcc_cnt++; bcc[bcc_cnt].clear(); for(;;) { Edge x = S.top(); S.pop(); if(bccno[x.u] != bcc_cnt) { bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt; } if(bccno[x.v] != bcc_cnt) { bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt; } if(x.u == u && x.v == v) break; } } } else if(pre[v] < pre[u] && v != fa) { S.push(e); lowu = min(lowu, pre[v]); // 用反向边更新自己 } } if(fa < 0 && child == 1) iscut[u] = 0; return lowu; } void find_bcc(int n) { // 调用结束后S保证为空,所以不用清空 memset(pre, 0, sizeof(pre)); memset(iscut, 0, sizeof(iscut)); memset(bccno, 0, sizeof(bccno)); dfs_clock = bcc_cnt = 0; for(int i = 0; i < n; i++) if(!pre[i]) dfs(i, -1); }; //无向图求桥 & 双连通分量 int dfs(int u, int fa) { int lowu = pre[u] = ++dfs_clock; int nc = G[u].size(); REP(i, nc) { int v = edges[G[u][i]].to; if(!pre[v]) { int lowv = dfs(v, u); lowu = min(lowu, lowv); if(lowv > pre[u]) edges[G[u][i]].flag = 1, edges[G[u][i]^1].flag = 1; //标记所有桥 } else if(pre[v] < pre[u] && v != fa) lowu = min(lowu, pre[v]); } return low[u] = lowu; } void dfs1(int u) { bccno[u] = bcc_cnt; int nc = G[u].size(); REP(i, nc) { int v = edges[G[u][i]].to; if(!bccno[v] && edges[G[u][i]].flag != 1) dfs1(v);//不经过桥 } } void find_bcc() { CLR(pre, 0); CLR(bccno, 0); dfs_clock = bcc_cnt = 0; REP(i, n) if(!pre[i]) dfs(i, -1); REP(i, n) if(!bccno[i]) bcc_cnt++, dfs1(i); } //有向图的强连通分量 vector<int> G[maxn]; int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt; stack<int> S; void dfs(int u) { pre[u] = lowlink[u] = ++dfs_clock; S.push(u); for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!pre[v]) { dfs(v); lowlink[u] = min(lowlink[u], lowlink[v]); } else if(!sccno[v]) { lowlink[u] = min(lowlink[u], pre[v]); } } if(lowlink[u] == pre[u]) { scc_cnt++; for(;;) { int x = S.top(); S.pop(); sccno[x] = scc_cnt; if(x == u) break; } } } void find_scc(int n) { dfs_clock = scc_cnt = 0; memset(sccno, 0, sizeof(sccno)); memset(pre, 0, sizeof(pre)); for(int i = 0; i < n; i++) if(!pre[i]) dfs(i); }; //无向图的欧拉回路 保存在G中 void add(int u, int v) { g[u][v] = g[v][u] = 1; degree[u]++, degree[v]++; } void Euler() { FF(i, 1, n+1) if(degree[i]) { int u = i; while(true) { FF(j, 1, n+1) if(g[u][j] && g[j][u]) { g[j][u] = 0; degree[u]--, degree[i]--; u = j; break; } if(u == i) break; } } } //2-sat dfs版本 struct TwoSAT { int n; vector<int> G[maxn*2]; bool mark[maxn*2]; int S[maxn*2], c; bool dfs(int x) { if (mark[x^1]) return false; if (mark[x]) return true; mark[x] = true; S[c++] = x; for (int i = 0; i < G[x].size(); i++) if (!dfs(G[x][i])) return false; return true; } void init(int n) { this->n = n; for (int i = 0; i < n*2; i++) G[i].clear(); memset(mark, 0, sizeof(mark)); } // x = xval or y = yval void add_clause(int x, int xval, int y, int yval) { x = x * 2 + xval; y = y * 2 + yval; G[x^1].push_back(y); G[y^1].push_back(x); } bool solve() { for(int i = 0; i < n*2; i += 2) if(!mark[i] && !mark[i+1]) { c = 0; if(!dfs(i)) { while(c > 0) mark[S[--c]] = false; if(!dfs(i+1)) return false; } } return true; } }; //堆优化的Dijkstra struct Edge { int from, to, dist; }; struct HeapNode { int d, u; bool operator < (const HeapNode& rhs) const { return d > rhs.d; } }; struct Dijkstra { int n, m; vector<Edge> edges; vector<int> G[maxn]; bool done[maxn]; // 是否已永久标号 int d[maxn]; // s到各个点的距离 int p[maxn]; // 最短路中的上一条弧 void init(int n) { this->n = n; for(int i = 0; i < n; i++) G[i].clear(); edges.clear(); } void AddEdge(int from, int to, int dist) { edges.push_back((Edge){from, to, dist}); m = edges.size(); G[from].push_back(m-1); } void dijkstra(int s) { priority_queue<HeapNode> Q; for(int i = 0; i < n; i++) d[i] = INF; d[s] = 0; memset(done, 0, sizeof(done)); Q.push((HeapNode){0, s}); while(!Q.empty()) { HeapNode x = Q.top(); Q.pop(); int u = x.u; if(done[u]) continue; done[u] = true; for(int i = 0; i < G[u].size(); i++) { Edge& e = edges[G[u][i]]; if(d[e.to] > d[u] + e.dist) { d[e.to] = d[u] + e.dist; p[e.to] = G[u][i]; Q.push((HeapNode){d[e.to], e.to}); } } } } // dist[i]为s到i的距离,paths[i]为s到i的最短路径(经过的结点列表,包括s和t) void GetShortestPaths(int s, int* dist, vector<int>* paths) { dijkstra(s); for(int i = 0; i < n; i++) { dist[i] = d[i]; paths[i].clear(); int t = i; paths[i].push_back(t); while(t != s) { paths[i].push_back(edges[p[t]].from); t = edges[p[t]].from; } reverse(paths[i].begin(), paths[i].end()); } } }; //spfa判负环 struct Edge { int from, to; double dist; }; struct spfa { int n, m; vector<Edge> edges; vector<int> G[maxn]; bool inq[maxn]; // 是否在队列中 double d[maxn]; // s到各个点的距离 int p[maxn]; // 最短路中的上一条弧 int cnt[maxn]; // 进队次数 void init(int n) { this->n = n; for(int i = 0; i < n; i++) G[i].clear(); edges.clear(); } void AddEdge(int from, int to, double dist) { edges.push_back((Edge){from, to, dist}); m = edges.size(); G[from].push_back(m-1); } bool negativeCycle() { queue<int> Q; memset(inq, 0, sizeof(inq)); memset(cnt, 0, sizeof(cnt)); for(int i = 0; i < n; i++) { d[i] = 0; inq[0] = true; Q.push(i); } while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int i = 0; i < G[u].size(); i++) { Edge& e = edges[G[u][i]]; if(d[e.to] > d[u] + e.dist) { d[e.to] = d[u] + e.dist; p[e.to] = G[u][i]; if(!inq[e.to]) { Q.push(e.to); inq[e.to] = true; if(++cnt[e.to] > n) return true; } } } } return false; } }; //kruskal求次小生成树 maxcost[i][j]为i->j的瓶颈路 //对于MST边u, v maxcost[u][v] = 0 int n, m, x[maxn], y[maxn], p[maxn]; int pa[maxn]; int findset(int x) { return pa[x] != x ? pa[x] = findset(pa[x]) : x; } //G保存MST C保存MST边权 vector<int> G[maxn]; vector<double> C[maxn]; struct Edge { int x, y; double d; bool operator < (const Edge& rhs) const { return d < rhs.d; } }; Edge e[maxn*maxn]; double maxcost[maxn][maxn]; vector<int> nodes; void dfs(int u, int fa, double facost) { for(int i = 0; i < nodes.size(); i++) { int x = nodes[i]; maxcost[u][x] = maxcost[x][u] = max(maxcost[x][fa], facost); } nodes.push_back(u); for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(v != fa) dfs(v, u, C[u][i]); } } double MST() { sort(e, e+m); for(int i = 0; i < n; i++) { pa[i] = i; G[i].clear(); C[i].clear(); } int cnt = 0; double ans = 0; for(int i = 0; i < m; i++) { int x = e[i].x, y = e[i].y, u = findset(x), v = findset(y); double d = e[i].d; if(u != v) { pa[u] = v; G[x].push_back(y); C[x].push_back(d); G[y].push_back(x); C[y].push_back(d); ans += d; if(++cnt == n-1) break; } } return ans; } //prim求次小生成树 //use[u][v] = 2时,边<u, v>在MST上 //use[u][v] = 1时,原图存在边<u, v>。 //f[u][v]表示u->v的最小瓶颈路 初始化为0 double prim() { int pre[maxn] = {-1}; bool vis[maxn] = {0}; double d[maxn], ret = 0; FF(i, 1, n+1) d[i] = INF; d[1] = 0; FF(i, 1, n+1) { int pos; double tmp = INF; FF(j, 1, n+1) if(!vis[j] && d[j] < tmp) tmp = d[j], pos = j; if(pre[pos] != -1) { use[pre[pos]][pos] = use[pos][pre[pos]] = 2; FF(j, 1, n+1) if(vis[j]) f[pos][j] = f[j][pos] = max(f[j][pre[pos]], g[pre[pos]][pos]); } vis[pos] = 1; ret += d[pos]; FF(j, 1, n+1) if(!vis[j] && use[pos][j] && g[pos][j] < d[j]) d[j] = g[pos][j], pre[j] = pos; } return ret; } //LCA struct LCA { int n; int fa[maxn]; // 父亲数组 int cost[maxn]; // 和父亲的费用 int L[maxn]; // 层次(根节点层次为0) int anc[maxn][logmaxn]; // anc[p][i]是结点p的第2^i级父亲。anc[i][0] = fa[i] int maxcost[maxn][logmaxn]; // maxcost[p][i]是i和anc[p][i]的路径上的最大费用 // 预处理,根据fa和cost数组求出anc和maxcost数组 void preprocess() { for(int i = 0; i < n; i++) { anc[i][0] = fa[i]; maxcost[i][0] = cost[i]; for(int j = 1; (1 << j) < n; j++) anc[i][j] = -1; } for(int j = 1; (1 << j) < n; j++) for(int i = 0; i < n; i++) if(anc[i][j-1] != -1) { int a = anc[i][j-1]; anc[i][j] = anc[a][j-1]; maxcost[i][j] = max(maxcost[i][j-1], maxcost[a][j-1]); } } // 求p到q的路径上的最大权 int query(int p, int q) { int tmp, log, i; if(L[p] < L[q]) swap(p, q); for(log = 1; (1 << log) <= L[p]; log++); log--; int ans = -INF; for(int i = log; i >= 0; i--) if (L[p] - (1 << i) >= L[q]) { ans = max(ans, maxcost[p][i]); p = anc[p][i];} if (p == q) return ans; // LCA为p for(int i = log; i >= 0; i--) if(anc[p][i] != -1 && anc[p][i] != anc[q][i]) { ans = max(ans, maxcost[p][i]); p = anc[p][i]; ans = max(ans, maxcost[q][i]); q = anc[q][i]; } ans = max(ans, cost[p]); ans = max(ans, cost[q]); return ans; // LCA为fa[p](它也等于fa[q]) } }; //生成树计数问题 int degree[N]; LL C[N][N]; LL det(LL a[][N], int n)//生成树计数:Matrix-Tree定理 { LL ret=1; for(int i=1; i<n; i++) { for(int j=i+1; j<n; j++) while(a[j][i]) { LL t=a[i][i]/a[j][i]; for(int k=i; k<n; k++) a[i][k]=(a[i][k]-a[j][k]*t); for(int k=i; k<n; k++) swap(a[i][k],a[j][k]); ret=-ret; } if(a[i][i]==0) return 0; ret=ret*a[i][i]; } if(ret<0) ret=-ret; return ret; } void solve() { memset(degree,0,sizeof(degree)); memset(C,0,sizeof(C)); scanf("%d%d",&n,&m); while(m--) { scanf("%d%d",&u,&v); u--; v--; C[u][v]=C[v][u]=-1; degree[u]++; degree[v]++; } for(int i=0; i<n; ++i) C[i][i]=degree[i]; printf("%lld\n",det(C,n)); } //离线MST //n个点m条边,然后给出q个询问,表示把目前第numi条边的边权修改为di后的mst int n,m,q; int x[MAXM],y[MAXM],num[MAXM],d[MAXM],f[MAXN],ord[MAXM],t[MAXN]; long long z[MAXM],c[MAXM], answer; struct Edge{ int cnt; int a[MAXM*5],b[MAXN*5]; inline void renew(int); inline void merge(int u,int v); inline int find(int v); }edge[20]; inline void Edge::renew(int top = 0) { for (;cnt != top; --cnt) f[a[cnt]] = b[cnt]; } inline void Edge::merge(int v,int u) { int _v = find(v); int _u = find(u); a[++cnt] = _v; b[cnt] = f[_v]; f[_v] = _u; } inline int Edge::find(int v) { if (!f[v]) return v; int ret = v; while (f[ret]) ret = f[ret]; while (f[v] != ret) { a[++cnt] = v; b[cnt] = f[v]; f[v] = ret; v = b[cnt]; } return ret; } void Qsort(int l,int r) { long long k = z[ord[(l+r)/2]]; int i = l, j = r; while (i<j) { while (z[ord[i]] < k) i++; while (z[ord[j]] > k) j--; if (i<=j) swap(ord[i++], ord[j--]); } if (i<r) Qsort(i,r); if (l<j) Qsort(l,j); } inline void work(int l ,int r,int dep) { Edge &e = edge[dep]; e.cnt = 0; if (l>r) return ; if (l == r) { z[num[l]] = c[num[l]] = d[l]; Qsort(1,m); long long ans = answer; for (int i = 1; i <= m;++i) if (e.find(x[ord[i]]) != e.find(y[ord[i]])) { ans+=z[ord[i]]; e.merge(x[ord[i]],y[ord[i]]); } e.renew(); printf("%lld\n",ans); return ; } int temp_m = m; long long temp_ans = answer; //Contrresume; for (int i = l; i <= r; ++i) z[num[i]] = -INF; Qsort(1,m); t[0] = 0; for (int i = 1; i <= m; ++i) { if (e.find(x[ord[i]]) != e.find(y[ord[i]])) { if (z[ord[i]] != -INF) t[++t[0]] = ord[i]; e.merge(x[ord[i]],y[ord[i]]); } } e.renew(); for (int i = 1; i <= t[0] ; ++i) { e.merge(x[t[i]], y[t[i]]); answer +=z[t[i]]; } int temp_cnt = e.cnt; //Reduction for (int i = l; i <= r; ++i) z[num[i]] = INF; Qsort(1,m); t[0] =0; for (int i = 1; i <= m; ++i) { if (e.find(x[ord[i]]) != e.find(y[ord[i]])) e.merge(x[ord[i]], y[ord[i]]); else { if (z[ord[i]] != INF) t[++t[0]] = i; } } for (int i = t[0]; i ; --i) swap(ord[t[i]],ord[m--]); e.renew(temp_cnt); for (int i =l ; i<=r; ++i) z[num[i]] = c[num[i]]; work(l,(l+r)/2, dep+1); work((l+r)/2+1,r,dep+1); e.renew(); answer = temp_ans; m = temp_m; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m ;++i) { scanf("%d %d %d", &x[i],&y[i],&z[i]); c[i] = z[i]; ord[i] = i; } scanf("%d",&q); Qsort(1,m); for (int i = 1; i <= q; ++i) scanf("%d %d", &num[i], &d[i]); work(1,q,0); return 0; } //固定根的最小树型图,邻接矩阵写法 struct MDST { int n; int w[maxn][maxn]; // 边权 int vis[maxn]; // 访问标记,仅用来判断无解 int ans; // 计算答案 int removed[maxn]; // 每个点是否被删除 int cid[maxn]; // 所在圈编号 int pre[maxn]; // 最小入边的起点 int iw[maxn]; // 最小入边的权值 int max_cid; // 最大圈编号 void init(int n) { this->n = n; for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) w[i][j] = INF; } void AddEdge(int u, int v, int cost) { w[u][v] = min(w[u][v], cost); // 重边取权最小的 } // 从s出发能到达多少个结点 int dfs(int s) { vis[s] = 1; int ans = 1; for(int i = 0; i < n; i++) if(!vis[i] && w[s][i] < INF) ans += dfs(i); return ans; } // 从u出发沿着pre指针找圈 bool cycle(int u) { max_cid++; int v = u; while(cid[v] != max_cid) { cid[v] = max_cid; v = pre[v]; } return v == u; } // 计算u的最小入弧,入弧起点不得在圈c中 void update(int u) { iw[u] = INF; for(int i = 0; i < n; i++) if(!removed[i] && w[i][u] < iw[u]) { iw[u] = w[i][u]; pre[u] = i; } } // 根结点为s,如果失败则返回false bool solve(int s) { memset(vis, 0, sizeof(vis)); if(dfs(s) != n) return false; memset(removed, 0, sizeof(removed)); memset(cid, 0, sizeof(cid)); for(int u = 0; u < n; u++) update(u); pre[s] = s; iw[s] = 0; // 根结点特殊处理 ans = max_cid = 0; for(;;) { bool have_cycle = false; for(int u = 0; u < n; u++) if(u != s && !removed[u] && cycle(u)){ have_cycle = true; // 以下代码缩圈,圈上除了u之外的结点均删除 int v = u; do { if(v != u) removed[v] = 1; ans += iw[v]; // 对于圈外点i,把边i->v改成i->u(并调整权值);v->i改为u->i // 注意圈上可能还有一个v'使得i->v'或者v'->i存在,因此只保留权值最小的i->u和u->i for(int i = 0; i < n; i++) if(cid[i] != cid[u] && !removed[i]) { if(w[i][v] < INF) w[i][u] = min(w[i][u], w[i][v]-iw[v]); w[u][i] = min(w[u][i], w[v][i]); if(pre[i] == v) pre[i] = u; } v = pre[v]; } while(v != u); update(u); break; } if(!have_cycle) break; } for(int i = 0; i < n; i++) if(!removed[i]) ans += iw[i]; return true; } }; //KM int W[maxn][maxn], n; int Lx[maxn], Ly[maxn]; // 顶标 int left[maxn]; // left[i]为右边第i个点的匹配点编号 bool S[maxn], T[maxn]; // S[i]和T[i]为左/右第i个点是否已标记 bool match(int i){ S[i] = true; for(int j = 1; j <= n; j++) if (Lx[i]+Ly[j] == W[i][j] && !T[j]){ T[j] = true; if (!left[j] || match(left[j])){ left[j] = i; return true; } } return false; } void update(){ int a = INF; for(int i = 1; i <= n; i++) if(S[i]) for(int j = 1; j <= n; j++) if(!T[j]) a = min(a, Lx[i]+Ly[j] - W[i][j]); for(int i = 1; i <= n; i++) { if(S[i]) Lx[i] -= a; if(T[i]) Ly[i] += a; } } void KM() { for(int i = 1; i <= n; i++) { left[i] = Lx[i] = Ly[i] = 0; for(int j = 1; j <= n; j++) Lx[i] = max(Lx[i], W[i][j]); } for(int i = 1; i <= n; i++) { for(;;) { for(int j = 1; j <= n; j++) S[j] = T[j] = false; if(match(i)) break; else update(); } } } // 二分图最大基数匹配,邻接矩阵写法 struct BPM { int n, m; // 左右顶点个数 int G[maxn][maxn]; // 邻接表 int left[maxn]; // left[i]为右边第i个点的匹配点编号,-1表示不存在 bool T[maxn]; // T[i]为右边第i个点是否已标记 void init(int n, int m) { this->n = n; this->m = m; memset(G, 0, sizeof(G)); } bool match(int u){ for(int v = 0; v < m; v++) if(G[u][v] && !T[v]) { T[v] = true; if (left[v] == -1 || match(left[v])){ left[v] = u; return true; } } return false; } // 求最大匹配 int solve() { memset(left, -1, sizeof(left)); int ans = 0; for(int u = 0; u < n; u++) { // 从左边结点u开始增广 memset(T, 0, sizeof(T)); if(match(u)) ans++; } return ans; } }; // 二分图最大基数匹配求覆盖集 struct BPM { int n, m; // 左右顶点个数 vector<int> G[maxn]; // 邻接表 int left[maxn]; // left[i]为右边第i个点的匹配点编号,-1表示不存在 bool T[maxn]; // T[i]为右边第i个点是否已标记 int right[maxn]; // 求最小覆盖用 bool S[maxn]; // 求最小覆盖用 void init(int n, int m) { this->n = n; this->m = m; for(int i = 0; i < n; i++) G[i].clear(); } void AddEdge(int u, int v) { G[u].push_back(v); } bool match(int u){ S[u] = true; for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if (!T[v]){ T[v] = true; if (left[v] == -1 || match(left[v])){ left[v] = u; right[u] = v; return true; } } } return false; } // 求最大匹配 int solve() { memset(left, -1, sizeof(left)); memset(right, -1, sizeof(right)); int ans = 0; for(int u = 0; u < n; u++) { // 从左边结点u开始增广 memset(S, 0, sizeof(S)); memset(T, 0, sizeof(T)); if(match(u)) ans++; } return ans; } // 求最小覆盖。X和Y为最小覆盖中的点集 int mincover(vector<int>& X, vector<int>& Y) { int ans = solve(); memset(S, 0, sizeof(S)); memset(T, 0, sizeof(T)); for(int u = 0; u < n; u++) if(right[u] == -1) match(u); // 从所有X未盖点出发增广 for(int u = 0; u < n; u++) if(!S[u]) X.push_back(u); // X中的未标记点 for(int v = 0; v < m; v++) if(T[v]) Y.push_back(v); // Y中的已标记点 return ans; } }; //二分匹配 邻接表版本 struct Edge { int to,next; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; CLR(head, -1); } void addEdge(int u,int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; } int linker[MAXN]; int used[MAXN]; //时间戳when优化 int uN, when; bool dfs(int u) { for(int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to; if(used[v] != when) { used[v] = when; if(linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } bool hungary() { memset(linker,-1,sizeof(linker)); when = 0; for(int u = 0; u < uN;u++) { when++; if(!dfs(u))return false; } return true; } //ISAP struct Edge { int from, to, cap, flow; }; bool operator < (const Edge& a, const Edge& b) { return a.from < b.from || (a.from == b.from && a.to < b.to); } struct ISAP { int n, m, s, t; vector<Edge> edges; vector<int> G[maxn]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号 bool vis[maxn]; // BFS使用 int d[maxn]; // 从起点到i的距离 int cur[maxn]; // 当前弧指针 int p[maxn]; // 可增广路上的上一条弧 int num[maxn]; // 距离标号计数 void AddEdge(int from, int to, int cap) { edges.push_back((Edge){from, to, cap, 0}); edges.push_back((Edge){to, from, 0, 0}); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis, 0, sizeof(vis)); queue<int> Q; Q.push(t); vis[t] = 1; d[t] = 0; while(!Q.empty()) { int x = Q.front(); Q.pop(); for(int i = 0; i < G[x].size(); i++) { Edge& e = edges[G[x][i]^1]; if(!vis[e.from] && e.cap > e.flow) { vis[e.from] = 1; d[e.from] = d[x] + 1; Q.push(e.from); } } } return vis[s]; } void ClearAll(int n) { this->n = n; for(int i = 0; i < n; i++) G[i].clear(); edges.clear(); } void ClearFlow() { for(int i = 0; i < edges.size(); i++) edges[i].flow = 0; } int Augment() { int x = t, a = INF; while(x != s) { Edge& e = edges[p[x]]; a = min(a, e.cap-e.flow); x = edges[p[x]].from; } x = t; while(x != s) { edges[p[x]].flow += a; edges[p[x]^1].flow -= a; x = edges[p[x]].from; } return a; } int Maxflow(int s, int t, int need) { this->s = s; this->t = t; int flow = 0; BFS(); memset(num, 0, sizeof(num)); for(int i = 0; i < n; i++) num[d[i]]++; int x = s; memset(cur, 0, sizeof(cur)); while(d[s] < n) { if(x == t) { flow += Augment(); if(flow >= need) return flow; x = s; } int ok = 0; for(int i = cur[x]; i < G[x].size(); i++) { Edge& e = edges[G[x][i]]; if(e.cap > e.flow && d[x] == d[e.to] + 1) { // Advance ok = 1; p[e.to] = G[x][i]; cur[x] = i; // 注意 x = e.to; break; } } if(!ok) { // Retreat int m = n-1; // 初值注意 for(int i = 0; i < G[x].size(); i++) { Edge& e = edges[G[x][i]]; if(e.cap > e.flow) m = min(m, d[e.to]); } if(--num[d[x]] == 0) break; num[d[x] = m+1]++; cur[x] = 0; // 注意 if(x != s) x = edges[p[x]].from; } } return flow; } vector<int> Mincut() { // call this after maxflow BFS(); vector<int> ans; for(int i = 0; i < edges.size(); i++) { Edge& e = edges[i]; if(!vis[e.from] && vis[e.to] && e.cap > 0) ans.push_back(i); } return ans; } void Reduce() { for(int i = 0; i < edges.size(); i++) edges[i].cap -= edges[i].flow; } void print() { printf("Graph:\n"); for(int i = 0; i < edges.size(); i++) printf("%d->%d, %d, %d\n", edges[i].from, edges[i].to , edges[i].cap, edges[i].flow); } }; //Dinic struct Edge { int from, to, cap, flow; }; bool operator < (const Edge& a, const Edge& b) { return a.from < b.from || (a.from == b.from && a.to < b.to); } struct Dinic { int n, m, s, t; vector<Edge> edges; // 边数的两倍 vector<int> G[maxn]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号 bool vis[maxn]; // BFS使用 int d[maxn]; // 从起点到i的距离 int cur[maxn]; // 当前弧指针 void ClearAll(int n) { for(int i = 0; i < n; i++) G[i].clear(); edges.clear(); } void ClearFlow() { for(int i = 0; i < edges.size(); i++) edges[i].flow = 0; } void AddEdge(int from, int to, int cap) { edges.push_back((Edge){from, to, cap, 0}); edges.push_back((Edge){to, from, 0, 0}); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis, 0, sizeof(vis)); queue<int> Q; Q.push(s); vis[s] = 1; d[s] = 0; while(!Q.empty()) { int x = Q.front(); Q.pop(); for(int i = 0; i < G[x].size(); i++) { Edge& e = edges[G[x][i]]; if(!vis[e.to] && e.cap > e.flow) { vis[e.to] = 1; d[e.to] = d[x] + 1; Q.push(e.to); } } } return vis[t]; } int DFS(int x, int a) { if(x == t || a == 0) return a; int flow = 0, f; for(int& i = cur[x]; i < G[x].size(); i++) { Edge& e = edges[G[x][i]]; if(d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap-e.flow))) > 0) { e.flow += f; edges[G[x][i]^1].flow -= f; flow += f; a -= f; if(a == 0) break; } } return flow; } int Maxflow(int s, int t) { this->s = s; this->t = t; int flow = 0; while(BFS()) { memset(cur, 0, sizeof(cur)); flow += DFS(s, INF); } return flow; } }; //MCMF struct Edge { int from, to, cap, flow, cost; Edge(){} Edge(int a, int b, int c, int d, int e):from(a), to(b), cap(c), flow(d), cost(e){} }; struct MCMF { int n, m, s, t; vector<Edge> edges; vector<int> G[maxn]; int inq[maxn]; // 是否在队列中 int d[maxn]; // Bellman-Ford int p[maxn]; // 上一条弧 int a[maxn]; // 可改进量 void init(int n) { this->n = n; for(int i = 0; i < n; i++) G[i].clear(); edges.clear(); } void AddEdge(int from, int to, int cap, int cost) { Edge e1 = Edge(from, to, cap, 0, cost), e2 = Edge(to, from, 0, 0, -cost); edges.push_back(e1); edges.push_back(e2); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool spfa(int s, int t, int &flow, int &cost) { REP(i, t+1) d[i] = INF; memset(inq, 0, sizeof(inq)); d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF; queue<int> Q; Q.push(s); while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = 0; REP(i, G[u].size()) { Edge& e = edges[G[u][i]]; if(e.cap > e.flow && d[e.to] > d[u] + e.cost) { d[e.to] = d[u] + e.cost; p[e.to] = G[u][i]; a[e.to] = min(a[u], e.cap - e.flow); if(!inq[e.to]) { Q.push(e.to); inq[e.to] = 1; } } } } if(d[t] == INF) return false;//!!! flow += a[t]; cost += d[t] * a[t]; int u = t; while(u != s) { edges[p[u]].flow += a[t]; edges[p[u]^1].flow -= a[t]; u = edges[p[u]].from; } return true; } // 需要保证初始网络中没有负权圈 int Mincost(int s, int t, int& flow, int& cost) { flow = cost = 0; while(spfa(s, t, flow, cost)); return cost; } }; //ZKW费用流 struct ZKW_flow{ int st, ed, ecnt, n; int head[MAXN]; int cap[MAXE], cost[MAXE], to[MAXE], next[MAXE], dis[MAXN]; ; void init(){ memset(head, 0, sizeof(head)); ecnt = 2; } void addEdge(int u, int v, int cc, int ww){ cap[ecnt] = cc; cost[ecnt] = ww; to[ecnt] = v; next[ecnt] = head[u]; head[u] = ecnt++; cap[ecnt] = 0; cost[ecnt] = -ww; to[ecnt] = u; next[ecnt] = head[v]; head[v] = ecnt++; } void SPFA(){ for(int i = 1; i <= n; ++i) dis[i] = INF; priority_queue<pair<int, int> > Q; dis[st] = 0; Q.push(make_pair(0, st)); while(!Q.empty()){ int u = Q.top().second, d = -Q.top().first; Q.pop(); if(dis[u] != d) continue; for(int p = head[u]; p; p = next[p]){ int &v = to[p]; if(cap[p] && dis[v] > d + cost[p]){ dis[v] = d + cost[p]; Q.push(make_pair(-dis[v], v)); } } } for(int i = 1; i <= n; ++i) dis[i] = dis[ed] - dis[i]; } int minCost, maxFlow; bool use[MAXN]; int add_flow(int u, int flow){ if(u == ed){ maxFlow += flow; minCost += dis[st] * flow; return flow; } use[u] = true; int now = flow; for(int p = head[u]; p; p = next[p]){ int &v = to[p]; if(cap[p] && !use[v] && dis[u] == dis[v] + cost[p]){ int tmp = add_flow(v, min(now, cap[p])); cap[p] -= tmp; cap[p^1] += tmp; now -= tmp; if(!now) break; } } return flow - now; } bool modify_label(){ int d = INF; for(int u = 1; u <= n; ++u) if(use[u]) for(int p = head[u]; p; p = next[p]){ int &v = to[p]; if(cap[p] && !use[v]) d = min(d, dis[v] + cost[p] - dis[u]); } if(d == INF) return false; for(int i = 1; i <= n; ++i) if(use[i]) dis[i] += d; return true; } int min_cost_flow(int ss, int tt, int nn){ st = ss, ed = tt, n = nn; minCost = maxFlow = 0; SPFA(); while(true){ while(true){ for(int i = 1; i <= n; ++i) use[i] = 0; if(!add_flow(st, INF)) break; } if(!modify_label()) break; } return minCost; } }; //斯坦纳树 //此代码用于求hdu4085 最小斯坦纳森林 const int N = (1<<10) + 10; int n, m, K, tot; int s[maxn], d[maxn][N], dp[N], u, v, w; bool vis[maxn][N]; void spfa() { while(!q.empty()) { int x = q.front() / MOD, y = q.front() % MOD; q.pop(); vis[x][y] = 0; int nc = G[x].size(); REP(i, nc) { Edge e = edges[G[x][i]]; int v = e.to, t = y | s[v]; if(d[v][t] > d[x][y] + e.dist) { d[v][t] = d[x][y] + e.dist; if(t == y && !vis[v][y]) { vis[v][y] = 1; q.push(v*MOD + y); } } } } } void init() { FF(i, 1, n+1) G[i].clear(); edges.clear(); CLR(s, 0); scanf("%d%d%d", &n, &m, &K); tot=1<<(2*K); for(int i=1;i<=n;i++) for(int j=0;j<tot;j++) d[i][j]=INF; while(m--) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); } for(int i=1;i<=K;i++) { s[i]=1<<(i-1),d[i][s[i]]=0; s[n-i+1]=1<<(K+i-1),d[n-i+1][s[n-i+1]]=0; } } void go_spfa() { REP(y, tot) { FF(x, 1, n+1) { for(int i=(y-1)&y; i; i=(i-1)&y) d[x][y] = min(d[x][y], d[x][i|s[x]] + d[x][(y-i)|s[x]]); if(d[x][y] < INF) { q.push(x*MOD + y); vis[x][y] = 1; } } spfa(); } } void solve() { REP(y, tot) { dp[y] = INF; FF(x, 1, n+1) dp[y] = min(dp[y], d[x][y]); } FF(i, 1, tot) if(check(i)) for(int j=(i-1)&i; j; j=(j-1)&i) if(check(j)) dp[i] = min(dp[i], dp[j] + dp[i-j]); if(dp[tot-1] >= INF) puts("No solution"); else printf("%d\n", dp[tot-1]); } int main() { int T; scanf("%d", &T); while(T--) { init(); go_spfa(); solve(); } return 0; } //次小生成树 最佳替代边 //当边<i, j>为MST边时 dp[i][j]为边ij的最佳替代边 void prim() { REP(i, n) d[i] = g[0][i]; vis[0] = 1; fa[0] = -1; d[0] = INF; mst = 0; FF(i, 1, n) { int pos = 0; FF(j, 1, n) if(!vis[j] && d[pos] > d[j]) pos = j; mst += d[pos]; vis[pos] = 1; //构造MST G[pos].PB(fa[pos]); G[fa[pos]].PB(pos); FF(j, 1, n) if(!vis[j] && g[pos][j] < d[j]) d[j] = g[pos][j], fa[j] = pos; } } //用所有非MST边g[p][i] 更新所有dp[u][v] double dfs(int p, int u, int f) { double ans = INF; REP(i, G[u].size()) { int v = G[u][i]; if(v != f) { double tmp = dfs(p, v, u); ans = min(ans, tmp); dp[u][v] = dp[v][u] = min(dp[u][v], tmp); } } //保证非MST边才能更新 if(p != f) ans = min(ans, g[p][u]); return ans; } void solve() { prim(); REP(i, n) dfs(i, i, -1); //每个点更新一次 } //全局最小割 int map[MAXN][MAXN]; int v[MAXN], dis[MAXN]; //v数组是马甲数组,dis数组用来表示该点与A集合中所有点之间的边的长度之和 bool vis[MAXN];//用来标记是否该点加入了A集合 int Stoer_Wagner(int n) { int i, j, res = INF; for(i = 0; i < n; i ++) v[i] = i; //初始马甲为自己 while(n > 1) { int k, pre = 0; //pre用来表示之前加入A集合的点,我们每次都以0点为第一个加入A集合的点 memset(vis, 0, sizeof(vis)); memset(dis, 0, sizeof(dis)); for(i = 1; i < n; i ++) { k = -1; for(j = 1; j < n; j ++) //根据之前加入的点,要更新dis数组,并找到最大的dis if(!vis[v[j]]) { dis[v[j]] += map[v[pre]][v[j]]; if(k == -1 || dis[v[k]] < dis[v[j]]) k = j; } vis[v[k]] = true;//标记该点已经加入A集合 if(i == n - 1) //最后一次加入的点就要更新答案了 { res = min(res, dis[v[k]]); for(j = 0; j < n; j ++) //将该点合并到pre上,相应的边权就要合并 { map[v[pre]][v[j]] += map[v[j]][v[k]]; map[v[j]][v[pre]] += map[v[j]][v[k]]; } v[k] = v[-- n];//删除最后一个点 } pre = k; } } return res; } /* 无源汇上下界网络流:构图如下: 1、首先对于与每条边(u,v,L,H),u->v连一条容量为H-L的边 2、创建一个源S,和汇T 3、对于任意一个结点,如果u出边下界和 OutL > 入边下界和InL,则u->T一条OutL - InL的边。 否则连S->u一条InL-OutL的边。 4、求s-t最大流,若与S关联的边满容量,则有解。则残余网络中u->v的流量+其原来图的下界构成一个可行流 */ /* 有源汇(S T)上下界最小流 像无源无汇上下界可行流那样先建好图,记图的超级源点为SS,超级汇点为TT。 先从SS到TT跑一遍最大流,然后加边T->S容量为无穷大,然后再从SS到TT跑一遍最大流,若与SS关联的边满容量,则有解。 其中最小流为最后加进去的n→1的边的流量,找边的流量跟无源无汇上下界可行流一样,否则无解。 */ /* 有源汇(S T)上下界最大流 像无源无汇上下界可行流那样先建好图,记图的超级源点为SS,超级汇点为TT。 然后T到S连一条边,容量为无穷大。 从SS->TT跑一遍最大流 判可行性 最后从源点S到汇点T跑一遍最大流就是答案, 每条边容量的取法和无源无汇上下界可行流一样。 */