双子树问题

这一天,随身带来的干粮豆吃玩了,必须有人去找些食物来吃才行。可Z4的懒惰是出了名的,谁会愿意去呢?经过一番商讨,大家最后决定以老规矩“猜十次”的方式决定人选。可怜的立方由于猜拳技术不过关,屡战屡败,只好踏上觅食的征途。
立方后来找到了两棵奇怪的树,由于它们的形态十分相像,双子座的立方一时兴起便把它们命名为“双子树”。可双子树的果实能不能吃呢?“管它呢,先摘了再说。”于是身手敏捷的立方便爬上其中一棵,摘下一个大大的果实。可就在此时,另一棵却有几个果实坠地。
立方细心一看,发现这双子树上的某些果实有一些细丝相连,只要摘下其中一棵树的某一个果实,另一棵树将会有相应的一个或多个(也可能没有)果实坠地而摔坏,这些果实都和摘下的果实用细丝相连。摔坏的果实当然不能吃了。而且,立方发现,那些细丝是没办法弄断的,除非摘下它两端的果实的其中一个。由于这时只有立方一人(好没义气的Z4啊……),他只能眼睁睁地看着它们摔坏。也就是说,立方无法同时摘取任一条细丝两端上的两个果实。于是,不同的摘法最后会得到不同数量的果实,而立方将会用随身带的一个容量为V(表示能装V个果实)的大袋子将它们装好。
馋嘴的立方当然希望摘得越多越好啦,那么,他最多可以得到多少个果实呢?
特别地,任两个果实间最多只会有一条细丝相连,同一棵树上的果实间不会有细丝相连,当袋子装满后,立方的口还可以塞进一个。

输入格式:

输入文件dobtree.in的第一行包括四个整数V(0<=V<=1000),N1,N2(0<=N1,N2<=500)和M,分别表示袋子的容量,第一棵树上的果实个数,第二棵树上的果实个数和细丝总数。为了方便计算,立方人为地把果实分别按1..N1和1..N2标号。
接下来有M行,每行有两个整数A,B(1<=A<=N1,1<=B<=N2),表示第一棵树上的果实A和第二棵树上的果实B有一条细丝相连。

输出格式:

输出文件dobtree.out仅有一个整数,表示立方最多能得到的果实个数。

样例输入:

10 3 3 5
1 2
2 1
2 2
2 3
3 2

样例输出:

4

数据范围:

样例1中,立方最后摘取了第一棵树上的1、3和第二棵树上的1、3。
样例2中,立方本来最多可以摘4个果实,但由于袋子容量仅为2,再加上他口中的一个,最后他只能得到3个。

时间限制:

1

空间限制:

256M

解题分析:

    就是一个裸的konig定理,就能够解决问题。

    从样例中发现,这些细丝不具有传递性。即若摘下A树甲果通过细丝影响到相连的B树乙果掉下,但不会通过乙果的细丝又传回到A树中与乙果相连的C掉下。

    很显然,A树和B树及细线组成了二分图。

    根据题意,A树和B树有细线相连的两个果子,不管如何摘, 至少要掉下一只。设本图最大匹配数为K,考虑到最大匹配中的每一对匹配都互相独立的,因此,至少要掉K只果子。

     另一方面,我们考察能否寻找到只掉K个果子的摘果方法。我们从A树考察开始摘果子甲果, 如果甲果是未匹配的果子,则影响B树的掉下的乙果一定是匹配了的果子,否则存在交错轨了。接着可推出与B树乙果相匹配的A树丙果,可以摘到。A树丙果不可能受到相连的B树摘下未匹配的丁果而掉下,因为这样又存在“甲---丁”交叉轨了。如果我们在A树摘下的是匹配的果子,则B树与它匹配的果子掉下。因此,我们全部摘下A树中未匹配的果子,只引起B树部分匹配的果子掉下,与这些果子匹配的A树果子保住了,全部摘下B树中未匹配的果子,只引起A树部分匹配的果子掉下,与这些果子匹配的A树果子保住了,A树与B树的一对匹配果子不可能都掉下,否则这对匹配果子都连上一个未匹配的果子,这就是交叉轨,当然不可能。

 综上分析,按上面摘法,所有未匹配的果子都可摘下,匹配的一对果子都能摘下一个,因此,存在摘法只掉K只果子。因引,最多可以得到果子数为果子总数-最大匹配数K

 显然答案是MIN果子总数-最大匹配数K,袋子容量+1)。

 算法复杂度为On^2)。

//双子树问题,THIS IS A MARICLE;
#include<bits/stdc++.h>
using  namespace  std;
int  n1,n2,v,m,d[1001],ans; //d  records it -> what in another tree;
bool  used[1001],way[1001][1001];
void  init()
{
     for ( int  i=1;i<=m;i++)
     {
         int  x,y;
         cin>>x>>y;
         way[x][y]=1;
     }
}
bool  find( int  x)
{
     if  (used[x])  return  0;
     for ( int  i=1;i<=n2;i++)
     {
         if  (way[x][i])
         {
             used[x]=1; //find will use it!!!
             if (d[i]==0||find(d[i]))
             {
                 d[i]=x;
                 return  1;
             }
         }
     }
     return  0;
}
int  main()
{
     cin>>v>>n1>>n2>>m;
     v++;
     init();
     for ( int  i=1;i<=n1;i++)
     {
         for ( int  k=1;k<=n1;k++)
         {
             used[k]=0;
         }
         if  (find(i)) ans++;
     }
     ans=n1+n2-ans;
     cout<<min(ans,v);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值