Showing posts with label OI. Show all posts
Showing posts with label OI. Show all posts

Wednesday, October 17, 2012

一个有关最小生成树的问题

题目来源:NOI导刊,NOIP模拟试题九

题目描述:输入一个联通的无向图,对于每条边,判断这条边是属于这张图的任意一个MST、至少一个MST还是不属于任何一个MST。对应输出"any","at least one","none"。

这个问题看上去有些复杂,不过,可以确定这一点:无向图中的桥一定在每个MST中。

接着,让我们从Kruskal算法的角度分析一下。Kruskal先把边按照边权递增排序,使用并查集记录每个点属于的联通分量,如果扫描到一条边的两个点不属于同一个联通分量,那么就把这条边加入MST中,并合并两个顶点所在的联通分量。

可见,对于同一个无向图,Kruskal给出的MST的结果很大程度上取决于排序的结果,如果使用稳定的排序算法对边表排序,那么Kruskal输出的结果是唯一的。如果使用随机化快排,当存在权值相等的边时,Kruskal给出的MST就不是一成不变的了。

我们就从这里下手。

新建一张空图G。在边表中找出所有边权为w的边,保存在list中,用Kruskal合并所有边权小于w的安全边。然后,依次扫描list中的所有边(i,j),如果i和j在同一个联通分量中,那么,显然这条边一定不属于任何一MST。否则,这条边就属于某个MST,并把这条边(两端点要改成原端点所属的联通分量)加入G中。

接着,用Tarjan算法求出G中所有的桥,这些桥必然存在任意一个MST。

按照上面的方法对所有w都运行一遍,就可以获得结果了。

代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define MAXN 100005
#define MAXE 100005

typedef struct node
{
   long v,ord;
   struct node *next;
}node;

typedef struct edge
{
   long u,v,w,ord;
}edge;

long N,M;
edge e[MAXE];

node *first[MAXN];
node space[MAXE * 3];
long empty;

long fa[MAXN];   //disjoint set
long dfsn[MAXN],low[MAXN];
long T;   //DFS time
int type[MAXE];

int ecmp(const void *a,const void *b)
{
   if(((edge *)a)->w > ((edge *)b)->w)    return 1;
   return -1;
}

inline void init_fa()
{
   long i;
   for(i = 1;i <= N;i ++)
       fa[i] = i;
}

long getfa(long x)
{
   if(fa[x] != x)
       fa[x] = getfa(fa[x]);
   return fa[x];
}

inline long getmin(long a,long b)
{
   if(a < b)    return a;
   return b;
}

void init()
{
   long i;
   scanf("%ld%ld",&N,&M);
   for(i = 0;i < M;i ++)
   {
       scanf("%ld%ld%ld",&e[i].u,&e[i].v,&e[i].w);
       e[i].ord = i;
   }
}

inline void addedge(long a,long b,long ord)
{
   space[empty].next = first[a];
   space[empty].v = b;
   space[empty].ord = ord;
   first[a] = space + empty;
   empty ++;
}

void tarjan(long x,long f)   //find bridges
{
   node *p;
   T ++;
   dfsn[x] = low[x] = T;
   for(p = first[x];p != NULL;p = p->next)
       if(p->ord != f)
       {
           if(dfsn[p->v] == 0)   //not visited yet
           {
               tarjan(p->v,p->ord);
               low[x] = getmin(low[x],low[p->v]);
               if(dfsn[x] < low[p->v])
                   type[p->ord] = 2;
           }
           else
               low[x] = getmin(low[x],dfsn[p->v]);
       }
}

