集合划分(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].cj;
if headi 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
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
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;ir||bian[top2].v>bian[top1].v))
tmp[i]= bian[top1++];
else
tmp[i]= bian[top2++];
}
for(long i=l;i=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=j) continue;
insert(i,j,c);
}
}
m= n*n-n;
m/=2;
merge_sort(1,m);
longj = 1;
for(long i=1;i