图论练习题

[NOIP2003]神经网络
1.题意看懂以后就是计算一下每一个入度0的点最终的状态,并且如果这个状态>0就输出出来,对于阈值,我们可以在一开始就对这些入度的0的点直接减去阈值。
2.然后就是一个拓扑排序的模型,因为我们要计算一个点的状态是需要这个点前面相连的所有点的状态而来,因此很容易想到拓扑排序。其次需要掌握链式前向星的存储方式,方便理解代码。
3.注意处理一些代码的细节,比如计算状态的时候,只有状态>0的点可以传递信号

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,p;

int din[1015],dout[1015];//入度和出度
int c[1015],u[1015];//各自的状态,阈值
vector<int>tp;//存放点的拓扑序
//---------链式前向星存图------
struct Edge{
    int to;
    int w;
    int next;
}edge[1015];
int head[1015],idx;

void add(int u,int v,int w)
{
    edge[idx].w=w;
    edge[idx].to=v;
    edge[idx].next=head[u];
    head[u]=idx++;
}
//----------------------------



void toposort()
{
   queue<int>q;
   for(int i=1;i<=n;i++) if(din[i]==0) q.push(i);
   while(q.size())
   {
       auto x=q.front();q.pop();
       tp.push_back(x);
       for(int i=head[x];i!=-1;i=edge[i].next)//链式前向星的遍历
       {
           if(--din[edge[i].to]==0) q.push(edge[i].to);
       }
       
   }
}



signed main()
{
 
   cin>>n>>p;
   for(int i=1;i<=n;i++)
   {
       cin>>c[i]>>u[i];
       if(!c[i]) c[i]-=u[i];
       //输出层可以直接减去这个阈值
   }
   
   
   //别忘记对head数组初始化
   memset(head,-1,sizeof head);
   
   
   while(p--)
   {
       int u,v,w;
       cin>>u>>v>>w;
        add(u,v,w);
       din[v]++;//别忘记入度和出度
       dout[u]++;
  
   }
   
   //对这些点进行拓扑排序
   toposort();
   
  
   //排完序后计算每点的状态
   for(int i=0;i<tp.size();i++)
   {
       int j=tp[i];
       if(c[j]>0){//只有大于0的状态才可以向其他神经元传递信号
       for(int k=head[j];k!=-1;k=edge[k].next)
       {
           c[edge[k].to]+=edge[k].w*c[j];
       }
       }
   }
   
 
   int fla=1;//检查神经元状态是否都为0
   for(int i=1;i<=n;i++)
   {
       //出度为0的点才是输出层,并且状态要大于0
       if(c[i]>0 and !dout[i]) cout<<i<<" "<<c[i]<<endl,fla=0;
   }
   if(fla) cout<<"NULL";
  
    
    
}

使用vector存图的代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef  pair<int,int> pii;

struct edge{
	int v;
	int w;
};
int n,p;
int c[1000];//存神经层的状态 
vector<edge>e[1000];
vector<int>tp;


int din[1000],dout[1000];
void toposort()
{
	queue<int>q;
	for(int i=1;i<=n;i++) if(din[i]==0) q.push(i);
	while(q.size())
	{
		auto x=q.front(); q.pop();
		tp.push_back(x);
		for(auto t:e[x]){
			if(--din[t.v]==0) q.push(t.v);
		}
	}
}



void solve()
{
	cin>>n>>p;
	for(int i=1;i<=n;i++)
	{
		int u;
		cin>>c[i]>>u;
		if(!c[i]) c[i]-=u;
	}
	
	for(int i=1;i<=p;i++){
		int u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		din[v]++;
		dout[u]++;//出度大于0才是输出层 
	}
	
	toposort();
	
	for(int i=0;i<tp.size();i++)
	{
		int j=tp[i];
		if(c[j]>0){
		
			for(int k=0;k<e[j].size();k++)
			{
				c[e[j][k].v]+=e[j][k].w* c[j];	
			} 
		}
	}
	
//	for(int i=0;i<n;i++) cout<<tp[i]<<" ";
	int flag=1;
	for(int i=1;i<=n;i++)
	{
		if(c[i]>0 and dout[i]==0) cout<<i<<" "<<c[i]<<endl, flag=0;
	}
	if(flag) cout<<"NULL";
}


signed main()
{
	int t=1;
//	cin>>t;
	while(t--) solve();
	
	
}

[NOIP2013]车站分级
1.因为火车靠站的级别是高于未靠站的,类似于拓扑序,但是怎么建立图就是个问题了,假设起点为s,终点为e,把途中所有停靠站向未停靠站连一条边,表示停靠站的级别高于未停靠站。

样例1

2.其次就是利用拓扑思想,一直维护入度为0的点即可,用dep数组记录每一个点的最大层数,最后取最大值即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,ans;
int din[1015];
int dep[1015];//存层数
int is[1015];//每一趟标记该站是否是停靠站
int a[1015];//a[i]为停靠站

int vis[1010][1015];//用来标记这条边是否已经参与过入度的计算
//vis[2][3]=1,就是点2和点3的度已经算过

//---------链式前向星存图------
int head[1015],idx;
struct Edge{
    int to;
    int next;
}e[1010100];//边不止1000条

void add(int u,int v)
{
    e[idx].to=v;
    e[idx].next=head[u];
    head[u]=idx++;
}

//----------------------------



void toposort()
{
   queue<int>q;
   for(int i=1;i<=n;i++) if(din[i]==0) q.push(i),dep[i]=1;
   
   while(q.size())
   {
       int t=q.front();q.pop();
       for(int i=head[t];i!=-1;i=e[i].next)
       {
           dep[e[i].to]=dep[t]+1;
          
           ans=max(ans,dep[e[i].to]);//取最大层数即可
           if(--din[e[i].to]==0) q.push(e[i].to);
           
       }
       
       
   }
    
}


