图论优题(1)

1.Computer

HDU - 2196
算法:树的直径,树形DP
注释:dist[i][0]表示向下最长距离,dist[i][1]表示向下次长距离,dist[i][2]表示反向最长距离
1)先按求树的直径的方法计算出每个节点向下的最长距离和次长距离,注意应该记录达到最长距离最经过的儿子节点,以方便后边方向距离的计算。状态转移公式为:

if(dist[u][0]<dist[v][0]+w)
{
longest[u]=v;
dist[u][1]=dist[u][0];
dist[u][0]=dist[v][0]+w;
}
else if(dist[u][1]<dist[v][0]+w)
dist[u][1]=dist[v][0]+w;

DP顺序为先计算儿子节点,再计算父节点。
2)在求该节点经过父节点的反向最长距离,注意不应该用父节点能到达的最大距离,因为可能出现重复路径。状态转移公式为:

if(v==longest[u])dist[v][2]=max(dist[u][2],dist[u][1])+w;
else dist[v][2]=max(dist[u][2],dist[u][0])+w;

DP顺序先计算父节点,再计算子节点。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 10010
#define ll long long
using namespace std;
int first[N],nex[N<<1],ord[N<<1],vul[N<<1],cnt;
ll dist[N][3],longest[N];
void add(int u,int v,ll w)
{
	nex[++cnt]=first[u];
	ord[cnt]=v;
	vul[cnt]=w;
	first[u]=cnt;
}
ll max(ll a,ll b)
{
	return a>b?a:b;
}
void dp1(int u,int father)
{
	//cout<<u<<endl;
	int i,v,w;
	for(i=first[u];i;i=nex[i])
	{
		v=ord[i];
		w=vul[i];
		if(v==father)continue;
		dp1(v,u);
		if(dist[u][0]<dist[v][0]+w)
		{
			longest[u]=v;
			dist[u][1]=dist[u][0];
			dist[u][0]=dist[v][0]+w;
		}
		else if(dist[u][1]<dist[v][0]+w)
			dist[u][1]=dist[v][0]+w;
	}
}
void dp2(int u,int father)
{
	int v,w,i;
	for(i=first[u];i;i=nex[i])
	{
		v=ord[i];
		w=vul[i];
		if(v==father)continue;
		if(v==longest[u])dist[v][2]=max(dist[u][2],dist[u][1])+w;
		else dist[v][2]=max(dist[u][2],dist[u][0])+w;
		dp2(v,u);
	}
}
int main()
{
	ll i,u,n,v,w;
	while(cin>>n&&n!=0)
	{
		memset(dist,0,sizeof(dist));
		memset(first,0,sizeof(first));
		memset(longest,0,sizeof(longest));
		cnt=0;
		for(u=2;u<=n;u++)
		{
			cin>>v>>w;
			add(u,v,w);
			add(v,u,w);
		}
		dp1(1,0);
		dp2(1,0);
		for(u=1;u<=n;u++)
		cout<<max(dist[u][2],dist[u][0])<<endl;
	}
	
	return 0;
}

2.Shichikuji and Power Grid

CF1245D
算法:最小生成树
注释:本题考验抽象思维

  • 将每个城市建造发电厂所需要的费用看做是次点到零点所的距离,然后依次计算每两个点之间建设路线的花费,将问题转化为最小生成树问题,因为包含零点,所以必定至少一个点由建设发电厂,由于最终结果是一棵树,所以必定每个点都有可能连接到发电厂。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 2010
#define M 5000010
#define ll long long 
using namespace std;
struct point{
	int x,y;
}p[N];
struct edge{
	int u,v;
	ll w;
}e[M],con[N];
int c[N],k[N],f[N],cnt;
int vn=0,build[N],en=0;
ll ans;
bool cmp(const edge &a,const edge &b)
{
	return a.w<b.w;
}
void init(int n)
{
	for(int i=0;i<=n;i++)
	f[i]=i;
}
int find(int u)
{
	if(f[u]==u)return u;
	return find(f[u]);
}
ll dis(int i,int j)
{
	return (abs(p[i].x-p[j].x)+abs(p[i].y-p[j].y));
}
int main()
{
	int n,m=0;
	
	cin>>n;
	init(n);
	
	for(int i=1;i<=n;i++)
		cin>>p[i].x>>p[i].y;
	for(int i=1;i<=n;i++)
	{
		cin>>c[i];
		e[m].u=i;
		e[m].v=0;
		e[m++].w=c[i];
	}
	for(int i=1;i<=n;i++)
		cin>>k[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			e[m].u=i;
			e[m].v=j;
			//cout<<i<<" "<<j<<" "<<dis(i,j)<<endl; 
			e[m++].w=dis(i,j)*(k[i]+k[j]);
		}
	}
	//cout<<m<<endl<<endl;
	sort(e,e+m,cmp);
	
	
	for(int i=0;i<m;i++)
	{
		//cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<endl;
		int u=find(e[i].u);
		int v=find(e[i].v);
		if(u!=v)
		{
			if(e[i].v==0)
			{
				build[++vn]=e[i].u;
			}
			else 
			{
				con[++en].u=e[i].u;
				con[en].v=e[i].v;
			}
			 
			f[u]=v;
			ans+=e[i].w;
			cnt++;
		}
		if(cnt==n)break;
	}
	cout<<ans<<endl;
	cout<<vn<<endl;
	for(int i=1;i<=vn;i++)
	cout<<build[i]<<" ";
	cout<<endl<<en<<endl;
	for(int i=1;i<=en;i++)
	cout<<con[i].u<<" "<<con[i].v<<endl;
	return 0;
 } 

3.book of evil

