算法解决

大致题意:

有N只奶牛,其中奶牛A认为奶牛B备受注目,而奶牛B也可能认为奶牛C备受注目。奶牛们的这种“认为”是单向可传递的,就是说若奶牛A认为奶牛B备受注目,但奶牛B不一定会认为奶牛A备受注目。而当A认为B备受注目,且B认为C备受注目时,A一定也认为C备受注目。

       现在给出M对这样的“认为...备受注目”的关系对,问有多少只奶牛被除其本身以外的所有奶牛关注。

 

解题思路:

极大强连通分量+缩点。

 

发现自从用Tarjan算法做了POJ2942之后,这些利用Tarjan算法的题目都是水题。

 

构造模型:

N个顶点的有向图G,有M条边。求一共有多少个点,满足这样的条件:所有其它的点都可以到达这个点。

 

首先,这个题的N和M都非常大,暴搜肯定TLE。

考虑一下,如果图G是一棵有向树,那么问题就变的很简单了,因为当且仅当这棵树只有一个叶子结点(出度为0的点)时,树上的其他所有结点都能到达这个点。而当有向树上有1个以上的叶子时,都是无解的。

由于树是无环的,下面成这样的一棵有向树为 有向无环树DAG

 

那么我们能否把图转化为树去求解呢?

 

首先可以想到的是,如果图G中包含有环,那么就可以把这个环缩成一个点,因为环中的任意两个点可以到达,环中所有的点具有相同的性质,即它们分别能到达的点集都是相同的,能够到达它们的点集也是相同的。

 

那么是否只有环中的点才具有相同的性质呢?进一步的考虑,图中的每一个极大强连通分量中的点都具有相同的性质。所以,如果把图中的所有极大强连通分量求出后,对每个极大强连通分量缩点,就可以把图收缩成一棵有向无环树DAG,那么只要判断出度为0的缩点是否只有1个,若DAG中有且仅有1个这样的缩点,则输出缩点(图G的极大强连通分量)内所包含的图G的结点个数,问题就解决。

 

预备知识:Tarjan算法求有向图的极大强连通分量。

 

相关知识点传送门:

有关图论的知识点的定义:http://www.byvoid.com/blog/biconnect/

Tarjan算法入门基础:

http://hi.baidu.com/lydrainbowcat/blog/item/42a6862489c98820c89559f3.html

 

补充一个小内容:

用Tarjan算法求极大强连通分量时,对于有向边s->t

1、  若DFN[t]==0,则s->t是一条树边,t尚未入栈;

2、  若DFN[t]<DFN[s],当t在栈中,s->t为一条后向边;当t已经出栈,s->t为一条横叉边。

注意只有有向图有横叉边,无向图不存在横叉边的概念。

对横叉边的处理:无视掉。

 

Source修正

USACO 2003 Fall

http://www.4ucode.com/Study/Topic/1377469

 

 

