一本通:
提高篇:
图论:
最小生成树:
1486 【例题1】黑暗城堡
求最短路径生成树,用最短路算法求出最短路后,找到最后dis数组,遍历每条边上找到对应成立dis[]的度数(个数),再用乘法原理求出结果。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//const ll mod = (1<<31)-1;
#define mod 2147483647LL
const int N = 1005;
const int INF = 1e9;
const int M = 2000005;
int n,m,cnt;
int head[N],ver[M],nex[M],edge[M];
int vis[N];ll dis[N],ksum[N];
void add(int a,int b,int w){
ver[++cnt] = b;
nex[cnt] = head[a];
edge[cnt] = w;
head[a] = cnt;
}
void solve(){
for(int i=0;i<=n;i++)
dis[i] =INF;
priority_queue<pair<int,int> > q;
q.push(make_pair(0,1)),dis[1]=0;
while(q.size()){
int u =q.top().second;
q.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int i=head[u];~i;i = nex[i]){
int v=ver[i],w =edge[i];
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push(make_pair(-dis[v],v));
}
}
}
}
int work(){
ll ans= 1;
for(int i=1;i<=n;i++){
for(int j=head[i];~j;j=nex[j])
if(dis[i]+edge[j]==dis[ver[j]])
ksum[ver[j]]++; //这是入度 ,表示这个点可以由多少最短路上的点得到
}
for(int i=2;i<=n;i++)
ans = ans*ksum[i] %mod;//根据乘法原理累计
printf("%lld",ans);
}
int main(){
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for(int i = 1,x,y,w;i <= m;i++){
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
add(y,x,w);
}
solve();
work();
return 0;
}
1487 【例 2】北极通讯网络
求最小生成树的第k长边
//求最小生成树的第k长边
#include<bits/stdc++.h>
using namespace std;
const int N = 250100;
int n,k,m,cnt;
double ans[N];
int pre[N],x[N],y[N];
struct EG{
int from,to;
double w;
EG(){}
EG(int from,int to,double w) : from(from),to(to),w(w){}
bool operator < (const EG &b) const{
return (w - b.w) < 1e-9;
};
}e[N];
void read(){
scanf("%d%d",&n,&k);
for(int i = 1,a,b;i <= n;i++){
scanf("%d%d",x+i,y+i);
pre[i] = i;
}
}
int find(int x){
return x==pre[x]?x:pre[x]=find(pre[x]);
}
void un(int x,int y){
int fx = find(x);
int fy = find(y);
if(fx != fy) pre[fx] = pre[fy];
}
double dis(int a,int b){
return sqrt( (x[a]-x[b])*(x[a]-x[b]) + (y[a]-y[b]) * (y[a]-y[b]) );
}
void solve(){
for(int i = 1; i <= n; i++){
for(int j=1+i;j <= n; j ++){
e[++m] = EG(i,j,dis(i,j));
}
}
sort(e+1,e+m+1);
for(int i=1;i <= m; i++){
int fx = find(e[i].from),fy = find(e[i].to);
if(fx==fy) continue;
un(fx,fy);
ans[++cnt] = e[i].w;
//cout<<ans[cnt-1]<<endl;
if(cnt == n) break;
}
printf("%.2f",ans[cnt-k+1]);
}
int main(){
read();
solve();
return 0;
}
1488 新的开始
求最小生成树
#include<bits/stdc++.h>
using namespace std;
const int N = 500,INF = 0x3f3f3f3f;
int n,ans,minn;
int c[N],w[N][N],dis[N],vis[N];
void read(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&w[0][i]),w[i][0] = w[0][i];
for(int i=1;i <= n;i++)
for(int j=1;j <= n;j++)
scanf("%d",&w[i][j]);
}
void prim(){//堆优化的prim
for(int i=1;i<=n;i++) dis[i] = INF;
priority_queue<pair<int,int> > q;
dis[0] = 0;
q.push(make_pair(0,0));
int cnt = 0,ans=0;
while(q.size() && cnt <=n){
int d = -q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++;
ans += d;
vis[u] = 1;
for(int i=1;i<=n;i++){
if(w[u][i] < dis[i]){
dis[i] = w[u][i];
q.push(make_pair(-dis[i],i));
}
}
}
printf("%d",ans);
}
/*void prim(){
for(int i=0;i<=n;i++) dis[i] = INF;
dis[0] = 0,ans = 0;
int k;
for(int i=0;i<=n;i++){
minn = INF;
for(int j = 0;j<=n;j++)
if(!vis[j] && minn > dis[j]) minn = dis[j],k=j;
vis[k] = 1;
ans += dis[k];
for(int j=0;j<=n;j++)
if(!vis[j] && dis[j] > w[k][j]) dis[j] = w[k][j];
}
printf("%d",ans);
}*/
int main(){
read();
prim();
return 0;
}
1489:构造完全图
因为没有确切的边,我们只要把最小生成树上的点两两相连,这些非树边的权值是这两个点权值+1。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7,INF = 0x3f3f3f3f;
typedef long long ll;
ll ans;
ll n,pre[N],num[N];
struct E{
ll from,to,w;
E(){}
E(ll from,ll to,ll w):from(from),to(to),w(w){}
bool operator < (const E &b) const{
return w<b.w;
}
}e[N];
ll find(int x){
return x==pre[x]?x:pre[x]=find(pre[x]);
}
void read(){
scanf("%lld",&n);
for(int i=1;i<n;i++)
scanf("%lld%lld%lld",&e[i].from,&e[i].to,&e[i].w),ans+=e[i].w;
for(int i=1;i<=n;i++)
pre[i] = i,num[i] = 1;
}
void solve(){
sort(e+1,e+n);
for(int i=1;i<n;i++){
int r1 = find(e[i].from);
int r2 = find(e[i].to);
if(r1!=r2){
ans += (num[r1]*num[r2]-1)*(e[i].w+1);
pre[r1] = r2;
num[r2] += num[r1];
}
}
printf("%lld\n",ans);
}
int main(){
read();
solve();
return 0;
}
1490:秘密的牛奶运输
求次小生成树,次小生成树讲解
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+7,M = 3e5 + 7,MM = 3e5+7;
const ll INF = 0x7ffffffffff;;
int n,m;
ll sum;
int cnt,head[MM],ver[MM],nex[MM],edge[MM];
int tree[MM],pre[N],ppre[N][23],depth[N],lg[N];
ll maxf[N][23],minf[N][23];//这两个要和ans 作比较,所以用ll
struct E{
int from,to,w;
E(){}
E(int from,int to,int w) : from(from),to(to),w(w){}
bool operator < (const E &b)const{
return w < b.w;
}
}e[M];
void add(int x,int y,int w){
ver[++cnt] = y;
nex[cnt] = head[x];
edge[cnt] = w;
head[x] = cnt;
}
int find(int x){
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1;i <= m;i ++)
scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].w);
for(int i=0; i < N; i++)
pre[i] = i;
}
void work1(){
sort(e+1,e+m+1);
for(int i=1;i <= m;i ++){
int x = e[i].from,y = e[i].to,w = e[i].w;
int fx = find(x), fy = find(y);
if(fx != fy){
pre[fx] = fy;
sum += w;
add(x,y,w);
add(y,x,w);
tree[i] = 1;
}
}
}
void dfs(int f,int fa,int w){
depth[f] = depth[fa] + 1;
ppre[f][0] = fa;
minf[f][0] = -INF;
maxf[f][0] = w;
for(int i=1; (1<<i) <= depth[f];i++){
ppre[f][i] = ppre[ppre[f][i-1]][i-1];
maxf[f][i] = max(maxf[f][i-1],maxf[ppre[f][i-1]][i-1]);
minf[f][i] = max(minf[f][i-1],minf[ppre[f][i-1]][i-1]);//这里分清次小关系
if(maxf[f][i-1] > maxf[ppre[f][i-1]][i-1]) minf[f][i] = max(minf[f][i],maxf[ppre[f][i-1]][i-1]);
else if(maxf[f][i-1] < maxf[ppre[f][i-1]][i-1]) minf[f][i] = max(minf[f][i],maxf[f][i-1]);
}
for(int i=head[f]; i ; i=nex[i]){
int y = ver[i],w = edge[i];
if(y != fa){
dfs(y,f,w);
}
}
}
int lca(int x,int y){
if(depth[x] < depth[y]) swap(x,y);
while(depth[x] > depth[y])
x = ppre[x][lg[depth[x]-depth[y]] -1];
if(x==y) return x;
for(int i = lg[depth[x]]-1; i>=0; i--){
if(ppre[x][i] != ppre[y][i])
x= ppre[x][i],y = ppre[y][i];
}
return ppre[x][0];
}
ll qmax(int x,int y,int maxx){
ll ans = -INF;
for(int i = lg[depth[x]]-1;i>=0;i--){
if(depth[ppre[x][i]]>=depth[y]){
if(maxx != maxf[x][i]) ans = max(ans,maxf[x][i]);
else ans = max(ans,minf[x][i]);
x = ppre[x][i];
}
}
return ans;
}
void work2(){
for(int i=1;i <= n;i++)
lg[i] = lg[i-1] + (1<<lg[i-1]==i);
dfs(1,0,0);
ll ans = INF;
for(int i = 1;i <= m;i++){
if(tree[i]) continue;
int x = e[i].from,y = e[i].to,w = e[i].w;
int lc = lca(x,y);
ll maxx = qmax(x,lc,w);
ll maxv = qmax(y,lc,w);
ans = min(ans,sum-max(maxx,maxv)+w);//找到到公共父亲节点路径中最大的那条,替换到非树边。
}
printf("%lld\n",ans);
}
int main(){
read();
work1();
work2();
return 0;
}
1491:Tree
听说数据有问题还是咋地
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+7,M = 2e5+7;
typedef long long ll;
struct E{
int from,to,w,col;
bool operator < (const E &b) const{
if(w==b.w) return col < b.col;//这是一个坑点,排序的时候如果值相等,要优先考虑白的
return w < b.w;
}
}e[M];
int zong,n,m,need;
int pre[N];
ll ans,res;
void read(){
scanf("%d%d%d",&n,&m,&need);
for(int i=1;i <= m;i++){
scanf("%d%d%d%d",&e[i].from,&e[i].to,&e[i].w,&e[i].col);
}
}
int find(int x){
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void kuskr(int mid){
for(int i=0;i<=n;i++)
pre[i] = i;
ans = zong = 0;
for(int i=1;i<=m;i++)
if(!e[i].col) e[i].w += mid;
sort(e+1,e+m+1);
for(int i=1;i <= m;i++){
int fx = find(e[i].from);
int fy = find(e[i].to);
if(fx != fy){
pre[fx] = fy;
ans += e[i].w;
if(!e[i].col) zong ++;
}
}
for(int i=1;i<=m;i++)
if(!e[i].col) e[i].w -= mid;
}
void solve(){
int l =-100,r=100;
while(l <= r){
int mid = (l+r)>>1;
kuskr(mid);
if(zong >= need) l=mid+1,res = ans - need*mid;//这个点我有点不明白,为什么是need而不是zong,比较是树上的,即使最后保证有结果。
else r = mid-1;
}
printf("%lld\n",res);
}
int main(){
read();
solve();
return 0;
}
1492:最小生成树计数
矩阵树后码,这里给出的是暴力求。
#include<bits/stdc++.h>
using namespace std;
const int N = 1100,M = 1e3+7,mod = 31011;
int n,m,tot,cnt;
int ans,sum;
int l[N],r[N],va[N],pre[N];
struct E{
int from,to,w;
bool operator < (const E &b) const{
return w < b.w;
}
}e[M];
int find(int x){
return x==pre[x] ? x: find(pre[x]);
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].w);
}
void dfs(int x,int now,int k){
if(now == r[x]+1){
if(k == va[x]) sum++;
return;
}
int fx = find(e[now].from),fy = find(e[now].to);
if(fx != fy){
pre[fx] = fy;
dfs(x,now+1,k+1);
pre[fx] = fx,pre[fy] = fy;
}
dfs(x,now+1,k);
}
void solve(){
sort(e+1,e+m+1);
for(int i=0;i<=110;i++) pre[i] = i;
for(int i=1;i <= m;i++){
if(e[i].w != e[i-1].w) cnt++,l[cnt]=i,r[cnt-1]=i-1;
int fx = find(e[i].from),fy = find(e[i].to);
if(fx != fy){
pre[fx] = fy;
va[cnt]++; tot++;
}
}
if(tot != n-1){
printf("0");return;
}
r[cnt] = m;
for(int i=0;i<=110;i++) pre[i] = i;
ans = 1;
for(int i=1;i <= cnt; i++){
sum = 0;
dfs(i,l[i],0);
ans = (ans*sum) % mod;
for(int j=l[i];j<=r[i];j++){
int fx = find(e[j].from),fy = find(e[j].to);
if(fx != fy) pre[fx] = fy;
}
}
printf("%d",ans);
}
int main(){
read();
solve();
return 0;
}
1493:次小生成树
参考 1490 秘密的牛奶运输,代码一样。