Codeforces 337D
算法:树的直径,树形DP
注释:

  • 树的直径问题就是线性求多源最长(或最短)路径,或者求解树中的最长链,当然这都是在图是一棵树的基础上进行的。
  • 本题可以转化为求每个点到最远的受到伤害的点的距离,最终遍历所有点的最长距离,只要小于等于所给范围,就可以认定为可能放魔法书的点。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
#define MINN -0x3f3f3f3f
using namespace std;
int dist[N][3],longest[N];
int first[N],nex[N<<1],ord[N<<1],cnt=0;
bool p[N];
int max(int a,int b)
{
	return a>b?a:b;
}
void add(int u,int v)
{
	nex[++cnt]=first[u];
	ord[cnt]=v;
	first[u]=cnt;
}
void dp1(int u,int father)
{
	int v,i;
	if(p[u])dist[u][0]=dist[u][2]=0;
	for(i=first[u];i;i=nex[i])
	{
		v=ord[i];
		if(v==father)continue;
		dp1(v,u);
		if(dist[u][0]<dist[v][0]+1)
		{
			longest[u]=v;
			dist[u][1]=dist[u][0];
			dist[u][0]=dist[v][0]+1;
		}
		else if(dist[u][2]<dist[v][0]+1)
		{
			dist[u][2]=dist[v][0]+1;
		}
	}
}
void dp2(int u,int father)
{
	int v,i;
	for(i=first[u];i;i=nex[i])
	{
		v=ord[i];
		if(v==father)continue;
		if(dist[v][0]+1==dist[u][0])
		{
			dist[v][2]=max(dist[u][2],dist[u][1])+1;
		}
		else 
			dist[v][2]=max(dist[u][0],dist[u][2])+1;
		dp2(v,u);
	}
}
int main()
{
	int n,m,d,i,u,v,x,tot=0;
	cin>>n>>m>>d;
	for(i=1;i<=n;i++)
	dist[i][0]=dist[i][1]=dist[i][2]=MINN;
	for(i=1;i<=m;i++)
	{
		cin>>x;
		p[x]=true;
	}
	for(i=1;i<n;i++)
	{
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dp1(1,0);
	dp2(1,0);
	//for(i=1;i<=n;i++)
	//cout<<dist[i][0]<<" "<<dist[i][1]<<" "<<dist[i][2]<<endl;
	for(i=1;i<=n;i++)
	{
		if(i==1)
		{
			if(max(dist[i][0],dist[i][1])<=d)tot++;
		}
		else if(max(dist[i][0],dist[i][2])<=d)tot++;
	}
	cout<<tot<<endl;
	return 0;
} 

4.You Are Given a Tree

第一周提高 H - 8
算法:树的直径,树形DP
注释:

  • 因为是在给出的一棵树中找出三个点,所以三个点形成的三条边之间一定存在一个交点,当然如果三个点在同一条直线上,那么可能存在多个交点。问题可以最终转为求解每个点所能到达的最长,次长,次次长路径(由于是DP所以也能记录最终点或者路径),即为求解时树的直径时再增加一条次次长路径的判断。注意问题要求最终求得三个点互不相同,所以需要加一条判断。
#include<iostream>
#include<cstdio>
#include<cstring> 
#define N 200010
using namespace std;
int first[N],nex[N<<1],ord[N<<1],cnt;
int dist[N][4],p[N][4],longest[N],maxn,res[3];
void add(int u,int v)
{
	nex[++cnt]=first[u];
	ord[cnt]=v;
	first[u]=cnt;
}
void init(int n)
{
	int i;
	memset(dist,0,sizeof(dist));
	for(i=0;i<=n;i++)
	p[i][0]=p[i][1]=p[i][2]=p[i][3]=i;
}
int max(int a,int b)
{
	return a>b?a:b;
}
void dfs1(int u,int father)
{
	int v,i;
	for(i=first[u];i;i=nex[i])
	{
		v=ord[i];
		if(v==father)continue;
		dfs1(v,u);
		if(dist[u][0]<dist[v][0]+1)
		{
			longest[u]=v;
			dist[u][2]=dist[u][1];
			p[u][2]=p[u][1];
			dist[u][1]=dist[u][0];
			p[u][1]=p[u][0];
			dist[u][0]=dist[v][0]+1;
			p[u][0]=p[v][0]; 
		}
		else if(dist[u][1]<dist[v][0]+1)
		{
			dist[u][2]=dist[u][1];
			p[u][2]=p[u][1];
			dist[u][1]=dist[v][0]+1;
			p[u][1]=p[v][0];
		}
		else if(dist[u][2]<dist[v][0]+1)
		{
			dist[u][2]=dist[v][0]+1;
			p[u][2]=p[v][0];
		}
	}
}
void dfs2(int u,int father)
{
	int v,i;
	for(i=first[u];i;i=nex[i])
	{
		v=ord[i];
		if(v==father)continue;
		if(v==longest[u])
		{
			if(dist[u][3]>dist[u][1])
			{
				dist[v][3]=dist[u][3]+1;
				p[v][3]=p[u][3];
			}
			else
			{
				dist[v][3]=dist[u][1]+1;
				p[v][3]=p[u][1];
			}
		}
		else 
		{
			if(dist[u][3]>dist[u][0])
			{
				dist[v][3]=dist[u][3]+1;
				p[v][3]=p[u][3];
			}
			else 
			{
				dist[v][3]=dist[u][0]+1;
				p[v][3]=p[u][0];
			}
		}//dist[v][3]=max(dist[u][0],dist[u][3])+1;
		dfs2(v,u);
	}
}
bool judge(int u)
{
	if((p[u][0]!=p[u][1])&&((p[u][1]!=p[u][2])||(p[u][1]!=p[u][3]))&&((p[u][0]!=p[u][2])||(p[u][0]!=p[u][3])))
	return 1;
	else return 0;
}
int main()
{
	int n,u,v,i;
	cin>>n;
	init(n);
	for(i=1;i<n;i++)
	{
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dfs1(1,0);
	dfs2(1,0);
	for(int i=1;i<=n;i++)
	{
		//cout<<dist[i][0]<<" "<<dist[i][1]<<" "<<dist[i][2]<<" "<<dist[i][3]<<endl;
		if(maxn<dist[i][0]+dist[i][1]+max(dist[i][2],dist[i][3])&&judge(i))
		{
			maxn=dist[i][0]+dist[i][1]+max(dist[i][2],dist[i][3]);
			res[0]=p[i][0];
			res[1]=p[i][1];
			res[2]=(dist[i][2]>dist[i][3]?p[i][2]:p[i][3]);//当相等时,优先选择父节点 
		}
	}
	cout<<maxn<<endl;
	cout<<res[0]<<" "<<res[1]<<" "<<res[2]; 
	return 0;
}

5.Fire

UVA-11624
算法:BFS
注释:

  • 进行两次BFS,先将火燃烧的每个点的所需的时间预处理出来,在第二次BFS的时候多加一个判断即可
  • 注意:在修改代码时,应该修改全,上下相似的代码都要进行修改
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define N 1010
using namespace std;
struct step{
	int x;
	int y;
	int s;
}temp;
queue<step>q;
char map[N][N];
bool vis[N][N];
int nx[4]={0,1,0,-1},ny[4]={1,0,-1,0},r,c,t,fmap[N][N];
void bfsf()
{
	step u,v;
	while(!q.empty())
	{
		u=q.front();
		//cout<<u.x<<" "<<u.y<<endl;
		q.pop();
		for(int i=0;i<=3;i++)
		{
			v.x=u.x+nx[i];
			v.y=u.y+ny[i];
			v.s=u.s+1;
			if(v.x==0||v.x>r||v.y==0||v.y>c)continue;
			else if(map[v.x][v.y]=='.'&&vis[v.x][v.y]==false)
			{
				vis[v.x][v.y]=true;
				fmap[v.x][v.y]=v.s; 
				q.push(v);
			}
		}
	}
}
int bfs(int sx,int sy)
{
	step u,v;
	
	vis[sx][sy]=true;
	u.x=sx;
	u.y=sy;
	u.s=0;
	q.push(u);
	
	while(!q.empty())
	{
		u=q.front();
		//cout<<u.x<<" "<<u.y<<":::::"<<endl;
		q.pop();		
		for(int i=0;i<=3;i++)
		{
			v.x=u.x+nx[i];
			v.y=u.y+ny[i];
			v.s=u.s+1;
			if(v.x==0||v.x>r||v.y==0||v.y>c)return v.s;
			else if(map[v.x][v.y]=='.'&&vis[v.x][v.y]==false)
			{
				vis[v.x][v.y]=true;
				//cout<<v.x<<" "<<v.y<<" "<<v.s<<endl;
				if(fmap[v.x][v.y]<=v.s)
				{
					//cout<<fmap[v.x][v.y]<<endl;
					continue;
				}
				q.push(v);
			}
		}
	}
	return -1;
}
void init()
{
	memset(vis,0,sizeof(vis));
	while(!q.empty())q.pop();
	
}
int main()
{
	//freopen("1.txt","r",stdin);
	//freopen("2.txt","w",stdout);
	cin>>t;
	int b=0;
	while(t--)
	{
		b++;
		init();
		memset(fmap,111,sizeof(fmap));
		int sx,sy;
		
		cin>>r>>c;
		for(int i=1;i<=r;i++)
		for(int j=1;j<=c;j++)
		{
			cin>>map[i][j];
			if(map[i][j]=='F')
			{
				fmap[i][j]=0;
				temp.x=i;
				temp.y=j;
				temp.s=0;
				q.push(temp);
			}
			if(map[i][j]=='J')
			{
				sx=i;
				sy=j;
			}
		}
		
		bfsf();
		init(); 
		int res=bfs(sx,sy);
		if(res==-1)cout<<"IMPOSSIBLE"<<endl;
		else cout<<res<<endl;
	}
	return 0;
 } 

6.Monocycle

UVA-10047
算法:BFS
注释:

  • 变式的BFS,每个点有多个状态,包括颜色和朝向的不同都应该记录,看作是不同的点,每种状态的方向只有三种,向左转,向右转和向前走。其实就是抽象成图,状态做点,方向做边。
  • 注意:数组大小尽量开大一点,否则可能出现越界数组出现不同值的情况,在修改代码时,应该将代码修改全。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue> 
using namespace std;
struct state{
	int x,y,d,c;
	int s;
}s,t;

int f[26][26][4][5],n,m,nx[4]={-1,0,1,0},ny[4]={0,1,0,-1};
bool vis[26][26][4][5];//检查时应该注意数组的大小开的是否合适,尽量开大一点 
char map[26][26];
queue<state>q;
void init()
{
	memset(f,0,sizeof(f));
	memset(vis,0,sizeof(vis));
	while(!q.empty())q.pop();
}
void is_push(state v)
{
	if(vis[v.x][v.y][v.d][v.c]==0)
	{
		vis[v.x][v.y][v.d][v.c]=1;
		q.push(v);
	}
}
int bfs()
{
	state u,v;
	s.s=0;
	s.c=0;
	s.d=0;
	vis[s.x][s.y][s.d][s.c]=1;
	q.push(s);
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		v.s=u.s+1;
		//向下一个方向转
		v.x=u.x;
		v.y=u.y;
		v.c=u.c;
		v.d=(u.d+5)%4;
		is_push(v);
		//向上一个方向转
		v.x=u.x;
		v.y=u.y;
		v.c=u.c;
		v.d=(u.d+3)%4;
		is_push(v);
		//向当前方向走一格
		
		v.x=u.x+nx[u.d];
		v.y=u.y+ny[u.d];
		v.c=(u.c+1)%5;
		v.d=u.d;
		if(v.x>0&&v.y>0&&v.x<=n&&v.y<=m&&map[v.x][v.y]!='#')
		{ 
			
			if(v.x==t.x&&v.y==t.y&&v.c==0)
			return v.s;
			is_push(v);
		}
	}
	return -1;
}
int main()
{
	int cnt=0; 
	while(cin>>n>>m&&n&&m)
	{
		cnt++;
		init();
		for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			cin>>map[i][j];
			if(map[i][j]=='S')
			{
				s.x=i;
				s.y=j;
			}
			if(map[i][j]=='T')
			{
				t.x=i;
				t.y=j;
			}
		}
		if(cnt!=1)cout<<endl;
		cout<<"Case #"<<cnt<<endl;
		int res=bfs();
		if(res!=-1)cout<<"minimum time = "<<res<<" sec"<<endl;
		else cout<<"destination not reachable"<<endl;
	}
	return 0;
} 

7.糖果

LibreOJ - 2436
算法:拓扑排序,spfa,差分约束
注释:

  • 对于差分约束题目,需要使用spfa来做,根据大小关系,建图求最短路径。应该注意的是。首先建立虚源点指向所有点,求解最小值以及建立的是是正图时,求最长路,建立的昰负图时是求最短路,可以具体问题具体分析来看。当出现负环时说明不存在这种约束关系,所有由虚源点直接指向的值可以修改以得到不同的结果集合,可以将结果加上同一个数字也可以得到不同的结果集合。
  • 当差分约束的值为单一值,零或者一的时候,可以考虑使用缩点加拓扑排序的方式来做。
  • 本题目中,将符合条件一的,建立权值为0的双向边,条件二和四建立权值为1的单向边,条件三和五建立权值为0的单向边,然后建立一个指向所有点且权值为0的原点。需要注意的是,条件二和条件四的两个点不应该相同,若相同,直接判定为有负环。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue> 
#define ll long long
#define N 100010
#define K 100010
using namespace std;
struct edge{
	int next;
	int to;
	ll w;
}e[K<<2];
ll first[N],cnt,dis[N],num[N];
bool vis[N];
queue<int>q;
void add(int u,int v,int w)
{
	e[++cnt].next=first[u];
	e[cnt].to=v;
	e[cnt].w=w;
	first[u]=cnt;
}
void init()
{
	cnt=0;
	memset(first,-1,sizeof(first));
	memset(dis,0,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(num,0,sizeof(num));
	while(!q.empty())q.pop();
	
}
int spfa(int s,int n)
{
	dis[s]=0;
	vis[s]=1;
	num[s]++;
	q.push(s);
	
	int u,v,w;
	
	while(!q.empty())
	{
		u=q.front();
		vis[u]=0;
		q.pop();
		
		for(int i=first[u];i+1;i=e[i].next)
		{
			v=e[i].to;
			w=e[i].w;
			if(dis[v]<dis[u]+w)
			{
				dis[v]=dis[u]+w;
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				} 
				num[v]++;
				if(num[v]>n)return -1;
			}
		}
	}
	return 1;
}
int min(int a,int b)
{
	return a<b?a:b;
}
int main()
{
	//freopen("3.in","r",stdin); 
	init();
	ll n,k,x,a,b,tot=0,minn=0;
	cin>>n>>k;
	for(int i=0;i<k;i++)
	{
		cin>>x>>a>>b;
		if(x==1)
		{
			add(b,a,0);
			add(a,b,0);
		}
		else if(x==2) 
		{
			if(a==b){cout<<-1;return 0;}//特殊情况的考虑 
			add(a,b,1);
		}
		else if(x==3) add(b,a,0);
		else if(x==4)
		{
			 if(a==b){cout<<-1;return 0;}
			 add(b,a,1);
		}
		else if(x==5) add(a,b,0);
	}
	for(int i=n;i>=1;i--)
	add(0,i,1);
	
	
	
	if(spfa(0,n)==-1)
		cout<<-1;
	else 
	{
		for(int i=1;i<=n;i++)
			tot+=dis[i];
		//cout<<n<<" "; 
		cout<<tot;
	}
	return 0;
}

8.Guess

UVALive - 4255
算法:前缀和,差分约束,拓扑排序
注释:

  • 本题可以将前缀和看做一个元素,这样,符号矩阵中的每个符号可以表示成前缀和之差:S[i][j]=sum[j]-sum[i-1],求出每个sum后可以计算每个元素的值。
  • 加号和减号建立边关系,等号不建立。可以得到几组不等式,转化为差分约束问题,由于两个元素的差都是与0相比,可以抽象成边的权值为零,这样可以利用拓扑排序来解决这个问题。
  • 注意:最后求出前缀和的从小到大的关系,应该利用零值确定每个前缀和的正负号。同样为差分约束系统,可以类比糖果一题。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define N 20
using namespace std;
struct edge{
	int next,to;
}e[N*N*2];
struct point{
	int num,pri;
}q[N];
int sum[N],n,first[N],cnt,inum[N];
char pos[N][N];
int head,tail;
void add(int u,int v)
{
	e[++cnt].next=first[u];
	e[cnt].to=v;
	first[u]=cnt;
}
void init()
{
	memset(first,-1,sizeof(first));
	memset(sum,0,sizeof(sum));
	memset(inum,0,sizeof(inum));
	head=tail=cnt=0;
}
int main()
{
	int t,x,zero;
	point u,v;
	cin>>t;
	while(t--)
	{
		init();
		cin>>n;
		for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
		cin>>pos[i][j];
		
		for(int i=1;i<=n;i++)
		{
			for(int j=i;j<=n;j++)
			{
				if(pos[i][j]=='+')
				{
					add(i-1,j);
					inum[j]++;
				}
				else if(pos[i][j]=='-')
				{
					add(j,i-1);
					inum[i-1]++;
				} 
			}
		}
		/*for(int i=0;i<=n;i++)
		{
			cout<<i<<":";
			for(int j=first[i];j+1;j=e[j].next)
			cout<<e[j].to<<" ";
			cout<<endl;
		}*/
		for(int i=0;i<=n;i++)
		if(!inum[i])
		{
			q[tail].num=i;
			q[tail].pri=0;
			tail++;
		}
		point temp;
		while(head<tail)
		{
			u=q[head++];
			for(int i=first[u.num];i+1;i=e[i].next)
			{
				v.num=e[i].to;
				inum[v.num]--;
				if(!inum[v.num])
				{
					v.pri=u.pri+1;
					q[tail++]=v;
				}
			}
		}
		for(int i=0;i<=n;i++)
			if(q[i].num==0)
			{
				zero=i;
				break;
			}
		
		for(int i=zero-1;i>=0;i--)
		{
			if(q[i].pri!=q[i+1].pri)
			sum[q[i].num]=sum[q[i+1].num]-1;
			else 
			sum[q[i].num]=sum[q[i+1].num];
		}
		for(int i=zero+1;i<=n;i++)
		{
			if(q[i].pri!=q[i-1].pri)
			sum[q[i].num]=sum[q[i-1].num]+1;
			else 
			sum[q[i].num]=sum[q[i-1].num];
		}
		for(int i=1;i<=n;i++)
		{
			cout<<sum[i]-sum[i-1]<<" ";
		}
		cout<<endl;
	}
	return 0;
}

9.Knights of the Round Table

UVALive - 3523
算法:双连通分量,割点问题
注释:

  • 本题比较考量图论的基础知识和分析。将骑士看做点,可以相邻的看做无向边,本题其实是求解不在任何一个简单奇圈上的点的个数。
  • 简单圈上的点一定处于双连通分量上,为保证存在简单奇圈,该双连通分量一定不是二分图(无向图是二分图当且仅当不存在奇圈)。此时剩下一个问题,就是如果点V在双连通分量上,是否一定在简单奇圈上?想给出答案是肯定的
  • 假设U1和U2在简单奇圈上,一定存在两条不相交路径相互到达,且长度一定是一奇一偶,从V也一定存在两条不相交路径到达U1和U2,所以V,U1,U2一定在同一个简单奇圈中。所以最终问题转化为求解不在任何一个不是二分图的双连通分量中的点的个数。
    在这里插入图片描述
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<stack>
#define N 1010
#define M 1000010

using namespace std;
struct edge{
	int u,v;
};
int n,m,bccno[N],pre[N],low[N],bcc_cnt,dfs_clock,color[N]; //注意变量类型 
bool A[N][N],iscut[N],odd[N];
vector<int>G[N],bcc[N];
stack<edge>s;
void init()
{
	memset(A,0,sizeof(A));
	memset(bccno,0,sizeof(bccno));
	memset(pre,0,sizeof(pre));
	memset(low,0,sizeof(low));
	memset(iscut,0,sizeof(iscut));
	memset(color,0,sizeof(color));
	memset(odd,0,sizeof(odd));
	for(int i=1;i<=n;i++)
	G[i].clear();
	while(!s.empty())s.pop();
	bcc_cnt=dfs_clock=0;
}
int dfs(int u,int fa)
{
	int lowu=pre[u]=low[u]=++dfs_clock;
	int child=0;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		edge e=(edge){u,v};
		if(!pre[v])
		{
			s.push(e);
			int lowv=dfs(v,u);
			lowu=min(lowu,lowv);
			child++;
			
			if(lowv>=pre[u])
			{
				iscut[u]=1;
				bcc_cnt++;
				bcc[bcc_cnt].clear();
				for(;;)
				{
					edge x=s.top();s.pop();
					if(bccno[x.u]!=bcc_cnt)
					{
						bccno[x.u]=bcc_cnt;
						bcc[bcc_cnt].push_back(x.u);
					}
					if(bccno[x.v]!=bcc_cnt)
					{
						bccno[x.v]=bcc_cnt;
						bcc[bcc_cnt].push_back(x.v);
					}
					if(x.u==u&&x.v==v)break;
				}
			}
		}
		else if(pre[v]<pre[u]&&v!=fa)
		{
			s.push(e);
			lowu=min(lowu,pre[v]);
		}
	}
	if(fa==0&&child==1)iscut[u]=0;
	low[u]=lowu;
	return lowu;
}
void find_bcc(int n)
{
	for(int i=1;i<=n;i++)
	if(!pre[i])dfs(i,0);
}
bool partite(int u,int cnt)
{
	//cout<<u<<endl;
	//for(int i=1;i<=n;i++)
	//cout<<color[i]<<" ";
	//cout<<endl;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(bccno[v]!=cnt)continue;
		if(color[u]==color[v])return false;
		if(color[v]==0)
		{
			color[v]=3-color[u];
			if(!partite(v,cnt))return false;
		} 
	}
	return true;
}
int main()
{
	//freopen("1.txt","w",stdout);
	int u,v;
	bool flag=false;
	while(scanf("%d%d",&n,&m)!=EOF&&(n||m))
	{
		init();
		for(int i=0;i<m;i++)
		{
			scanf("%d%d",&u,&v);
			A[u][v]=A[v][u]=1;
		}
		for(int i=1;i<=n;i++)
		{
			//G[i].clear();
			for(int j=i+1;j<=n;j++)
			if(!A[i][j])
			{
				G[i].push_back(j);
				G[j].push_back(i);
			}
		}
		find_bcc(n);
		
		for(int i=1;i<=bcc_cnt;i++)
		{
			//cout<<i<<":";
			//if(bcc[i].size()<=2)continue;
			memset(color,0,sizeof(color));
			for(int j=0;j<bcc[i].size();j++)
			{
				bccno[bcc[i][j]]=i;
				//color[bcc[i][j]]=0;
			//	cout<<bcc[i][j]<<" ";
			}
			//cout<<endl;
			color[bcc[i][0]]=1;
			
			if(!partite(bcc[i][0],i))
			{
				//cout<<i<<endl;
				for(int j=0;j<bcc[i].size();j++)
				odd[bcc[i][j]]=1;
			}
		}
		int ans=n;
		for(int i=1;i<=n;i++)
		if(odd[i])ans--;
		cout<<ans<<endl;
	}
	return 0;
} 

