您的位置:首页 > 理论基础 > 计算机网络

ACM练级日志:HDU 4735(ACM 成都网络赛) 重复覆盖与DLX

2014-08-09 22:26 453 查看
今天费了一下午+一晚上的劲,终于把重复覆盖问题给解决了。作为这算法的牺牲品的就是成都网络赛让我知道DLX这东西存在的那道题,HDU 4735。这也是第一次尝试独立对问题构造矩阵然后调用DLX得出结果的题目。

题目是说有一棵树,树上每个节点有一个男孩或女孩,每个男孩能保护距离为D以及D以内的节点,你可以任意交换两个节点的孩子,最后要使得所有女孩都被保护。问最少需要交换多少次。实际上你会发现女孩没用,问题实际就是让你重新安排这些男孩的位置使得所有节点都被保护。

如果这么去想的话,这就是一个覆盖性问题,而且是一个重复覆盖问题。但是如果把行(决策)定义为“交换某两个点的人”就很困难。如果我们能够,不管怎么说,先弄出一种方案来的话,那么再计算需要交换多少次就方便了(对于本方案中的i号节点,如果已经有少年,那就什么也不用干,否则一定可以找到一个站错了地方的少年来这儿,ans+1即可);至于找到一种摆法,就是很容易的矩阵构造了:行=每个节点放一个男孩,列=每个节点是否被保护,提前预处理出每个节点能保护哪些节点,然后填一下矩阵,之后调用DLX求解重复覆盖问题即可。只不过需要注意的是,这里并不是求最小覆盖,最好的例子是:

1

5 1

1 0 1 0 1

1 2 1

2 3 1

3 4 1

4 5 1

答案是0,一个也不用动,但其实如果你直接用DLX,你会发现这幅图两个男孩就能解决问题,但是这不是我们要求的。

对于重复覆盖算法,说实在的我对其中DFS选定列以后为什么要把del操作放在循环里面很不解,想了一晚上也没有想出来,于是就先这么着吧,期待能有人赐教。

然后就是……DLX对付重复覆盖问题不像对付精确覆盖问题那么速度,所以一个A*是少不了的。我原来就没搞懂过A*,现在发现其中的h函数似乎就是目前状态到目标状态距离的一个【乐观】估计,如果你现在的代价加上这个乐观估计的代价还比我能接受的高,就直接return。这次代码里的Hash函数就是干这个的。感觉也并不是100%的弄懂了…… 只能说是懂了大意吧。

这个A*所干的事情是估计一下我还需要多少个少年才能填完,如果我有的少年数不够,就可以直接return了。另外在做的时候维护一下到现在为止的交换次数(只要选了一个上面没有少年的节点,代价就+1),如果代价已经>=ans_num,就可以return了。一开始没有加等号,就TLE,加了以后就900ms过了,其实不太明白为什么差别会这么大…… 之所以加等号是因为你已经不可能给答案做什么贡献了,所以应该直接回去。

两个模板在手,感觉对DLX熟悉不少,还对A*有点感觉了,不用操心搜索细节是我这种懒人最喜欢的,现在再加紧积累如何构造矩阵和解决问题的经验吧。

附上代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;

const int INF=2<<29;
const int MAXNUM=5000;

int u[MAXNUM], d[MAXNUM], l[MAXNUM], r[MAXNUM];//上下左右
int s[MAXNUM], col[MAXNUM];//s[i]:第i列有几个节点,即有几个1,col[i]能告诉你i这个节点在第几列

int head;//总表头,其实就是0号节点
int p_nodes;//目前用了多少节点

///////////INSERT UNIQUE THINGS HERE
int m;
int n;
int n_nodes;
int dis;
int t;
int row[MAXNUM];
int protect[60][60];
int p_protect[60];
int ans[60];
int boy_sum=0;
int child[60];
int ans_num;
int map[70][70];

///////////////////////////////////

void del(int c)//注意和精确模板的差别
{
for(int i=d[c]; i!=c; i=d[i])
{
l[ r[i] ] = l[i];
r[ l[i] ] = r[i];
s[ col[i] ] --;
}
return;
}

void resume(int c)//恢复上面的操作
{
for(int i=u[c]; i!=c; i=u[i])
{
s[ col[i] ] ++;
r[ l[i] ] =i;
l[ r[i] ] = i;
}
return;
}

int Hash()
{
int ret=0;
int hs[60]={0};
for(int c=r[head]; c!=head;c=r[c])
{
if(hs[c]==0)
{
hs[c]=1;
ret++;

for(int i=d[c]; i!=c; i=d[i])
{
for(int j=r[i]; j!=i; j=r[j])
{
hs[ col[j] ] =1;
}
}
}
}
return ret;
}

