了解各类生成树之前,先花了半天研究最小生成树和构造他的prim算法。
大体讲prim算法的建树过程类似贪心,每次都选最小边,只不过选的基准在变,不能用传统的贪心来做,prim在贪心的同时要不断更新周围边的权值大小,从而构造出最小生成树。值得一提的是对于传统的prim,这里有一个通过队列优化的prim算法。
//优化后,优先队列nlogn
struct node
{
int v,cap;
bool friend operator < ( node a, node b)
{
return a.cap>b.cap;
}
};
int prim(int cur ,int id)
{
priority_queue<node> q;
while(!q.empty()) q.pop();
node fir;
fir.v=cur,fir.cap=0;
q.push(fir);
while(!q.empty())
{
node tp=q.top();
q.pop();
int u=tp.v;
if(!tree[u])
{
ans+=tp.val;
tree[u]=id;//最小生成树的序号
for(int i=1;i<n;i++)
{
if(!tree[i]&&mp[u][i]!=0&&dis[i]>mp[u][i])
{
pre[i]=u;
dis[i]=mp[u][i];
node temp;
temp.val=dis[i];
temp.v=i;
q.push(temp);
}
}
}
}
return ans;
}
最小生成树之后还有次小生成树,字面意思呗。
相对于构建最小生成树,多了以下步骤(同时要在prim中标记used[i][j],即(i,j)这条边是否用过)
int second_tree()//求次小生成树
{
int res=inf;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j&&!used[i][j])
res=min(res,mst-path[i][j]+g[i][j]);//删除树上权值最大的路径并且加上这条路径其它边
return res;
}
其次,最小限度生成树(也称最小K度生成树),其实就是在根节点连了K个子树后求一个最小生成树。
大致思路是,不管根节点,求根节点除外的森林中的每个最小生成树,然后通过加边比较删最大边再换边的一系列操作是各个生成树与根节点相连,构成最小k度生成树。
A POJ 1639 Picnic Planning
题目大意:公园只有三个停车场,给出很多人之间或与公园的距离,相遇后可挤在同一辆车生前往往下个目的地,问到公园最少开车距离。
很显然,求最小三度生成树
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#define INF 0x3f3f3f
using namespace std;
const int N=30;
struct node
{
int v,cap;
bool friend operator < ( node a, node b)
{
return a.cap>b.cap;
}
};
map<string,int> mp;
int g[N][N],dis[N],clo[N],pre[N],fst[N],max_side[N];
int n,m,k;
int Prim(int cur,int id)
{
int ans=0;
priority_queue<node>q;
node fir;
fir.v=cur,fir.cap=0;
q.push(fir);
while(!q.empty())
{
node tp=q.top();
q.pop();
int u=tp.v;
if(!clo[u])
{
ans+=tp.cap;
clo[u]=id;
for(int i=1;i<n;i++)
{
if(!clo[i]&&g[u][i]!=0&&dis[i]>g[u][i])
{
pre[i]=u;
dis[i]=g[u][i];
node temp;
temp.cap=dis[i];
temp.v=i;
q.push(temp);
}
}
}
}
return ans;
}
void update(int cur,int last,int maxside)
{
max_side[cur]=maxside>g[cur][last]?maxside:g[cur][last];
for(int i=1; i<n; i++)
if(i!=last && g[cur][i]!=0 && (pre[cur]==i || pre[i]==cur))
{
update(i,cur,max_side[cur]);
}
}
void solve()
{
for(int i=0; i<n; i++)
{
dis[i]=INF;
clo[i]=pre[i]=fst[i]=0;
}
int res=0,cnt=0;
for(int i=1; i<n; i++)
{
if(!clo[i])
res+=Prim(i,++cnt);
}
for(int i=1; i<n; i++) //不断更新与0连接的各树最小边
{
int id=clo[i];
if(g[0][i]!=0&&(!fst[id]||g[0][i]<g[0][fst[id]]))
fst[id]=i; //id树与0连接的最小边号
}
for(int i=1; i<=cnt; i++)
{
res+=g[0][fst[i]];
g[0][fst[i]]=g[fst[i]][0]=0;
update(fst[i],0,0);
}
k=k-cnt;
while(k--)
{
int tmp=0;
for(int i=1; i<n; i++)
{
if(g[0][i]!=0&&(tmp==0||max_side[tmp]-g[0][tmp]<max_side[i]-g[0][i]))
tmp=i;
}
if(max_side[tmp]<=g[0][tmp])
break;
res=res-max_side[tmp]+g[0][tmp];
g[0][tmp]=g[tmp][0]=0;
int p=0;
for(int i=tmp; pre[i]!=0; i=pre[i])
if(p==0||g[p][pre[p]]<g[i][pre[i]])
p=i;
pre[p]=0;
update(tmp,0,0);
}
printf("Total miles driven: %d\n",res);
}
int main()
{
freopen("ttt.txt","r",stdin);
char s1[15],s2[15];
cin>>m;
mp.clear();
mp["Park"]=0;
n=1;
while(m--)
{
int d;
cin>>s1>>s2>>d;
if(mp.count(s1)==0)
{
mp[s1]=n++;
}
if(mp.count(s2)==0)
{
mp[s2]=n++;
}
int u=mp[s1],v=mp[s2];
if(g[u][v]==0||g[u][v]>d)
{
g[u][v]=d;
g[v][u]=d;
}
}
cin>>k;
solve();
return 0;
}
之后,最优比率生成树。了解这个之前,先了解01规划。(网上很多)
经典思想——二分即可.
建立在01规划基础上再加一个建树过程,即就是利用树上的各类权值进行01规划问题(据说有种迭代方法没还没来得及看)
C POJ 2728 Desert King
题意很明显,就是01规划
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f
#define maxn 1005
int n;
int x[maxn], y[maxn], h[maxn];
double v[maxn][maxn], c[maxn][maxn];
int vis[maxn];
double w[maxn];
double prim(double r)
{
int next;
double min,mincost=0.0;
memset(vis,0,sizeof(vis));
for(int i=2;i<=n;i++)
{
w[i]=v[1][i]-r*c[1][i];
}
vis[1]=1;
for(int i=1;i<n;i++)
{
min=INF;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&min>w[j])
{
min=w[j];
next=j;
}
}
mincost+=min;
vis[next]=1;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&w[j]>v[next][j]-r*c[next][j])
{
w[j]=v[next][j]-r*c[next][j];
}
}
}
return mincost;
}
int main()
{
//freopen("ttt.txt","r",stdin);
while(~scanf("%d",&n)&&n)
{
for(int i=1;i<=n;i++)
scanf("%d%d%d",&x[i],&y[i],&h[i]);
double maxnv=-INF,maxnc=-INF,minnv=INF,minnc=INF;
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
c[i][j]=c[j][i]=sqrt((double)(x[i]-x[j])*(x[i]-x[j])+(double)(y[i]-y[j])*(y[i]-y[j]));
v[i][j]=v[j][i]=abs((double)h[i]-h[j]);
maxnv=max(maxnv,v[i][j]);
minnv=min(minnv,v[i][j]);
maxnc=max(maxnc,c[i][j]);
minnc=min(minnc,c[i][j]);
}
}
double l=minnv/maxnc;
double r=maxnv/minnc;
double mid;
while(r-l>1e-6)
{
mid=(l+r)/2.0;
if(prim(mid)>1e-8f) l=mid;
else r=mid;
}
printf("%.3f\n",mid);
}
return 0;
}
特别注意这道题的数据类型和范围。
然后,最优比率环(没做)。
然后,最小树形图(没做)。
然后,然后就到二分匹配了。
二分图匹配衍生出来的问题大都同一解法,关键在于理解二分匹配
这里有几篇非常赞的文章了解一下
讲的一些概念的区分和关系
二分图最大匹配的König定理及其证明
二分图大讲堂
一般裸二分题目套路都是差不多(同一个模板??),重点是理解二分图中每一个小概念(尤其是增广路)和一下核心代码(匈牙利算法)。
int find(int cur)
{
for(int i=1;i<=m;i++)
{
if(!vis[i]&&map[cur][i])
{
vis[i]=true;
if(pre[i]==0||find(pre[i]))//如果y集合中的v元素没有匹配或者
//是v已经匹配,但是从cy[v]中能够找到一条增广路
{
pre[i]=cur;
return 1;
}
}
}
return 0;
}
遍历
for(int i=1;i<=m;i++)
{
memset(vis,false,sizeof(vis));
if(find(i)) sum++;
}
K POJ 1486 Sorting Slides
很有意思,只要想到合适的构图方法,就很容易想到用二分图匹配来做
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#define MAXN 510
using namespace std;
bool vis[MAXN];
int map[MAXN][MAXN],m,pre[MAXN];
int n;
struct tag{
int xmin, xmax, ymin , ymax;
int id;
}t[MAXN];
struct Node{
int x,y;
int id;
}node[MAXN];
int find(int cur)
{
for(int i=1;i<=n;++i)
{
if(!vis[i]&&map[cur][i])
{
vis[i] = true;
if(pre[i] == 0 || find(pre[i]))
{
pre[i] = cur;
return 1;
}
}
}
return 0;
}
int main()
{
//freopen("ttt.txt","r",stdin);
int cas=0;
while(scanf("%d",&n)!=EOF&&n)
{
printf("Heap %d\n",++cas);
memset(map,0,sizeof(map));
memset(pre, 0, sizeof(pre));
memset(t,0,sizeof(t));
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&t[i].xmin,&t[i].xmax,&t[i].ymin,&t[i].ymax);
t[i].id=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d%d",&node[i].x,&node[i].y);
node[i].id=i;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(node[i].x<=t[j].xmax&&node[i].x>=t[j].xmin&&node[i].y<=t[j].ymax&&node[i].y>=t[j].ymin)
{
map[i][j]=1;
}
}
}
int sum = 0;
for(int i = 1; i <=n; i++)
{
memset(vis, false, sizeof(vis));
if(find(i)) sum++;
}
int t,flag=0;
for(int i=1;i<=n;i++)
{
if(!pre[i])
continue;
t=pre[i];
pre[i]=0;
map[t][i]=0;
memset(vis,0,sizeof(vis));
if(find(t)==0)
{
pre[i]=t;
if(!flag)
printf("(%c,%d)",i-1+'A',pre[i]);
else
printf(" (%c,%d)",i-1+'A',pre[i]);
flag++;
}
map[t][i]=1;
}
if(flag==0)
printf("none");
printf("\n\n");
}
return 0;
}
接下来是Ramsey定理(还没看到)