10.Mining Your Own Business

UVALive - 5135
算法:双连通分量,割点割边
注释:

  • 显然,太平井不应该建在割点处,求解所有双连通分量,如果一个双连通分量有一个割点,那么只需建立一个太平井,如果有两个割点,不需要建立太平井,如果没有割点(这种情况只可能出现一次),需要建立两个。
  • 求解方案的时候,割点不应该成为选择的可能。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<stack>
#define N 100010
#define ll long long
using namespace std;
struct edge{
	int u,v;
};
int dfs_clock,bcc_cnt,pre[N],low[N],bccno[N];
bool iscut[N];
vector<int>G[N],bcc[N];
stack<edge>s;
void init(int n)
{
	memset(pre,0,sizeof(pre));
	memset(low,0,sizeof(low));
	memset(bccno,0,sizeof(bccno));
	memset(iscut,0,sizeof(iscut));
	while(!s.empty())s.pop();
	for(int i=1;i<=N;i++)//初始化一定要完整 
		G[i].clear();
	dfs_clock=bcc_cnt=0;
}
int dfs(int u,int fa)
{
	int lowu=pre[u]=low[u]=++dfs_clock;
	int child=0;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		edge e=(edge){u,v};
		if(!pre[v])
		{
			s.push(e);
			int lowv=dfs(v,u);
			lowu=min(lowu,lowv);
			child++;
			
			if(lowv>=pre[u])
			{
				iscut[u]=1;
				bcc_cnt++;
				bcc[bcc_cnt].clear();
				for(;;)
				{
					edge x=s.top();s.pop();
					if(bccno[x.u]!=bcc_cnt)
					{
						bccno[x.u]=bcc_cnt;
						bcc[bcc_cnt].push_back(x.u);
					}
					if(bccno[x.v]!=bcc_cnt)
					{
						bccno[x.v]=bcc_cnt;
						bcc[bcc_cnt].push_back(x.v);
					}
					if(x.u==u&&x.v==v)break;
				}
				//cout<<u<<" "<<v<<endl;
			}
		}
		else if(pre[v]<pre[u]&&v!=fa)
		{
			s.push(e); 
			lowu=min(lowu,pre[v]);
		}
	}
	if(fa==0&&child==1)iscut[u]=0;
	low[u]=lowu;
	return lowu;
}
void find_bcc(int n)
{
	for(int i=1;i<=n;i++)
	if(!pre[i])dfs(i,0);
}
int main()
{
	int m,n,u,v,t=0;
	while(scanf("%d",&m)!=EOF&&m)
	{
		t++;
		ll ans=0,tot=1;
		n=0;//n需要初始化 
		init(m);
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
			n=max(n,max(u,v));
		}
		//cout<<endl;
		find_bcc(n);
		
		for(int i=1;i<=bcc_cnt;i++)
		{
			ll num=0;
			//cout<<i<<":";
			for(int j=0;j<bcc[i].size();j++)
			{
				u=bcc[i][j];
			//	cout<<u<<" ";
				if(iscut[u])num++;
			}
			//cout<<endl; 
			if(num==1)
			{
				ans++;
				tot=tot*(ll)(bcc[i].size()-1);
			}
		}
		if(bcc_cnt==1)
		{
			ans=2;
			tot=(ll)bcc[1].size()*(bcc[1].size()-1)/2; 
		}
		cout<<"Case "<<t<<": "<<ans<<" "<<tot<<endl;
	}
	return 0;
} 

11.Proving Equivalences

UVALive - 4287
算法:强连通分量,拓扑排序,缩点
注释:

  • 首先求出所有强连通分量,因为可以相互到达,缩为一点,最终形成几个DAG图。
  • DAG的两条重要性质:

性质一:DAG中所有入度不为0的点,一定可以从某个入度为0的点出发可达。
性质二:从DAG上任何一个点出发不断往前走必然终止于一个出度为0的点。

  • 也就是说从所有入度为零的点出发一定可以走遍边到达所有出度为零的点,只需要将所有出度为零的点与入度为零的点相连即可,可以将每个DAG出度为零的点连向下一个DAG入度为零的点,两两相连,形成环路,设a为入度为零的点个数,b为出度为零的点的个数,max(a,b)即为最终结果。