void DFS(int depth, int cost)
{
////TEST
//cout<<depth<<endl;
if(depth-1 + Hash() > boy_sum)
return;
if(cost >= ans_num)
return;

if(r[head] == head)
{
ans_num=min(cost, ans_num);
return;//矩阵被删干净了
}

int min1=INF, now;//挑一个1数最少列的先删
for(int t=r[head]; t!=head; t=r[t])
{
if(s[t]==0)
return;
if(s[t] <=min1 )
{
min1=s[t];
now=t;
}
}
//TEST
//cout<<"now: "<<now<<endl;

int i, j;
for(i=u[now]; i!=now; i=u[i])
{
del(i);
//↑注意和精确模板的差别
//枚举这一列每个1由哪行来贡献,这行即为暂时性的答案,如果需记录答案,此时ans[depth]=row[i]
ans[depth]=row[i];
if(child[ ans[depth] ] ==0)
cost++;

///TEST
//cout<<"ans[depth]: "<<ans[depth]<<endl;
for(j=r[i]; j!=i; j=r[j])
{
del(j);
}

DFS(depth+1, cost);

for(j=l[i]; j!=i; j=l[j])
{
resume(j);
}

if(child[ ans[depth] ] ==0)
cost--;

ans[depth]=0;
resume(i);
}

return;
}

void init()
{
memset(u,0,sizeof(u));
memset(d,0,sizeof(d));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
memset(s,0,sizeof(s));
memset(col,0,sizeof(col));

head=0;
p_nodes=0;

//INSERT UNIQUE THINGS HERE
memset(ans,0,sizeof(ans));
n=0;
memset(row,0,sizeof(row));
memset(protect, 0, sizeof(protect));
memset(p_protect,0,sizeof(p_protect));
boy_sum=0;
memset(child,-1,sizeof(child));

ans_num=INF;
for(int i=1;i<=66;i++)
for(int j=1;j<=66;j++)
map[i][j]=INF;
}

int insert_node(int row_first1, int j)//知道我当前的行第一个是谁,我在第j列
{
p_nodes++;
s[j] ++;
u[p_nodes] = u[j];
d[ u[j] ] = p_nodes;
u[j] = p_nodes;
d[p_nodes] = j;

col[p_nodes] = j;//和列的关系处理完毕

if(row_first1==-1)
{
l[p_nodes] = r[p_nodes] = p_nodes;
row_first1=p_nodes;
}
else
{
l[p_nodes] = l[row_first1];
r[ l[row_first1] ] = p_nodes;
r[p_nodes] = row_first1;
l[ row_first1 ]=p_nodes;//和行的关系处理完毕
}
row[p_nodes]=n;
return row_first1;

}

void insert_row(int idx)//新建一行,一行里会插入若干结点
{
n++;
int row_first1=-1;//看看这一行是不是已经有第一个1了

int i;
for(i=1; i<=p_protect[idx]; i++)
{
row_first1=insert_node(row_first1, protect[idx][i]);
}
return;
}

void InitLinks()//注意:该链表两个方向都是循环的,最上面的上面指最下面
{
int i;
r[head]=1;
l[head]=m;

for(i=1;i<=m;i++)//制作列的表头,使用结点1~m
{
l[i]=i-1;
r[i]=i+1;
if(i==m)
r[i]=head;
u[i]=i;
d[i]=i;
s[i]=0;
col[i]=i;
}

p_nodes=m;
for(i=1; i<=n_nodes; i++)
{
insert_row(i);
}
}

void OpenField(int ori, int now, int from, int rest)
{
int i;
for(i=1;i<=n_nodes; i++)
{
if(rest-map[now][i] >=0 && i != from)
{
p_protect[ori]++;
protect[ori][ p_protect[ori] ] = i;

OpenField(ori, i, now, rest-map[now][i]) ;
}
}
}

int main()
{
scanf("%d", &t);
for(int files=1; files<=t; files++)
{
init();
scanf("%d %d", &n_nodes, &dis);

int i;

for(i=1;i<=n_nodes; i++)
{
scanf("%d", &child[i]);
if(child[i]==1)
boy_sum++;
}

for(i=1;i<=n_nodes-1; i++)
{
int k1, k2, l;
scanf("%d %d %d", &k1, &k2, &l);
map[k1][k2]=l;
map[k2][k1]=l;
}

for(i=1;i<=n_nodes; i++)
{
p_protect[i]++;
protect[i][p_protect[i]] = i;
}

for(i=1;i<=n_nodes;i++)
OpenField(i,i,0,dis);

m=n_nodes;
InitLinks();
DFS(1,0);
printf("Case #%d: ", files);
if(ans_num==INF)
printf("-1\n");
else
printf("%d\n", ans_num);
}
system("pause");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: