The Summer Training Summary(拓扑,基础/带权/种类并查集 )-- the second

The Summer Training Summary-- the second

- 拓扑排序 hdu 1285

问题分析 怎么说呢 拓扑排序 给我的感觉就是 一棵树 从树冠 往下
每次遇到 比他的小的 就把它的入度+1 就往树下 走 然后从树冠 找
入度为0 是就是一个 小树冠 其下拥有若干 小树 小分支的入度为0 意味着到了
小树冠的底部但着并不意味着 这就是最小值 因为 可能还有比这个数的最大入度
还大的数存在

为了满足 不确定的 名次按照 队伍字典序排序
借助于优先队列来实现这个; 首先将 起始入度为0 的队伍加入 这意味着 树冠 (他们的 名词相对来说是靠前的单不能确定他们的具体名词 优先队列巴拉巴拉…)
然后 出队 扫描 分支 入度减一 存在 入度为0 就继续加入优先队列
具体代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
int n,m,map[505][505],in[505];
int main()
{
	while(cin>>n>>m)
    {
    	mem(map);
		mem(in);
		while(m--)
		{
			int a,b;
			cin>>a>>b;
			if(!map[a][b])
			{
				map[a][b]=1;
			in[b]++;
			}
			
		 } 
	  priority_queue<int,vector<int>,greater<int> > q;
	  for(int i=1;i<=n;i++)
	  {
	  	if(!in[i])
	      q.push(i);
	  }
	  bool f=0;
	  while(q.size())
	  {
	  	int top=q.top();
	  	q.pop();
	  	in[top]--;
	  	if(!f)
		  cout<<top;
		else
		cout<<" "<<top;
		f=1;
		for(int i=1;i<=n;i++)
		{
			if(map[top][i])
			{
			in[i]--;
			if(!in[i])
		      q.push(i);	
			}  
		}
	  }
	  cout<<endl;
	}
    return 0;
}

H - 拓扑排序 (2) hdu 3342

问题分析 :
和上个 拓扑排序一样只不过是是反向查询 是否是存在循环结构 如果 存在就意味这有错误
而这在 程序中反映出来就是 查询值等于n;
具体代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
int n,m,map[505][505],in[505];
int main()
{
     while(cin>>n>>m&&n&&m)
	 {     mem(map);
		   mem(in);
	 	for(int i=0;i<m;i++)
		 {
		 	int a,b;
		 	cin>>a>>b;
		 	if(!map[a][b])
		  {
		  	map[a][b]=1;
		  	in[b]++;
		  }
		  }
		   
		  int flag=0;
		  int i,j;
		  for( i=0;i<n;i++)
		  {
		  	for( j=0;j<n;j++)
		  	{
		  		if(!in[j])
		  		{
		  			in[j]--;
		  			for(int k=0;k<n;k++)
		  			{
		  				if(map[j][k])
					     in[k]--;
					}
					break;
				  }
			  }
			  if(j==n)
			  {
			  	flag=1;
			  break;	
			  } 
		  }
		  if(flag)
		  cout<<"NO\n";
		  else
		  cout<<"YES\n";
		  
	   }  
    return 0;
}

L - 拓扑排序 HDU 2647

就是拓扑排序 中间 加了一个 一888作为基值
代码如下:

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#define mem(a)  memset(a,0,sizeof(a))
using namespace std;
int main()
{
 int n,m;
 while(cin>>n>>m)
{
 vector<int> map[20001];
 int in[10001],mo[10001];
 for(int i=1;i<=n;i++)
 map[i].clear();
 mem(in);
 queue<int> q;
 while(m--)
 {
 	int a,b;
 	cin>>a>>b;
 	map[b].push_back(a);
 	in[a]++;	   
 }
 for(int i=1;i<=n;i++)
    if(!in[i])
    {
    	q.push(i);
    	mo[i]=888;
    }
    
    int tot=0;
    int sum=0;
    while(!q.empty())
    {
    	int x=q.front();
    	sum+=mo[x];
    	tot++;
    	q.pop();
    	for(int i=0;i<map[x].size();i++)
 	   {
    		in[map[x][i]]--;
    		mo[map[x][i]]=mo[x]+1;
    		if(!in[map[x][i]])
    		q.push(map[x][i]);
 	   }    	   
    }
    if(tot<n)
    cout<<-1<<endl;
    else
    cout<<sum<<endl;	   
 }
 return 0;
}

I - 并查集 poj 1456