void solve()
{
   long i,j,k,a,b;
   long addcnt = 0;
   qsort(e,M,sizeof(edge),ecmp);
   init_fa();
   for(i = 0;i < M && addcnt < N - 1;)
   {
       for(j = i;e[j].w == e[i].w;j ++);
       //e[i to j].w are the same
       for(k = i;k < j;k ++)
       {
           a = getfa(e[k].u);
           b = getfa(e[k].v);
           if(a != b)    //it's a safe edge
           {
               type[e[k].ord] = 1;   //in at least one MST
               first[a] = first[b] = NULL;   //make space for new graph
               dfsn[a] = dfsn[b] = low[a] = low[b] = 0;  //init for tarjan
           }
       }
       T = empty = 0;
       for(k = i;k < j;k ++)   //build new graph for tarjan to find bridges
       {
           if(type[e[k].ord] == 0)    continue;
           a = getfa(e[k].v);
           b = getfa(e[k].u);
           addedge(a,b,e[k].ord);
           addedge(b,a,e[k].ord);
       }
       for(k = i;k < j;k ++)
           if(dfsn[getfa(e[k].u)] == 0)  //not visited yet
               tarjan(getfa(e[k].u),-1);
       for(k = i;k < j;k ++)   //kruskal merge
       {
           a = getfa(e[k].u);
           b = getfa(e[k].v);
           if(a != b)
           {
               fa[a] = b;
               addcnt ++;
           }
       }
       i = j;
   }
}

void output()
{
   long i;
   for(i = 0;i < M;i ++)
   {
       switch(type[i])
       {
           case 1:printf("at least one\n");break;
           case 0:printf("none\n");break;
           case 2:printf("any\n");
       }
   }
}

int main()
{
   freopen("mst.in","r",stdin);
   freopen("mst.out","w",stdout);
   init();
   solve();
   output();
   fclose(stdout);
   return 0;
}

实际代码为了提高效率,重复利用了结果,并没有完全按照前面的方法设计程序。
起初本人图方便,建空图时直接memset整个first数组,导致最后三个数据超时,改成first[a] = first[b] = NULL就全部通过了。可见虽然memset速度很快,但如果只要重置某几个数值,还是直接赋值更好。

Tuesday, October 9, 2012

求一张图所有生成树中的最大边与最小边差的最小值

题目来源:常州曹文夏令营
题意:
输入一个N个点,M条边的无向图(可能有重边),输出在它所有的生成树中,最大边与最小边的差值的最小值。如果图不联通,输出-1.
输入格式:
第一行,两个用空格隔开的整数 N 和 M,分别表示顶点数和边数。
下面 M 行,每行 3 个数 u,v,w,表示 u 和 v 之间有一条权值为 w 的无向边。
数据范围:
N<=100,M<=3000,w<=10000

用搜索计算出有100个点的图的每个一生成树显然是会超时的。注意到题目只要求输出差值的最小值,可以想到用二分答案解决这个问题。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 102
#define MAXM 3003

typedef struct edge
{
   int u,v,w;
}edge;

int N,M;
int fa[MAXN];
int first[10000];
edge e[MAXM];

int ecmp(const void *a,const void *b)
{
   if(((edge *)a)->w > ((edge *)b)->w)    return 1;
   return -1;
}

void init()
{
   int i;
   scanf("%d%d\n",&N,&M);
   for(i = 0;i < M;i ++)
       scanf("%d%d%d\n",&e[i].u,&e[i].v,&e[i].w);
   qsort(e,M,sizeof(edge),ecmp);
   for(i = M - 1;i >= 0;i --)
       first[e[i].w] = i;
}

inline void init_union()
{
   int i;
   for(i = 1;i <= N;i ++)
       fa[i] = i;
}

int getfa(int x)
{
   if(fa[x] != x)
       fa[x] = getfa(fa[x]);
   return fa[x];
}

inline int allconnect()   //判断图是否联通
{
   int i,x;
   x = getfa(1);
   for(i = 2;i <= N;i ++)
       if(getfa(i) != x)    return 0;
   return 1;
}

int check(int diff)
{
   int i,j,x,y;
   for(i = 0;i < M;i ++)  //枚举起点
       if(first[e[i].w] == i)
       {
           init_union();
           for(j = i;e[j].w - e[i].w <= diff && j < M;j ++)
           {
               x = getfa(e[j].u);
               y = getfa(e[j].v);
               fa[x] = y;
           }
           if(allconnect())    return 1;
       }
   return 0;
}

void solve()
{
   int mid;
   int l,r;
   l = 0;
   r = e[M - 1].w - e[0].w;
   while(l < r)
   {
       mid = (l + r) / 2;
       //printf("%d\n",mid);
       if(check(mid))    r = mid;
       else    l = mid + 1;
   }
   printf("%d\n",l);
}