/*DAG:有向无环图*/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#define N 20010
using namespace std;
vector<int>G[N],scc[N],G2[N];
int pre[N],low[N],dfs_clock,sccno[N],scc_cnt,exp[N],imp[N];
stack<int>s;
void init(int n)
{
	dfs_clock=scc_cnt=0;
	memset(pre,0,sizeof(pre));
	memset(low,0,sizeof(low));
	memset(sccno,0,sizeof(sccno));
	memset(exp,0,sizeof(exp));
	memset(imp,0,sizeof(imp));
	while(!s.empty())s.pop();
	for(int i=1;i<=n;i++)
	{
		G[i].clear();
		scc[i].clear();
		G2[i].clear();
	}
}
int dfs(int u)
{
	//cout<<u<<endl;
	low[u]=pre[u]=++dfs_clock;
	s.push(u);
	
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		//cout<<v<<"--"<<pre[v]<<endl;
		if(!pre[v])
		{
			dfs(v);
			low[u]=min(low[v],low[u]);
		}
		else if(!sccno[v])
		{
			low[u]=min(low[v],low[u]);
		}
	}
	if(low[u]==pre[u])
	{
		scc_cnt++;
		while(1)
		{
			int v=s.top();s.pop();
			sccno[v]=scc_cnt;
			scc[scc_cnt].push_back(v);
			if(v==u)break;
		}
	}
} 
void find_scc(int n)
{
	for(int i=1;i<=n;i++)
	if(!pre[i])dfs(i);
}
int main()
{
	int t,n,m,u,v;
	scanf("%d",&t);
	while(t--)
	{
		int a=0,b=0;
		scanf("%d%d",&n,&m);
		init(n);
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
		}
		
		
		find_scc(n);
		
		
		for(u=1;u<=n;u++)
		{
			for(int j=0;j<G[u].size();j++)
			{
				v=G[u][j];
				if(sccno[v]!=sccno[u])
				{
					exp[sccno[u]]++;
					imp[sccno[v]]++;
				}
			}
		}
		for(int i=1;i<=scc_cnt;i++)
		{
			if(!exp[i])a++;
			if(!imp[i])b++;
		}
		if(scc_cnt==1)
		cout<<0<<endl;
		else cout<<max(a,b)<<endl;
	}
	return 0;
} 

12.The Largest Clique

UVA - 11324
算法:缩点,强连通分量,拓扑排序,线性DP
注释:

  • 求解强连通分量进行缩点,得到DAG图,求最长的一条链,线性动态规划
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<queue>
#define N 1010
using namespace std;
int pre[N],low[N],dfs_clock,sccno[N],scc_cnt,imp[N],num[N],maxn;
bool vis[N];
vector<int>G[N],T[N],scc[N];
stack<int>s;
queue<int>q;
void init(int n)
{
	memset(pre,0,sizeof(pre));
	memset(low,0,sizeof(low));
	memset(sccno,0,sizeof(sccno));
	memset(imp,0,sizeof(imp));
	memset(vis,0,sizeof(vis));
	memset(num,0,sizeof(num)); 
	maxn=dfs_clock=scc_cnt=0;
	for(int i=0;i<=n;i++)
	{
		G[i].clear();
		T[i].clear();
		scc[i].clear();
	}
	while(!s.empty())s.pop();
	while(!q.empty())q.pop();
}
void dfs(int u)
{
	//cout<<u<<endl;
	pre[u]=low[u]=++dfs_clock;
	s.push(u);
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(!pre[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!sccno[v])
		{
			low[u]=min(low[u],low[v]);
		}
	}
	if(pre[u]==low[u])
	{
		scc_cnt++;
		while(1)
		{
			int v=s.top();
			s.pop();
			sccno[v]=scc_cnt;
			scc[scc_cnt].push_back(v);
			if(v==u)break;
		}
	}
}
void find_scc(int n)
{
	for(int i=1;i<=n;i++)
	if(!pre[i])
		dfs(i);
}
void tupo()
{
	for(int i=1;i<=scc_cnt;i++)
	{
		if(!imp[i])
		q.push(i);
		num[i]=scc[i].size();
		maxn=max(maxn,num[i]);
	}
	int u,v;
	
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		for(int i=0;i<T[u].size();i++)
		{
			v=T[u][i];
			num[v]=max(num[v],num[u]+(int)scc[v].size());
			maxn=max(maxn,num[v]);
			imp[v]--;
			if(!imp[v])q.push(v);
		}
	}
}
int main()
{
	//freopen("1.txt","r",stdin);
	//freopen("2.txt","w",stdout);
	int t,n,m,u,v;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		//if(t==9)cout<<n<<" "<<m<<endl;
		init(n);
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			//if(t==9)cout<<u<<" "<<v<<endl;
			G[u].push_back(v);
		}
		
		find_scc(n);
		
		for(u=1;u<=n;u++)
		{
			for(int j=0;j<G[u].size();j++)
			{
				v=G[u][j];
				if(sccno[u]!=sccno[v])
				{
					imp[sccno[v]]++;
					T[sccno[u]].push_back(sccno[v]);
				}
			}
		}
		
		
		tupo();
		
		cout<<maxn<<endl;
	}
	return 0;
}

13.Now or later

UVALive - 3211
算法:dfs,2-SET问题,二分

  • 2-SET问题解决同差分约束系统一样,是解决给定条件后,判断条件是否能够满足的问题,只不过2-STE问题中是给定两个事件为真为假,(只要是事件有两种情况即可),建立反向变关系枚举事件可能的情况得到答案。
  • 本题中最后提到要求时间间隔的最小值应尽量大,很自然的想到是个二分,可以考虑枚举最小的时间间隔,然后作为条件两两个飞机建立落地的关系,2-SET问题。
  • 注意:初始化2-SET的的图时,应该初始化2*n;时间复杂度是O(n^2logT)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<stack>
#include<cstring>
#define N 2010 
using namespace std;
struct TwoSET{
	bool mark[N<<1];
	vector<int>G[N<<1];
	stack<int>s;
	int n;
	
	void init(int n)
	{
		this->n=n;
		memset(mark,0,sizeof(mark));
		for(int i=0;i<n*2;i++)G[i].clear();//注意初始化时应该初始化的点 
	}
	
	void add_clause(int x,int xval,int y,int yval)
	{
		x=x*2+xval;
		y=y*2+yval;
		G[x^1].push_back(y);
		G[y^1].push_back(x);
	}
	