问题分析:
这题有多种解法 我用是结构体 加优先队列 (结构体可以换成pair)
建立 天数 与 价格的结构体
然后按照 价格进行排序
开一个 cnt记录 所在天数(每天限制卖1中水果)
从头扫到尾部 在这期间
因为可能存在同一天但是确有多种选择
保持cnt<=结构体的.day
如果大于了就 检查 队首是否小于 新的 小就入队

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
struct node
{
	int vla;
	int d;
}ans[10005];
int cmp(node a,node b)
{
	return a.d<b.d;
}
int main()
{ 
int n;
while(cin>>n)
{
	for(int i=0;i<n;i++)
		cin>>ans[i].vla>>ans[i].d;
   sort(ans,ans+n,cmp);
   long long sum=0;
   int cnt=1;
   priority_queue<int,vector<int>,greater<int> >q;
   for(int i=0;i<n;i++)
   {
   	   if(cnt<=ans[i].d)
   	   {
   	   	q.push(ans[i].vla);
   	   	cnt++;
		  }
		  else if(q.top()<ans[i].vla)
		  {
		  	q.pop();
		  	q.push(ans[i].vla);
		  }
   }
   while(q.size())
   {
   	sum+=q.top();
   	q.pop();
   }
   cout<<sum<<endl;
}
    return 0;
}

J - 并查集 poj 1611

问题分析:并查集模板 直接套 然后求0点下的子节点个数
代码如下:

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#define mem(a)  memset(a,0,sizeof(a))
using namespace std;
int sum[30005],r[30005];
int find(int a)
{
	if(r[a]!=a)
	r[a]=find(r[a]);
	return r[a];
}
int build(int a,int b)
{
	int x=find(a);
	int y=find(b);	
	if(x!=y)
	{
		r[y]=x;
		sum[x]+=sum[y];
	}
	return 0;
}
int main()
{
 int n,m;
 while(cin>>n>>m&&n)
    {
    	for(int i=0;i<n;i++)
    	{
    		sum[i]=1;
    		r[i]=i;
		}
		while(m--)
		{
			int t,a,b;
			cin>>t>>a;
			t--;
			while(t--)
			{
				cin>>b;
				build(a,b);
			} 
		}
		cout<<sum[find(0)]<<endl;
	}	
	return 0;
}

M - 带权并查集 hdu 3038D

问题分析:
带权并查集 核心就是更新权值
主要 公式 寻根时的更新: val[a]+=val[pr[a]];
建立新链接是的更新 val[find(b)]=val[a]+v-val[b];
同根时检查 是否符合权值
代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#define  mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn =200005;
int  val[maxn],pr[maxn];
int find(int a)
{
   if(pr[a]==-1)
   return a;
   int t=find(pr[a]);
    val[a]+=val[pr[a]];
    return pr[a]=t;
}
int main() 
{
   int n,m,ans=0;
   while(cin>>n>>m)
   {
   	ans=0;
   	mem(val,0);
mem(pr,-1);
while(m--)
{
   int a,b,v;
   cin>>a>>b>>v;
   a--;
   int x=find(a);
   int y=find(b);
   if(x!=y)
   {
   	pr[y]=x;
   	val[y]=val[a]-val[b]+v;
    } 
    else if( val[b]-val[a]!=v)
     ans++;
}
cout<<ans<<endl;
   }

   return 0;
}

N - 种类并查集 POJ 2492

.种类并查集有2中写法:
1:一种是开倍数数组记记录 检查是否冲突 不冲突就合并(双向)
2:还有一种 是依赖于一个 偏移向量数组(相当于 权值数组 大神起的名字一听就感觉霸气) 来区别种类
这个题 用了 第一种方法 并 配了 测试数据
感觉关键就是 a-b+maxn b-a+maxn建立关联但他们相对 性别来说又是相对独立的
一旦出现 相同性别sex的时候就会找到同一个根
测试输出 各个数字 含义标式:

起始值 -起始值根植-过程根植-尾根植(就是root[a]=a情况 值a/root[a])

1000 4 4
1 2
a-again        1 1000002 1000002     a-end
b-maxn|-again: 2 2     b-end
b-again        1000002 1000002      b-end

a-again        2 1000001 1000001     a-end
b-maxn|-again: 1 1000002 1000002     b-end
b-again        1000001 1000001      b-end

2 3
a-again        2 1000001 1000003 1000003     a-end
b-maxn|-again: 3 3     b-end
b-again        1000003 1000003      b-end

a-again        3 1000002 1000002     a-end
b-maxn|-again: 2 1000003 1000003     b-end
b-again        1000002 1000002      b-end

1 4
a-again        1 1000002 1000004 1000004     a-end
b-maxn|-again: 4 4     b-end
b-again        1000004 1000004      b-end

a-again        4 1000003 1000003     a-end
b-maxn|-again: 1 1000004 1000004     b-end
b-again        1000001 1000003 1000003      b-end

1 3
1000004 Scenario #1:
Suspicious bugs found!

最好是自己推理一遍,有点难以理解…我是看了好久(看自闭的呢种 0_0 … )
输入 必须要用扫描 scanf() 要不就tle了…

代码如下:(含测试部分)

