图论基础之前先掌握构图基础
- 邻接矩阵(二维数组即可map[maxn][maxn])
- 邻接表
- 链式前向星(比较常用)
- ······(还没了解过)
这里重点说一下链式前向星:
链表嘛,结构体存边就行
struct Edge{
int next;//next表示与第i条边同起点的下一条边的存储位置
int to;//edge[i].to表示第i条边的终点
int w;//edge[i].w为边权值
}edge[maxn];
加边
int cnt=0,head[maxn];//head[maxn]用来表示以i为起点的第一条边存储的位置
//cur表示边数
void add(int u,int v,int w)
{
edge[cnt].w=w;
edge[cnt].to=u;
edge[cnt].next=head[u];
head[u]=cnt++;
}
遍历
for(int i=head[u];~i;i=edge[i].next)
{
//............................
}
这样就建成一张图啦!
建图能干嘛,建图可以干的事多了
这里先聊聊“并查集”
什么是并查集?
字面意思。。(通俗点按某种关系合并到一块的集合吧)
通过不断的研究,人们从集合的合并到树的合并等等等等终于能够用一种名为“路径压缩”的方法,将复杂度降为O(1)。tql~(如有不对请指正)。
上题来看
A - How Many Answers Are Wrong HDU - 3038
大意是说告诉一段区间的和,每次判断当前语句是否与之前的情况矛盾,记录有几个矛盾值。
很明显在没有熟练并查集时,我想的是用前缀和来储存进而进行判断。但琢磨了一会发现真假关系并不好联系到一块。只能现学现用了。
思路:sum[x]表示区间[x,f[x]]的和,这个可以在路径压缩的时候更新,对于一组数据(u,v,w),令r1=Find(u),r2=Find(v),于是若r1==r2,此时u,v就有了相同的参考点,而sum[u]为区间[u,r1(r2)]的和,sum[v]为区间[v,r2(r1)]的和,于是只需判断w==sum[v]-sum[u]即可;若r1
#include<cstdio>
#include<cstring>
#define maxn 200010
int sum[maxn];
int n,m,l,r,w,par[maxn];
int find(int x)
{
if(par[x]==x) return x;
int tmp=par[x];
par[x]=find(par[x]);
sum[x]+=sum[tmp];
return par[x];
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
int ans=0;
for(int i=0;i<=n;i++)
{
par[i]=i;
sum[i]=0;
}
while(m--)
{
scanf("%d%d%d",&l,&r,&w);
l-=1;
int rootl=find(l);
int rootr=find(r);
if(rootl!=rootr)
{
par[rootr]=rootl;
sum[rootr]=w+sum[l]-sum[r];
}
else if(sum[r]-sum[l]!=w)
{
ans++;
}
}
printf("%d\n",ans);
}
return 0;
}
B - 食物链 POJ - 1182
极其经典的并查集应用,也叫种族并查集。
具体做法依旧是记录每种动物的关系
思路:把确定了相对关系的节点放在同一棵树中
每个节点对应的 r[]值记录他与根节点的关系:
0:同类,
1:被父亲节点吃,
2: 吃父亲节点
关于合并时r[]值的更新:
如果 d == 1则 x和y 是同类 ,那么 y 对 x 的关系是 0
如果 d == 2 则 x 吃了 y, 那么 y 对 x 的关系是 1, x 对 y 的关系是 2.
综上所述 ,无论 d为1 或者是为 2, y 对 x 的关系都是 d-1
定义 :fx 为 x 的根点, fy 为 y 的根节点
合并时,如果把 y 树合并到 x 树中
如何求 fy 对 fx 的r[]关系?
fy 对 y 的关系为 3-r[y]
y 对 x 的关系为 d-1
x 对 fx 的关系为 r[x]
所以 fy 对 fx 的关系是(3-r[y] + d-1 + r[x])%3
代码
#include<cstdio>
#include<cstring>
#define maxn 50010
int par[maxn],r[maxn],n,k,ob,x,y;
int find(int x)
{
if(par[x]==x) return x;
int tmp=par[x];
par[x]=find(par[x]);
r[x]=(r[x]+r[tmp])%3;
return par[x];
}
int main()
{
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
par[i]=i;
r[i]=0;
}
int ans=0;
while(k--)
{
scanf("%d%d%d",&ob,&x,&y);
if(x>n||y>n||(ob!=1&&x==y))
{
ans++;
}
else
{
int rootx=find(x);
int rooty=find(y);
if(rootx==rooty)
{
if(ob == 1 && r[x] != r[y]) ans++;
if(ob == 2 && (r[x]+1)%3 != r[y]) ans++;
}
else
{
par[rooty]=rootx;
r[rooty]=r[x]-r[y]+3+(ob-1)%3;
}
}
}
printf("%d\n",ans);
}
浴谷P1525 关押罪犯
这是道很有意思的并查集裸题,虽说是裸题,但实际上题目思考起来还是蛮有趣的。题目大意是监狱里会有许多囚犯如果在同一所监狱两两成仇且有一定愤怒值,问监狱长将囚犯分配到两座监狱以使监狱中的愤怒值最小,求这个最小值。学了并查集的我苦思冥想在最后转换关系那卡住了,直到看到这么一句话“敌人的敌人就是朋友”才恍然大悟,话不多说上代码。
#include<cstdio>
#include<cstring>
#include <algorithm>
using namespace std;
#define maxn 20005
#define mmaxn 100005
int par[maxn],r[maxn],r1,r2,n,m;
struct p{
int a;
int b;
int c;
}ps[mmaxn];
inline bool cmp(p a,p b)
{
return a.c>b.c;
}
void init()
{
for(int i=1;i<=n;i++)
{
par[i]=i;
r[i]=0;
}
}
int find(int x)
{
if(par[x]==x) return x;
par[x]=find(par[x]);
return par[x];
}
void merge(int x,int y)
{
r1=find(x);
r2=find(y);
par[r1]=r2;
}
int check(int x,int y)
{
r1=find(x);
r2=find(y);
if(r1==r2) return true;
return false;
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++)
scanf("%d%d%d",&ps[i].a,&ps[i].b,&ps[i].c);
sort(ps+1,ps+m+1,cmp);
for(int i=1;i<=m+1;i++)
{
if(check(ps[i].a,ps[i].b)) {
printf("%d\n",ps[i].c);
break;
}
else
{
if(!r[ps[i].a]) r[ps[i].a]=ps[i].b;
else merge(r[ps[i].a],ps[i].b);
if(!r[ps[i].b]) r[ps[i].b]=ps[i].a;
else merge(r[ps[i].b],ps[i].a);
}
}
return 0;
}
在题解区很多大神提出了各种方法,二分图匹配,最小生成树,并查集···
(打扰了,连并差集都不咋会)不过通过这道题对并查集也有了更深的认识。
并查集刚开始接触觉得非常棘手,但事实上只有理解并查集的应用理念,然后结合题目仔细推敲题目中所叙述的不同种关系间的转化或者分辨,这样题目做起来会变得顺手许多。
虽说是图论基础,图论基础的基础我先学了老半天,只做了并差集部分的题,后面的一定补上!