	bool dfs(int u)
	{
		if(mark[u^1])return false;
		if(mark[u])return true;
		
		mark[u]=true;
		s.push(u);
		
		for(int i=0;i<G[u].size();i++)
		if(!dfs(G[u][i]))return false;
		
		return true;
	}
	
	bool solve()
	{
		for(int i=0;i<n*2;i+=2)
		{
			if(!mark[i]&&!mark[i+1])
			{
				while(!s.empty())s.pop();
				if(!dfs(i))
				{
					while(!s.empty())
					{
						mark[s.top()]=false;
						s.pop();
					}
					if(!dfs(i+1))return false;
				}
			}
		}
		return true;
	}
};
int n,T[N][2];
TwoSET solver;

bool test(int diff)
{
	solver.init(n);
	for(int i=0;i<n;i++)for(int a=0;a<2;a++)
		for(int j=i+1;j<n;j++)for(int b=0;b<2;b++)
		if(abs(T[i][a]-T[j][b])<diff)solver.add_clause(i,a^1,j,b^1);
	return solver.solve(); 
}

int main()
{
	int l,r;
	while(scanf("%d",&n)==1&&n)
	{
		l=r=0;
		
		for(int i=0;i<n;i++)
		for(int j=0;j<2;j++)
		{
			scanf("%d",&T[i][j]);
			r=max(r,T[i][j]);
		}
		while(l<r)
		{
			int mid=(l+r+1)>>1; 
			if(test(mid))l=mid;
			else r=mid-1;
		}
		
		cout<<r<<endl;
	}
	return 0;
} 

14.Astronauts

UVALive - 3713
算法:2-SET
注释:

  • 典型的2-SET问题,设两个宇航员相互讨厌i和j,如果两人是同类型的,那么条件是 非xi||非xj ,如果不是同种类型的则应由两个条件 非xi||非xj 和 xi||xj
  • 时间复杂度分析,由于是DFS所以对时间贡献是递归和循环,所以时间复杂度为O(max(n,2m))
#include<iostream>
#include<cstdio>
#include<stack>
#include<vector>
#include<cstring>
#define N 100010
#define ll long long
using namespace std;

struct TwoSET{
	int n;
	bool mark[N<<1];
	vector<int>G[N<<1];
	stack<int>s;
	
	void init(int n)
	{
		this->n=n;
		memset(mark,0,sizeof(mark));
		for(int i=0;i<n*2;i++)G[i].clear();
	}
	
	void add_clause(int x,int xtype,int y,int ytype)
	{
		x=x*2;
		y=y*2;
		if(xtype==ytype)
		{
			//cout<<"相同"<<endl; 
			G[x].push_back(y^1);
			G[y].push_back(x^1);
			G[x^1].push_back(y);
			G[y^1].push_back(x);
		}
		else 
		{
			//cout<<"不相同"<<endl;
			G[y^1].push_back(x);
			G[x^1].push_back(y);
			
		}
	}
	
	bool dfs(int u)
	{
		if(mark[u^1])return false;
		if(mark[u])return true;
		
		mark[u]=true;
		s.push(u);
		
		for(int i=0;i<G[u].size();i++)
		if(!dfs(G[u][i]))return false;
		
		return true;
	}
	
	bool solve()
	{
		for(int i=0;i<2*n;i+=2)
		{
			if(!mark[i]&&!mark[i^1])
			{
				while(!s.empty())s.pop();
				if(!dfs(i))
				{
					while(!s.empty())
					{
						mark[s.top()]=false;
						s.pop();
					}
					if(!dfs(i+1))return false;
				}
			}
		}
		return true;
	}
};
TwoSET solver;
ll age[N],type[N],n,m;

int main()
{
	int x,y;
	while(scanf("%d%d",&n,&m)==2&&(n+m))
	{
		ll tot=0;
		for(int i=0;i<n;i++)
		{
			type[i]=0;
			scanf("%d",&age[i]);
			tot+=age[i];
		}
		solver.init(n);
		
		
		for(int i=0;i<n;i++)
		{
			if(age[i]*n>=tot)type[i]=1;
			else type[i]=2; 
		}
		
		for(int i=0;i<m;i++)
		{
			cin>>x>>y;
			solver.add_clause(x-1,type[x-1],y-1,type[y-1]);
		}
		if(solver.solve())
		{
			for(int i=0;i<n;i++)
			{
				if(solver.mark[i*2])
				printf((type[i]==1?"A\n":"B\n"));
				else if(solver.mark[i*2+1])
				printf("C\n");
			}
		}
		else printf("No solution.\n");
	}
	return 0;
} 

15.Claw Decomposition

UVA - 11396
算法:二分图判定
注释:

  • 每个爪子可以看做一个点加三条边,当一个点作为一个爪的时候,相邻的点不能作为爪,当一个点不作为爪的时候,相邻的点必须作为爪,典型的二分图问题。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#define N 310
using namespace std;
vector<int>G[N];
int n,color[N];
void init(int n)
{
	for(int i=0;i<=n;i++)
	{
		G[i].clear();
		color[i]=0;
	}
}
bool bipartite(int u)
{
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(color[v]==color[u])return false;
		if(!color[v])
		{
			color[v]=3-color[u];
			if(!bipartite(v))return false;
		}
	}
	return true;
}
int main()
{
	//freopen("1.txt","w",stdout);
	int u,v;
	while(scanf("%d",&n)&&n)
	{
		init(n);
		while(scanf("%d%d",&u,&v)&&(u+v))
		{
			G[u].push_back(v);
			G[v].push_back(u);
		}
		color[1]=1;
		if(bipartite(1))printf("YES\n");
		else printf("NO\n");
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

601tyl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值