#include <iostream>
#include <cstring>
#include <algorithm>
#include<cstdio>
#define  mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn =1000000;
int  p[maxn*2],flag;
int find(int a)
{
    if( p[a]!=a)
    {
    	p[a]=find(p[a]);
    	cout<<p[a]<<" ";
	}
      
    return p[a];
}
int finds(int a)
{
    if( p[a]!=a)
    {
    	p[a]=find(p[a]);
	}
      
    return p[a];
}
int build(int a,int b)
{
	  int x,y;
	  x=finds(a);
	  y=finds(b-maxn);
	  if(x==y)
	  {
	  	flag=0;
	  	return 0;
	  }
	  y=finds(b);
	  if(x!=y)
	  {
	  	p[x]=p[y];
	  }
	  /*
	  cout<<"a-again        "<<a<<" "<<p[a]<<" ";
	  x=find(a);
	  cout<<"    a-end"<<endl;
	  cout<<"b-maxn|-again: "<<b-maxn<<" "<<p[b-maxn]<<" ";
	  y=find(b-maxn);
	  cout<<"    b-end"<<endl;
	  cout<<"b-again        "<<b<<" "<<p[b]<<" ";
	  y=find(b);
	  cout<<"     b-end"<<endl<<endl;
	  */	
}
int main() 
{
    int t,n,m,ans=0;
    scanf("%d",&t);
   for(int i=0;i<t;i++)
    {
	scanf("%d%d",&n,&m);
    	ans++;
    	for(int i=1;i<=n;i++)
    	{
    		p[i]=i;
			p[i+maxn]=maxn+i; 
		} 
    	flag=1;
      while(m--)
      {
      	int a,b;
      	scanf("%d%d",&a,&b);
      	build(a,b+maxn);
      	build(b,a+maxn);
      	} 
      	printf("Scenario #%d:\n",ans);
      	if(flag)
      	printf("No suspicious bugs found!\n");
      	else
      	printf("Suspicious bugs found!\n");
      	cout<<endl;
     }
    return 0;
}

O - 种类并查集 POJ 1182

这个用就是 变量val[i] 定值 区分
这个 玩意推了好久才看明白
附上大神 全面解析
把大神的解析精简了一下面结合起来应该会更好!(这里用 val代替relation).
1.偏移数组 val[]值的含义
是由于 输入关系1 2决定(取用 输入的关系-1) 因此
规定 0:同类 1:子被吃 x->y 2: 子吃父x<-y (这样子就可以 用%3进行 压缩)
2.路径压缩算法 公式
x->y->z (子->父->爷)关系表达式(枚举推理,x->y代表 x是y 的根哈~)
val[爷]=(vla[子]+val[父])%3 (%3为了 防止大于等于3的情况出现 )
同时 可以得到 x->y(子被吃 1)=y<-x(子吃父3-2)
可以得到 x->y(子被吃 3-1)=y<-x(子吃父2)
3.集合间情关系 确定公式:
val[b]=(3- val[y]+(relation -1)+val[x])%3;(relation 缩写 r)
(b=find(y),b为y的根,输入:relation x y )

关系图
X------>Y<-------B
r-1 val[y]
X------>Y------->B
r-1 3-val[y]
A----->-X------>Y------->B
val[x] r-1 val[y]

四. 判定公式

据题意共计4种为假话
1. x>n或者y>n
2. r=2&&x==y
3.a!=b时
1) x==&&val[x]!=val[y]
2) x==2&&(3-val[x]+val[y])%3!=1

关于3-2)的图解

目前状态 过程 所求状态
A<—X---->Y 变下方向:3-val[x] A—>X---->Y

累…总算写完了哭泣~~
代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include<cstdio>
#define  mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn =50010;
int  p[maxn],val[maxn];
int find(int a)
{
    int t;
    if(a==p[a])
    return a;
    t=p[a];
    p[a]=find(t);
    val[a]=(val[a]+val[t])%3;
    return p[a];
}
int main() 
{
     int n,m,ans=0;
scanf("%d%d",&n,&m);
	 for(int i=0;i<=n;i++)
     {
     	p[i]=i;
     	val[i]=0;
	 }
   while(m--)
   {
   	int relation,x,y;
scanf("%d%d%d",&relation,&x,&y);
   	if(x>n||y>n)
   	ans++;	
   	else if(relation==2&&x==y)
   		ans++;
   	else
   	{
   		int a=find(x),b=find(y);
   		if(a!=b)
   		{
   			p[b]=a;
   			val[b]=(3+relation-1+val[x]-val[y])%3;
		   }
		   else
		   {
		   	if(relation==1&&val[x]!=val[y])
		   	ans++;
		   	if(relation==2&&((3-val[x]+val[y])%3)!=1)
		   	ans++;
		   }
	}
   }
   printf("%d\n",ans);
    return 0;
}
发布了55 篇原创文章 · 获赞 1 · 访问量 990

猜你喜欢

转载自blog.csdn.net/weixin_43556527/article/details/102975882