[cpp]  view plain copy
  1. //Memory Time   
  2. //2116K  266MS   
  3.   
  4. #include<iostream>  
  5. #include<stack>  
  6. using namespace std;  
  7.   
  8. /*图G的结点的存储结构*/  
  9. class Node  
  10. {  
  11. public:  
  12.     int id;  
  13.     class Node* next;  
  14.     Node():id(0),next(0){}  
  15. };  
  16.   
  17. /*缩点(极大强连通分量)的存储结构*/  
  18. class Shrink_point  
  19. {  
  20. public:  
  21.     int in;     //缩点入度  
  22.     int out;    //缩点出度  
  23.     int num;    //缩点内含有图G的结点的个数  
  24.     Shrink_point():in(0),out(0),num(0){}  
  25. };  
  26.   
  27. /*******************************************************/  
  28.   
  29. class solve  
  30. {  
  31. public:  
  32.     solve(int n,int m):N(n),M(m)  
  33.     {  
  34.         Initial();  
  35.         Input_Creat();  
  36.   
  37.         /*注意:有向图G不一定从任何位置开始搜索都能遍历所有点*/  
  38.         for(int i=1;i<=N;i++)  
  39.             if(DFN[i]==0)  
  40.             {  
  41.                 stack_Node.push(i);     //搜索起点入栈  
  42.                 Status[i]=1;  
  43.                 Tarjan(i);  
  44.             }  
  45.   
  46.         printf("%d\n",solution());  
  47.   
  48.     }  
  49.     ~solve()  
  50.     {  
  51.         delete[] DFN;  
  52.         delete[] Low;  
  53.         delete[] Status;  
  54.         delete[] SCC;  
  55.         delete[] sp;  
  56.   
  57.         EmptyList();  
  58.   
  59.         while(!stack_Node.empty())  
  60.             stack_Node.pop();  
  61.     }  
  62.   
  63.     int min(int a,int b) const{return a<b?a:b;}  
  64.   
  65.     void Initial(void);     //申请存储空间并初始化  
  66.     void Input_Creat(void); //输入并构造有向图G  
  67.     void Tarjan(int s);     //寻找图G的所有极大强连通分量  
  68.     int solution(void);     //若缩点图只有1个出度为0的缩点,返回缩点内包含的结点数。否则无解,返回0  
  69.   
  70.     void DelLink(Node* p);  //释放以p为表头的整条链  
  71.     void EmptyList(void);   //释放邻接链表  
  72.   
  73. protected:  
  74.     int N;                  //the number of cows  
  75.     int M;                  //the number of popular pairs  
  76.     Node** LinkHead;        //邻接链表表头  
  77.   
  78.     int TimeStamp;          //(外部)时间戳  
  79.     int* DFN;               //DFN[u]: 结点u的搜索次序(时间戳)  
  80.     int* Low;               //Low[u]: 结点u或u的子树能够追溯到的最早的栈中结点的次序号  
  81.   
  82.     stack<int>stack_Node; //辅助栈,用于寻找极大强连通分量  
  83.     int* Status;            //Status[i]-> 0:i未入栈  1:i在栈中  2:i已出栈  
  84.     int* SCC;                 
  85.     int SCC_id;             //SCC[i]=SCC_id  图G中结点i所属的极大强连通分量(缩点)的编号为SCC_id  
  86.     Shrink_point* sp;       //存储每个缩点(极大强连通分量)的信息  
  87. };  
  88.   
  89. void solve::Initial(void)  
  90. {  
  91.     LinkHead=new Node*[N+1];  
  92.     for(int i=1;i<=N;i++)  
  93.         LinkHead[i]=0;  
  94.   
  95.     TimeStamp=0;  
  96.     DFN=new int[N+1];  
  97.     Low=new int[N+1];  
  98.     memset(DFN,0,sizeof(int)*(N+1));  
  99.     memset(Low,0,sizeof(int)*(N+1));  
  100.   
  101.     SCC_id=0;  
  102.     SCC=new int[N+1];  
  103.     Status=new int[N+1];  
  104.     memset(Status,0,sizeof(int)*(N+1));  
  105.   
  106.     sp=new Shrink_point[N+1];  
  107.   
  108.     return;  
  109. }  
  110.   
  111. void solve::Input_Creat(void)  
  112. {  
  113.     int a,b;  
  114.     for(int j=1;j<=M;j++)  
  115.     {  
  116.         scanf("%d %d",&a,&b);  
  117.   
  118.         if(!LinkHead[a])  
  119.             LinkHead[a]=new Node;  
  120.   
  121.         Node* tmp=LinkHead[a]->next;  
  122.         LinkHead[a]->next=new Node;  
  123.         LinkHead[a]->next->id=b;  
  124.         LinkHead[a]->next->next=tmp;  
  125.     }  
  126.     return;  
  127. }  
  128.   
  129. void solve::Tarjan(int s)  
  130. {  
  131.     DFN[s]=Low[s]=++TimeStamp;  
  132.     if(LinkHead[s])  
  133.     {  
  134.         for(Node* p=LinkHead[s]->next;p;p=p->next)  
  135.         {  
  136.             int t=p->id;  
  137.             if(DFN[t]<DFN[s])  
  138.             {  
  139.                 if(DFN[t]==0)           //s->t为树枝边  
  140.                 {  
  141.                     stack_Node.push(t);  
  142.                     Status[t]=1;  
  143.   
  144.                     Tarjan(t);  
  145.                     Low[s]=min(Low[s],Low[t]);  
  146.                 }  
  147.                 else if(DFN[t]!=0 && Status[t]==1)  //s->t为后向边  
  148.                 {  
  149.                     Low[s]=min(Low[s],DFN[t]);  
  150.                 }  
  151.             }  
  152.         }  
  153.     }  
  154.     if(DFN[s]==Low[s])  //找到极大强连通分量  
  155.     {  
  156.         SCC_id++;  
  157.         for(int node=stack_Node.top();;node=stack_Node.top())  
  158.         {  
  159.             stack_Node.pop();  
  160.             Status[node]=2;  
  161.             SCC[node]=SCC_id;  
  162.             sp[ SCC_id ].num++;  
  163.   
  164.             if(node==s || stack_Node.empty())  
  165.                 break;  
  166.         }  
  167.     }  
  168.     return;  
  169. }  
  170.   
  171. int solve::solution(void)  
  172. {  
  173.     /*计算所有缩点的入度和出度*/  
  174.   
  175.     for(int i=1;i<=N;i++)  
  176.         if(LinkHead[i])  
  177.         {  
  178.             for(Node* p=LinkHead[i]->next;p;p=p->next)  
  179.             {  
  180.                 int j=p->id;  
  181.                 if(SCC[i]!=SCC[j])  
  182.                 {  
  183.                     sp[ SCC[i] ].out++;  
  184.                     sp[ SCC[j] ].in++;  
  185.                 }  
  186.             }  
  187.         }  
  188.   
  189.     /*寻找出度为0的缩点*/  
  190.   
  191.     int cnt=0;      //记录出度为0的缩点个数  
  192.     int pk;         //记录出度为0的缩点编号  
  193.   
  194.     for(int k=1;k<=SCC_id;k++)  
  195.         if(sp[k].out==0)  
  196.         {  
  197.             cnt++;  
  198.             pk=k;  
  199.         }  
  200.     if(cnt!=1)          //出度为0的缩点的个数不为1,本题无解  
  201.         return 0;  
  202.       
  203.     return sp[pk].num;  //返回出度为0的缩点所包含图G中的结点个数  
  204. }  
  205.   
  206. void solve::DelLink(Node* p)  
  207. {  
  208.     if(p->next)  
  209.         p=p->next;  
  210.     delete[] p;  
  211.     return;  
  212. }  
  213.   
  214. void solve::EmptyList(void)  
  215. {  
  216.     for(int i=1;i<=N;i++)  
  217.         if(LinkHead[i])  
  218.             DelLink(LinkHead[i]);  
  219.     return;  
  220. }  
  221.   
  222. int main(void)  
  223. {  
  224.     int n,m;  
  225.     while(scanf("%d %d",&n,&m)!=EOF)  
  226.         solve poj2186(n,m);  
  227.   
  228.     return 0;  
  229. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值