【集合划分】解题报告

集合划分(Partition)

[问题描述]   

 

    给定一个集合X = {x1, x2, x3…xn}。

    定义函数D[xu, xv]:D[xu, xv]= D[xv, xu]且D[xu, xu] = 0。

    一个partition是指一种将X划分为K个不相交的子集T = (C1, C2…CK)。CP是X的一个非空子集。

定义一个partition的费用Cost(T) = min{D[u, v]},其中u属于Cp、v属于Cq且有p <>q。

[编程任务]

给定N、K和D,求一个划分使其费用最大。

[输入文件]input.txt。

N  K

然后一个N * N的矩阵,第i行j列描述D[i, j]。

[输出文件]output.txt。

你所找到的最大费用。

[样例输入输出]

Input.txt

4 3

0 1 2 3

1 0 2 3

2 2 0 3

3 3 3 0

 

Output.txt

2

[数据约定]

1 < k <= n <= 200

0 <= D[u, v] <= 32000

对于50%的数据满足k <= 10

 

 

 

 

 

 

这道题的模型和关押罪犯几乎一样。关押罪犯两种方法,贪心+并查集,类似于kruskal;二分+染色。当时我同时用了二分和并查集,结果就不知道该怎么做了。

       今天做集合划分又同时用了二分和并查集,WA10。其实考试的时候完全没有找到方向,以为关键是线性表划分,所以想用dfs进行分组,判断二分结果是否可行,但是后来不知道怎么地就放弃了,然后改写成并查集。

       当时的思路是小于二分结果的边都必须在区间内,所以就用并查集把必须分在同一边的加入到并查集了,因为区间连续所以这两个点之间的所有点也加入到这个并查集。

       统计区间的数量就数森林中树的数量,数量大于或等于怎么怎么处理,小于又怎么处理。

       鉴于当时思路混乱,这个想法没多大验证的价值了。摒弃。

 

同样是二分+并查集的网上的代码

==================================================================

type
node=record
t,w:longint;
c:longint;
end;
var
fa:array[0..300] of longint;
d:array[0..300,0..300] of longint;
e:array[1..40000] of node;
i,j,n,k,m,et,tot,ans:longint;
procedure qsort(head,tail:longint);
var i,j:longint; x,temp:node;
begin
i:=head; j:=tail;
x:=e[(i+j) shr 1];
repeat
   while(e[j].c>x.c) do dec(j);
   while(e[i].c<x.c) do inc(i);
   if i<=j then
    begin
     temp:=e[i];
     e[i]:=e[j];
     e[j]:=temp;
     inc(i); dec(j);
    end;
until i>j;
if head<j then qsort(head,j);
if tail>i then qsort(i,tail);
end;

procedure init;
var i,j:longint;
begin
readln(n,k);

for i:=1 to n do for j:=1 to n do d[i,j]:=-1;
for i:=1 to n do
for j:=1 to n do read(d[i,j]);

et:=0;
for i:=1 to n do
for j:=i+1 to n do
   begin inc(et); e[et].t:=i; e[et].w:=j; e[et].c:=d[i,j]; end;

qsort(1,et);
end;

function getfather(x:longint):longint;
begin
if fa[x]=x then exit(x);
fa[x]:=getfather(fa[x]);
exit(fa[x]);
end;

procedure union(x,y:longint);
var fx,fy:longint;
begin
fx:=getfather(x);
fy:=getfather(y);
if fx<>fy then
   fa[fy]:=fx;
end;

procedure check(mid:longint);
var i,j:longint;
begin

for i:=1 to n do fa[i]:=i;

for i:=1 to mid-1 do
union(e[i].t,e[i].w);

tot:=0;

for i:=1 to n do
if fa[i]=i then inc(tot);
end;

procedure work;
var i,j:longint; head,tail,mid:longint;
begin
tot:=0; ans:=0;
head:=1; tail:=et;
mid:=(1+et) shr 1;
check(mid);
while head<=tail do
begin

if tot<k then
    begin
     tail:=mid-1;
     mid:=(head+tail) shr 1;
     check(mid);
    end;

if tot>=k then
    begin
     if tot=k then
     begin
     if e[mid].c>ans then ans:=e[mid].c; end;
     head:=mid+1;
     mid:=(head+tail) shr 1;
     check(mid);
    end;

end;
writeln(ans);
end;
begin
assign(input,'input.txt'); reset(input);
assign(output,'output.txt'); rewrite(output);
init;
work;
close(input);
close(output);
end.


=====================================================================

另外一种思路:贪心的想法

       首先求最小生成树,然后在最小生成树中删边,删去k条较大的边

       或者说是加入n-k条较小的边。

连一条边表示将两个元素分在同一个集合中,又因为不存在环(并查集),加入n-k条之后就不能再加了,剩下的边都是集合之间的边,找到最大那一条就行了。

#include <cstdio>

 

struct ftv

{

       longf;

       longt;

       longv;

};

ftv bian[40002];

long top=0;

ftv tmp[40002];

long n;long k;

long m = 0;

long fa[40002];

 

long getroot(long a)

{

       if(fa[a]==a) return a;

       returnfa[a]=getroot(fa[a]);

}

 

void mer(long a,long b)

{

       fa[getroot(a)]=getroot(b);

}

 

void insert(long a,long b,long c)

{

       bian[++top].f= a;

       bian[top].t= b;

       bian[top].v= c;

}

 

void merge(long l,long m,long r)

{

       longtop1 = l;

       longtop2 = m+1;

      

       for(long i=l;i<r+1;i++)

       {

              if(top1<m+1&&(top2>r||bian[top2].v>bian[top1].v))

                     tmp[i]= bian[top1++];

              else

                     tmp[i]= bian[top2++];

      

       }

       for(long i=l;i<r+1;i++)

       {

              bian[i]= tmp[i];

       }

}

 

void merge_sort(long l,long r)

{

       if(l>=r) return;

       longm = (l+r)>>1;

       merge_sort(l,m);

       merge_sort(m+1,r);

       merge(l,m,r);

}

 

int main()

{

       freopen("partition.in","r",stdin);

       freopen("partition.out","w",stdout);

       scanf("%ld%ld",&n,&k);

       for(longi=1;i<n+1;i++)

       {

              fa[i]= i;

              for(long j=1;j<n+1;j++)

              {

                     longc;

                     scanf("%ld",&c);

                     if(i>=j) continue;

                     insert(i,j,c);

              }

       }

       m= n*n-n;

       m/=2;

       merge_sort(1,m);

      

       longj = 1;

       for(long i=1;i<n+1-k;i++)

       {

              while(getroot(bian[j].f)==getroot(bian[j].t))j++;

              mer(bian[j].f,bian[j].t);

              j++;

       }

       while(getroot(bian[j].f)==getroot(bian[j].t))j++;

       printf("%ld",bian[j].v);

      

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值