signed main()
{
    cin>>n>>m;
    memset(head,-1,sizeof head);
    //如果用的是head[u]=idx++,这一步不可以少
    
    
    //---------计算每个点的入度-------------
    for(int i=0;i<m;i++)
    {
        memset(a,0,sizeof a);
        memset(is,0,sizeof is);
        
        int x; cin>>x;//x为停靠站的数量
        for(int j=0;j<x;j++) cin>>a[j],is[a[j]]=1;
        
        
        for(int j=a[0]+1;j<=a[x-1];j++)//我从0开始,所以应该是x-1
        {
            if(!is[j])
                for(int k=0;k<x;k++)//k为停靠站的下标
                {
                    if(!vis[j][a[k]])
                    {
                        vis[j][a[k]]=1;
                        din[j]++;//入度增加
                        add(a[k],j);//建边
                    }
                }

        }
        
    }
    //------------------------------------------------
    toposort();

    cout<<ans;
    
    
}

[HNOI2015]菜肴制作
1.我的理解就是找符合拓扑序,但是尽量让小的点序号排在前面的合法拓扑序,但是这道题似乎有贪心的证明其是一道反向拓扑的模版题,可以去洛谷搜索一下这个题的题解。
2.反向拓扑使用大根堆来维护,并且反向建边,反向输出,就能得到在所有排序方式中,让序号小的尽量排前(只要满足拓扑条件)

#include <bits/stdc++.h>
#define int long long
using namespace std;
int d,n,m,p;
int din[100005];//反向拓扑时,此时这里应该叫做出度
//---------链式前向星存图------
struct Edge{
    int to;
    int next;
}e[100005];

int tp[100005];//存放拓扑序列
int head[100005],idx;
void add(int u,int v)
{
    e[idx].to=v;
    e[idx].next=head[u];
    head[u]=idx++;
}

//----------------------------


//反向拓扑的模版-----------------------------
bool toposort()
{
    priority_queue<int>q;//大根堆
    for(int i=1;i<=n;i++)
    {
        if(!din[i]) q.push(i);//出度为0的点放入
    }
    
    while(q.size())
    {
        auto t=q.top(); q.pop();
        tp[++p]=t;
        for(int i=head[t];i!=-1;i=e[i].next)
        {
            if(--din[e[i].to]==0) q.push(e[i].to);
        }
        
    }
    
    if(p==n) return 1;
    return 0;
    
}

void solve()
{
    cin>>n>>m;
    idx=0,p=0;
    memset(head,-1,sizeof head);
    memset(tp,0,sizeof tp);
    memset(din,0,sizeof din);
    for(int i=0;i<m;i++)
    {
        int u,v;
        cin>>u>>v;
        add(v,u);//反向建边,反向拓扑
        din[u]++;//这里注意时din[u]++;
    }
    if(toposort()){
        for(int i=n;i>0;i--) cout<<tp[i]<<" ";//要反向输出
    }else cout<<"Impossible!";
    cout<<endl;
    
}

//----------------------------------------------------------------


signed main()
{
    cin>>d;
    while(d--) solve();
    
}

旅行
1.实际上题意让我找三个点的最短路的和尽量大,指的是这个点可以经过其他点到达另外一个点的话费加起来,比如样例1,7-3总权值为212,3-5权值为210,画出图来就知道这个数据怎么来的了。
2.本质上是枚举中转点,然后计算这个点到其他点的最短路的最大值和次大值加起来,然后枚举完所有的点找最大的即可。
3.注意这题傻瓜数据,数组需要开大点

#include <bits/stdc++.h>
#define int long long
using namespace std;
int d,n,m;
typedef pair<int,int> pii;
//---------链式前向星存图------
struct Edge{
    int w;
    int to;
    int next;
}e[2015];//傻瓜题,数据得开到两千左右才不会卡死

int head[1015],idx;
void add(int u,int v,int w)
{
    e[idx].w=w;
    e[idx].to=v;
    e[idx].next=head[u];
    head[u]=idx++;
}

//----------------------------
int dis[2015];
bool vis[2015];
int dijkstra(int x)
{
    //----对每个点跑都要重新初始化标记数组和距离数组
    for(int i=1;i<=n;i++) vis[i]=0;
    memset(dis,0x3f3f3f,sizeof dis);
    //------------------------------------------
    
    int max1=0,max2=0;
     priority_queue<pii>q;
     dis[x]=0;
     q.push({0,x});

     while(q.size())
     {
         auto t=q.top();q.pop();
         int u=t.second;

         if(vis[u]) continue;
         vis[u]=1;
                  max1=max(dis[u],max1);
         for(int i=head[u];i!=-1;i=e[i].next)
         {
             int id=e[i].to,w=e[i].w;
             if(dis[u]+w<dis[id]) {
                 dis[id]=dis[u]+w;
             q.push({-dis[id],id});
         }
         }
         if(max1>max2) swap(max1,max2);
     }


    if(max1==0) return -1;
    return max1+max2;
  
}


void solve()
{
   cin>>n>>m;
   
    memset(head,-1,sizeof head);
    idx=0;
   for(int i=0;i<m;i++)
   {
       int u,v,w; cin>>u>>v>>w;
       add(u,v,w);
       add(v,u,w);
   }
   
   int ans=-1;
   
   for(int i=1;i<=n;i++)
    {
        ans=max(ans,dijkstra(i));
    }
   
   
   cout<<ans<<endl;
   
   
   
}

//----------------------------------------------------------------


signed main()
{
    cin>>d;
    while(d--) solve();
    
}

posted on 2024-08-12 20:34  swj2529411658  阅读(4)  评论(0编辑  收藏  举报

导航