P1550 [USACO08OCT] Watering Hole G
题目描述
Farmer John 的农场缺水了。
他决定将水引入到他的 n 个田地。他准备通过挖若干井,并在各块田中修筑水道来连通各块田地以供水。在第 i 号田中挖一口井需要花费 Wi 元。连接 i 号田与 j 号田需要 Pi,j(Pj,i=Pi,j)元。
请求出 FJ 需要为使所有田地都与有水的田地相连或拥有水井所需要的最少钱数。
输入格式
第一行为一个整数 n。
接下来 n 行,每行一个整数 Wi。
接下来 n 行,每行 n 个整数,第 i 行的第 j 个数表示连接 i 号田和 j 号田需要的费用 Pi,j。
输出格式
输出最小开销。
输入输出样例
输入 #1复制
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
输出 #1复制
9
说明/提示
对于 100% 的数据,1≤n≤300,1≤Wi≤105,0≤Pi,j≤105。
实现代码:
cpp
#include<bits/stdc++.h>
using namespace std;
struct edge{
int from,to,len;
const bool operator < (edge b) const{
return this->len>b.len;
}
}e[100000];
priority_queue <edge> que;
int n,a[302][302],w[301],fa[302],ans;
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
int mst(){
while(!que.empty()){
edge p=que.top();
if(find(p.from)!=find(p.to))
{
fa[fa[p.to]]=fa[p.from];
ans+=p.len;
}
que.pop();
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
if(i!=j)que.push((edge){i,j,a[i][j]});
}
}
n++;
for(int i=1;i<n;i++){
que.push((edge){i,n,w[i]});
que.push((edge){n,i,w[i]});
}
for(int i=1;i<=n;i++)fa[i]=i;
printf("%d",mst());
return 0;
}
P1967 [NOIP 2013 提高组] 货车运输
题目背景
NOIP2013 提高组 D1T3
题目描述
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入格式
第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行三个整数 x,y,z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。
注意:x=y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x,y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,保证 x=y。
输出格式
共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 −1。
输入输出样例
输入 #1复制
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出 #1复制
3
-1
3
说明/提示
对于 30% 的数据,1≤n<1000,1≤m<10,000,1≤q<1000;
对于 60% 的数据,1≤n<1000,1≤m<5×104,1≤q<1000;
对于 100% 的数据,1≤n<104,1≤m<5×104,1≤q<3×104,0≤z≤105。
实现代码:
cpp
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define MAXN 10005
#define INF 999999999
using namespace std;
struct Edge1{
int x,y,dis;
}edge1[50005];
struct Edge2{
int to,next,w;
}edge2[100005];
int cnt,n,m,head[MAXN],deep[MAXN],f[MAXN],fa[MAXN][21],w[MAXN][21];
bool vis[MAXN];
void addedge(int from, int to, int w){
edge2[++cnt].next=head[from];
edge2[cnt].to=to;
edge2[cnt].w=w;
head[from]=cnt;
return ;
}
bool CMP(Edge1 x, Edge1 y){
return x.dis>y.dis;
}
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void kruskal(){
sort(edge1+1, edge1+m+1, CMP);
for(int i=1; i<=n; i++)
f[i]=i;
for(int i=1; i<=m; i++)
if(find(edge1[i].x)!=find(edge1[i].y)){
f[find(edge1[i].x)]=find(edge1[i].y);
addedge(edge1[i].x, edge1[i].y, edge1[i].dis);
addedge(edge1[i].y, edge1[i].x, edge1[i].dis);
}
return ;
}
void dfs(int node){
vis[node]=true;
for(int i=head[node]; i; i=edge2[i].next){
int to=edge2[i].to;
if(vis[to]) continue;
deep[to]=deep[node]+1;
fa[to][0]=node;
w[to][0]=edge2[i].w;
dfs(to);
}
return ;
}
int lca(int x, int y)
{
if(find(x)!=find(y)) return -1;
int ans=INF;
if(deep[x]>deep[y]) swap(x,y);
for(int i=20; i>=0; i--)
if(deep[fa[y][i]]>=deep[x]){
ans=min(ans, w[y][i]);
y=fa[y][i];
}
if(x==y) return ans;
for(int i=20; i>=0; i--)
if(fa[x][i]!=fa[y][i]){
ans=min(ans, min(w[x][i], w[y][i]));
x=fa[x][i];
y=fa[y][i];
}
ans=min(ans, min(w[x][0], w[y][0]));
return ans;
}
int main()
{
int x,y,z,q;
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++){
scanf("%d%d%d",&x,&y,&z);
edge1[i].x=x;
edge1[i].y=y;
edge1[i].dis=z;
}
kruskal();
for(int i=1; i<=n; i++)
if(!vis[i]){
deep[i]=1;
dfs(i);
fa[i][0]=i;
w[i][0]=INF;
}
for(int i=1; i<=20; i++)
for(int j=1; j<=n; j++){
fa[j][i]=fa[fa[j][i-1]][i-1];
w[j][i]=min(w[j][i-1], w[fa[j][i-1]][i-1]);
}
scanf("%d",&q);
for(int i=1; i<=q; i++){
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
P2700 逐个击破
题目背景
三大战役的平津战场上,傅作义集团在以北平、天津为中心,东起唐山西至张家口的铁路线上摆起了一字长蛇阵,并企图在溃败时从海上南逃或向西逃窜。为了就地歼敌不让其逃走,指挥官制定了先切断敌人东西两头退路然后再逐个歼灭敌人的战略方针。秉承伟大军事家的战略思想,作为一个有智慧的军长你,遇到了一个类似的战场局面。
题目描述
现在有 N 个城市,其中 K 个被敌方军团占领了,N 个城市间有 N−1 条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你 K 个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这 K 个敌方军团互相隔离开,以便第二步逐个击破敌人。
输入格式
第一行包含两个正整数 N 和 K。
第二行包含 K 个整数,表示哪些城市被敌军占领。
接下来 N−1 行,每行包含三个正整数 a,b,c,表示从 a 城市到 b 城市有一条公路,以及破坏的代价 c。城市的编号从 0 开始。
输出格式
输出一行一个整数,表示最少花费的代价。
输入输出样例
输入 #1复制
5 3
1 2 4
1 0 4
1 3 8
2 1 1
2 4 3
输出 #1复制
4
说明/提示
对于 10% 的数据,N≤10。
对于 100% 的数据,2≤N≤105,2≤K≤N,1≤c≤106。
实现代码:
cpp
#include<bits/stdc++.h>
#define IL inline
#define RI register int
IL void in(int &x){
int f=1;x=0;char s=getchar();
while(s>'9' or s<'0'){if(s=='-')f=-1;s=getchar();}
while(s>='0' and s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,k,f[100008],tot;
bool init[1000008];
long long ans;
struct cod{int u,v,w;}edge[100008];
IL int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
IL bool ccp(const cod&a,const cod&b){return a.w>b.w;}
int main(void){
in(n),in(k);
for(RI i=1;i<=n;i++)f[i]=i;
for(RI i=1,x;i<=k;i++)in(x),init[x]=true;
for(RI i=1;i<=n-1;i++)
in(edge[i].u),in(edge[i].v),in(edge[i].w),ans+=edge[i].w;
std::sort(edge+1,edge+n,ccp);
for(RI i=1;i<=n-1;i++){
int u=edge[i].u,v=edge[i].v,w=edge[i].w;
int fu=find(u),fv=find(v);
if(init[fu] and init[fv])continue;
f[fu]=fv;
ans-=w;
if(init[fu])init[fv]=true;
else if(init[fv])init[fu]=true;
}
printf("%lld",ans);
}
P3623 [APIO2008] 免费道路
题目描述
新亚(New Asia)王国有 N 个村庄,由 M 条道路连接。其中一些道路是鹅卵石路,而其它道路是水泥路。保持道路免费运行需要一大笔费用,并且看上去王国不可能保持所有道路免费。为此亟待制定一个新的道路维护计划。
国王已决定保持尽可能少的道路免费,但是两个不同的村庄之间都应该有且仅有一条均由免费道路组成的路径连接。同时,虽然水泥路更适合现代交通的需要,但国王也认为走在鹅卵石路上是一件有趣的事情。所以,国王决定保持刚好 K 条鹅卵石路免费。
举例来说,假定新亚王国的村庄和道路如图 3(a) 所示。如果国王希望保持两条鹅卵石路免费,那么可以如图 3(b) 中那样保持道路 (1,2),(2,3),(3,4) 和 (3,5) 免费。该方案满足了国王的要求,因为:
- 两个村庄之间都有一条由免费道路组成的路径。
- 免费的道路已尽可能少。
- 方案中刚好有两条鹅卵石道路 (2,3) 和 (3,4)。

图 3:(a) 新亚王国中村庄和道路的一个示例。实线标注的是水泥路,虚线标注的是鹅卵石路。(b) 一个保持两条鹅卵石路免费的维护方案。图中仅标出了免费道路。
给定一个关于新亚王国村庄和道路的数目以及国王决定保持免费的鹅卵石道路数目,写一个程序确定是否存在一个道路维护计划以满足国王的要求,如果存在则任意输出一个方案。
输入格式
输入第一行包含三个由空格隔开的整数:
N,村庄的数目 (1≤N≤2×104);
M,道路的数目 (1≤M≤105);
K,国王希望保持免费的鹅卵石道路数目 (0≤K≤N−1)。
此后 M 行述了新亚王国的道路,编号分别为 1 到 M。第 (i+1) 行述了第 i 条道路的情况。用 3 个由空格隔开的整数述:
ui 和 vi,为第 i 条道路连接的两个村庄的编号,村庄编号为 1 到 N;
ci,表示第 i 条道路的类型。ci=0 表示第 i 条道路是鹅卵石路,ci=1 表示第 i 条道路是水泥路。
输入数据保证一对村庄之间至多有一条道路连接。
输出格式
如果满足国王要求的道路维护方案不存在,你的程序应该在输出第一行打印 no solution。 否则,你的程序应该输出一个符合要求的道路维护方案,也就是保持免费的道路列表。按照输入中给定的那样输出免费的道路。如果有多种合法方案,你可以任意输出一种。
输入输出样例
输入 #1复制
5 7 2
1 3 0
4 5 1
3 2 0
5 3 1
4 3 0
1 2 1
4 2 1
输出 #1复制
3 2 0
4 3 0
5 3 1
1 2 1
说明/提示
数据范围
对于 100% 的数据。保证 1≤N≤2×104,1≤M≤105,0≤K≤N−1。
实现代码:
cpp
#include <iostream>
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
struct edge{
int u,v,o;
}e[150000];
int fa[200000],n,m,k,tot,tot1,tot2,res,ans[200000][3];
void init(){
for(int i=1;i<=n;i++) fa[i]=i;
}
int get(int x){
return x==fa[x]?x:fa[x]=get(fa[x]);
}
bool same(int x,int y){
return get(x)==get(y);
}
void merge(int x,int y){
int fax=get(x),fay=get(y);
fa[fax]=fay;
}
int main(){
n=read(),m=read(),k=read();
init();
for(int i=1;i<=m;i++){
e[i].u=read();
e[i].v=read();
e[i].o=read();
if(e[i].o) if(!same(e[i].u,e[i].v)) merge(e[i].u,e[i].v),res++;
}
for(int i=1;i<=m;i++){
if(!e[i].o){
int x=e[i].u,y=e[i].v;
if(!same(x,y)) merge(x,y),ans[++tot][0]=x,ans[tot][1]=y,ans[tot][2]=0,res++;
if(tot1>k){
cout<<"no solution";
return 0;
}
}
}
tot1=tot;
if(res!=n-1){
cout<<"no solution";
return 0;
}
init();
for(int i=1;i<=m;i++){
if(!e[i].o){
int x=e[i].u,y=e[i].v;
if(!same(x,y)) merge(x,y);
}
}
for(int i=1;i<=m;i++){
if(e[i].o){
int x=e[i].u,y=e[i].v;
if(!same(x,y)) merge(x,y),ans[++tot][0]=e[i].u,ans[tot][1]=e[i].v,ans[tot][2]=1,tot2++;
if(tot2>n-1-k){
cout<<"no solution";
return 0;
}
}
}
init();
for(int i=1;i<=tot;i++){
int x=ans[i][0],y=ans[i][1];
merge(x,y);
}
for(int i=1;i<=m;i++){
int x=e[i].u,y=e[i].v;
if(e[i].o&&tot2<n-1-k&&!same(x,y)){
merge(x,y);
ans[++tot][0]=x,ans[tot][1]=y,ans[tot][2]=1;
tot2++;
}
if(!e[i].o&&tot1<k&&!same(x,y)){
merge(x,y);
ans[++tot][0]=x,ans[tot][1]=y,ans[tot][2]=0;
tot1++;
}
}
for(int i=1;i<n;i++) printf("%d %d %d\n",ans[i][0],ans[i][1],ans[i][2]);
return 0;
}