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*有点感觉了,不用操心搜索细节是我这种懒人最喜欢的,现在再加紧积累如何构造矩阵和解决问题的经验吧。
附上代码
题目是说有一棵树,树上每个节点有一个男孩或女孩,每个男孩能保护距离为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; }
相关文章推荐
- HDU 4735 Little Wish~ lyrical step~(DLX , 重复覆盖)
- [DLX重复覆盖] hdu 4735 Little Wish~ lyrical step~
- hdu 4735 Little Wish~ lyrical step~ (2013成都网络赛H题)(重复覆盖)
- [ACM] HDU 2295 Radar (二分+DLX 重复覆盖)
- ACM练级日志:HDU 4288 离散化
- HDU 2295 Radar 重复覆盖 DLX
- hdu 3529 Bomberman - Just Search 重复覆盖+DLX 炸弹人游戏中,问最少需要放多少颗炸弹,才能够将所有的墙壁炸掉,其中,炸弹在同一时间引爆
- hdu 3498 whosyourdaddy 重复覆盖+DLX 每次攻击i和i的所有邻居,最少攻击多少人才能杀死所有敌人
- HDU 3656 DLX重复覆盖
- HDU 3335 Divisibility(DLX可重复覆盖)
- HDU 2295 Radar(重复覆盖,DLX)
- 【HDU】4735 Little Wish~ lyrical step~ 重复覆盖
- ACM练级日志:HDU 4274
- hdu 3498(DLX 重复覆盖)
- (中等) HDU 5046 Airport ,DLX+可重复覆盖+二分。
- hdu - 4979 - A simple math problem.(可重复覆盖DLX + 打表)
- ACM练级日志:HDU 4286 STL-双端队列 以及Presentaion Error
- hdu 2295 Radar(重复覆盖,二分+DLX)
- ACM练级日志:“不作死就不会死” --HDU 4283 & 4284
- hdu 2295 DLX重复覆盖