void check_graph()
{
   int i,x,y;
   init_union();
   for(i = 0;i < M;i ++)
   {
       x = getfa(e[i].u);
       y = getfa(e[i].v);
       fa[x] = y;
   }
   if(allconnect() == 0)
   {
       printf("-1\n");
       fclose(stdout);
       exit(0);
   }
}

int main()
{
   freopen("span.in","r",stdin);
   freopen("span.out","w",stdout);
   init();
   check_graph();   //检查图是否联通
   solve();   //二分求解
   fclose(stdout);
   return 0;
}

其实,还有一个更简洁的方法:把所有边排序,枚举i,对i~M-1这M条边做一次Kruskal。那么,结果就是每一次Kruskal求得的最小生成树的最大边和最小边的差值的最小值。


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;

const int MaxN=111;
int N,M,fa[MaxN];
struct node
{
   int x,y,len;
} e[MaxN*MaxN];


bool cmp(node a, node b)
{
   return a.len<b.len;
}

int getfather(int x)
{
   if (x==fa[x]) return x;
   else
   {
       fa[x]=getfather(fa[x]);
       return fa[x];
   }
}

int kruscal(int sta)
{
   for (int i=1; i<=N; ++i) fa[i]=i;
   int set_num=N;
   if (N<=1) return 0;
   for (int i=sta; i<=M; ++i)
   {
       int t1=getfather(e[i].x);
       int t2=getfather(e[i].y);
       if (t1!=t2)
       {
           --set_num;
           fa[t1]=t2;
           if (set_num==1) return e[i].len-e[sta].len;
       }
   }
   return 111111111;
}

         

int main()
{
   freopen("span.in","r",stdin);
   freopen("span.out","w",stdout);
   scanf("%d %d",&N,&M);
   int ans=111111111;
   for (int i=1; i<=M; ++i) scanf("%d %d %d",&e[i].x,&e[i].y,&e[i].len);
   sort(e+1,e+M+1,cmp);
   for (int i=1; i<=M; ++i)   //枚举起始边
         ans=min(ans,kruscal(i));
   if (ans==111111111) printf("-1\n");   //图不联通
   else printf("%d\n",ans);

   return 0;

}


第二个算法是建立在对Kruskal算法的深入理解上的。Kruskal是一个贪心算法,它加入的边的权值一定是不递减的。因此,若给定了起始边,Kruskal获得的生成树的最大边和最小边的差值一定是最小的。

Wednesday, September 12, 2012

USACO MAR11 Bovine Bridge Battle

Problem 8: Bovine Bridge Battle [Richard Ho, 2007]

Each of Farmer John's N (4 <= N <= 1,000) cows is patiently waiting
in the main pasture with cow i at point with integer coordinates
(X_i, Y_i) (-1,000,000,000 <= X_i <= 1,000,000,000; -1,000,000,000
<= Y_i <= 1,000,000,000).

The cows wish to form into groups of four in order to play Bridge,
their new favorite card game. Each group must satisfy an important
constraint: four cows are allowed to team up if and only if there
exists some point X somewhere in the plane (and not coincident with
any of the four points of the potential group of four) such that
rotating any of the group's cows 180 degrees about that point X
gives the position of some other cow in the group.

Please help the cows determine the number of sets of four cows that
can form a Bridge group.

By way of example, suppose eight cows are standing at eight points:

                  |
                 f*
                  |             a = (-3, 1)    e = (-1, 1)
           b*     |             b = (-2, 2)    f = ( 0, 3)
        a      e  |             c = (-3, 0)    g = ( 2, 0)
         *     *  |             d = (-2, 0)    h = ( 3, 0)
         c  d     |     g  h
---------*--*-----+-----*--*---------
                  |

Then the three legal sets of four cows are {a, b, e, d} (they rotate
around point (-2, 1)), {b, c, e, f} (around the point (-1.5, 1.5)),
and {c, d, g, h} (around (0,0)).

The supplied locations of the cows given are all distinct, although
they are supplied in no particular order. Furthermore, the answer
will fit into a signed 32-bit integer.

PROBLEM NAME: rotsym

INPUT FORMAT:

* Line 1: A single integer: N

* Lines 2..N+1: Line i+1 contains two space-separated integers: X_i
        and Y_i

SAMPLE INPUT (file rotsym.in):

8
-3 0
-2 0
-1 1
0 3
2 0
-3 1
3 0
-2 2

OUTPUT FORMAT:

* Line 1: A single integer that is the number of sets of 4 cows that
        form valid groups for bridge.

SAMPLE OUTPUT (file rotsym.out):

3


**********************************************************************

显然,直接4个for嵌套是绝对超时的。题目要求转180度后对称,那么,我们可以求出每两点之间连线的中点坐标,如果两个线段的中点坐标相等,那么它们上面的4点就满足条件。

由于计算中点坐标时需要用到除以2的操作,因此可能造成0.5出现。由于我们需要的不是中点坐标,只要中点坐标相等的线段,因此除以2可以直接忽略。

本人最初使用Hash表把点映射到正整数,然后用数组cnt[i]记录以标号i的点为中点的线段条数。那么答案就是Sigma{cnt[i] * (cnt[i] - 1) / 2}。不过Hash表消耗的内存较大,超过了16MB的限制。后来改用排序后扫描的办法,就顺利通过了。

06 
07 #include <stdio.h>
08 #include <stdlib.h>
09 #include <string.h>
10 #include <math.h>
11 
12 typedef struct
13 {
14     long x,y;
15 }POINT;
16 
17 int N;
18 POINT pos[1002];
19 POINT c[1002 * 1002 / 2];
20 
21 int pcmp(const void *a,const void *b)
22 {
23     POINT *m,*n;
24     m = (POINT *)a;n = (POINT *)b;
25     if(m->x > n->x)    return 1;
26     if(m->x == n->x && m->y > n->y)    return 1;
27     return -1;
28 }
29 
30 void solve()
31 {
32     long i,j;
33     long ans = 0;
34     long t = 0;
35     for(i = 0;i < N;i ++)
36         for(j = 0;j < i;j ++)
37         {
38             c[t].x = (pos[i].x + pos[j].x);
39             c[t].y = (pos[i].y + pos[j].y);
40             t ++;
41         }
42     //sort and then figure how many are the same
43     qsort(c,t,sizeof(POINT),pcmp);
44     long cnt = 1;
45     for(i = 1;i < t;i ++)   //be careful with cnt here
46     {
47         if(c[i].x == c[i - 1].x && c[i].y == c[i - 1].y)
48             cnt ++;
49         else
50         {
51             ans += cnt * (cnt - 1) / 2;
52             cnt = 1;
53         }
54     }
55     printf("%ld\n",ans);
56 }
57 
58 int main()
59 {
60     freopen("rotsym.in","r",stdin);
61     freopen("rotsym.out","w",stdout);
62     scanf("%d\n",&N);
63     int i;
64     for(i = 0;i < N;i ++)
65         scanf("%ld%ld\n",&pos[i].x,&pos[i].y);
66     solve();
67     fclose(stdout);
68     return 0;
69 }

USACO OPEN11 Learning Languages

Farmer John's N (2 <= N <= 10,000) cows, conveniently numbered 1..N,
are fluent in some M (1 <= M <= 30,000) languages, also conveniently
numbered from 1..M. Cow i can speak in K_i (1 <= K_i <= M) languages,
namely L_i1, L_i2,..., L_{iK_i} (1 <= L_ij <= M). FJ's cows aren't
THAT smart, so the sum of K_i over all cows i is at most 100,000.

Two cows can't directly talk to each other unless both speak a
common language. However, cows can pass messages along, translating
if necessary. In other words, cows A and B can have a conversation
if and only if there exists a sequence of cows T_1, T_2, ..., T_k
such that A and T_1 share a language, T_1 and T_2 share a language,
etc., and T_k and B share a language.

Farmer John wishes that his cows could be even more social, so he
wants all his cows to be able to socialize with any other cow. He
can buy books to teach any one of his cows any language he pleases.
Being a fairly frugal farmer, FJ wants to purchase the minimum
number of books necessary to enable all of his cows to speak to
each other. Help him determine:

    * The minimum number of books he must purchase

    * Any set of books assigned to cows in any order which will
      help him meet this goal; a program will grade your output.

By way of example, suppose there are three cows named Alberta,
Bessie, and Contessa along with three languages denoted as #1, #2,
and #3. Alberta can speak languages #2 and #3, Bessie can speak
language #2, and Contessa can speak language #1. Currently, Alberta
and Bessie can talk to each other, but Contessa is left alone.

                   #1   #2   #3
       Alberta           x    x
       Bessie            x
       Contessa     x

FJ wants to fix this situation, so he can buy Contessa a book to
teach her language #2. This will ensure all cows speak the same
language, so they can all communicate with one another.

Note that an alternate solution exists: instead, FJ could buy
Contessa a book to teach her language #3. Not all cows would speak
the same language, but this would still be a valid solution because
Contessa could communicate through Alberta (who also speaks language
#3) if she wants to talk to Bessie. Other alternatives exist, and any
valid alternate solution will also be accepted.

PROBLEM NAME: llang

INPUT FORMAT:

* Line 1: Two space-separated integers: N and M

* Lines 2..N+1: Line i+1 describes the languages that cow i can speak
        with (K_i)+1 space-separated integers: K_i, L_i1,
        L_i2,...,L_i{K_i}.

SAMPLE INPUT (file llang.in):

3 3
2 3 2
1 2
1 1

OUTPUT FORMAT:

* Line 1: A single integer that is the minimum number of books that FJ
        must purchase.

* Lines 2..B+1: Line i+1 contains two space-separated integers: the
        language id # and the id # of the cow to receive book i. If
        multiple solutions exist, print any one.

SAMPLE OUTPUT (file llang.out):

1
2 3

**********************************************************************
 
本人使用并查集解决这个问题。
先用M个链表保存会某个语言的牛的编号。然后把会相同语言的牛全部merge掉。最后
统计联通分量的个数ccnt。所需要的最少的书本数就是ccnt-1.
对于安排书本,本人是这样解决的。由上一步可知,不同联通分量之间一定不不存在
相同的语言。因此,任意选取一个被某个联通分量中的牛掌握的语言,然后把ccnt-1
本书分别安排到其他每个联通分量中。 
处理无向图联通性的问题经常使用到并查集。
 
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>
04 #define MAXN 100005
05
06 typedef struct node
07 {
08    struct node *next;
09    long v;
10 }node;
11
12 long N,M;
13 long fa[MAXN];   //并查集
14 node *head[30002] = {};   //链表
15 _Bool vis[MAXN] = {};    //辅助数组,判断某个联通分量是否被访问过
16
17 void add(long lang,long v)
18 {
19    node *p = malloc(sizeof(node));
20    p->v = v;
21    p->next = head[lang];
22    head[lang] = p;
23 }
24
25 long getfa(long x)
26 {
27    if(fa[x] != x)
28        fa[x] = getfa(fa[x]);
29    return fa[x];
30 }
31
32 void init()
33 {
34    scanf("%ld%ld\n",&N,&M);
35    long i,k,lang;
36    for(i = 1;i <= N;i ++)
37    {
38        scanf("%ld",&k);
39        while(k --)
40        {
41            scanf("%ld",&lang);
42            add(lang,i);
43        }
44    }
45 }
46
47 void solve()
48 {
49    long i,x,first;
50    long ccnt = 0;
51    node *p;
52    for(i = 1;i <= N;i ++)
53        fa[i] = i;
54    for(i = 1;i <= M;i ++)
55    {
56        if(head[i] != NULL)
57            x = getfa(head[i]->v);
58        for(p = head[i];p != NULL;p = p->next)
59            fa[getfa(p->v)] = x;
60    }
61    for(i = 1;i <= N;i ++)
62    {
63        x = getfa(i);
64        if(!vis[x])
65        {
66            vis[x] = 1;
67            ccnt ++;
68        }
69    }
70    printf("%ld\n",ccnt - 1);
71    for(x = 1;x <= M && head[x] == NULL;x ++);  //find the first lang known by at least one cow
72    first = getfa(head[x]->v);
73    memset(vis,0,sizeof(vis));
74    vis[first] = 1;
75    for(i = 1;i <= N;i ++)
76        if(!vis[getfa(i)])
77        {
78            vis[getfa(i)] = 1;
79            printf("%ld %ld\n",x,i);
80        }
81 }
82
83 int main()
84 {
85    freopen("llang.in","r",stdin);
86    freopen("llang.out","w",stdout);
87    init();
88    solve();
89    fclose(stdout);
90    return 0;
91 }