这是一道典型的回溯法题目,第一次做的时候并没有做出来,在参考了网上的一些AC代码后经过修改,终于AC成功了。
下面先附上代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 10;
char letter[maxn];
int id[256];
char str[100];
int p[maxn];
int pos[maxn];
int vis[maxn];
int n;
int bestP[maxn];
int bindwidth;
int bestW;
int g[maxn][maxn];
void dfs(int cur)
{
if(cur == n)
{
bindwidth = 0;
for(int i = 0;i<n;i++)
for(int j = i+1;j<n;j++)
if(g[i][j]==1) bindwidth = max(abs(pos[i]-pos[j]),bindwidth);
if(bindwidth<bestW)
{
bestW=bindwidth;
memcpy(bestP,p,sizeof(p));
}
}
else
{
for(int i = 0;i<n;i++)
if(vis[i]==0)
{
p[cur]=i;
int ok = 1;
int unable = 0;
pos[p[cur]]=cur;
for(int j = 0;j<n;j++)//第一次剪枝
{
if(g[i][j]==1&&abs(pos[j]-pos[i])>=bestW)
{
ok = 0;
break;
}
}
for(int j = 0;j<n;j++)//第二次剪枝
{
if(g[i][j]==1)
{
int equal = 0;
for(int k = 0;k<cur;k++)
{
if(p[k]==j)
{
equal =1;
break;
}
}
if(equal == 0) unable++;
}
}
if(unable>=bestW) ok = 0;
if(ok)
{
vis[i]=1;
dfs(cur+1);
vis[i]=0;
}
}
}
}
int main()
{
while(scanf("%s",str)==1)
{
if(str[0]=='#') break;
n = 0;
memset(g,0,sizeof(g));
bindwidth = 0;
bestW = 0x7fffffff;
memset(vis,0,sizeof(vis));
for(char c = 'A';c<='Z';c++)
if(strchr(str,c)!=NULL)
{
letter[n]=c;
id[c]=n++;
}
int len = strlen(str);
int t;
for(int i = 0;i<len;i++)
{
t = i;i+=2;
while(i<len&&str[i]!=';')
{
g[id[str[t]]][id[str[i]]]=g[id[str[i]]][id[str[t]]]=1;i++;
}
}
dfs(0);
for(int i = 0;i<n;i++) printf("%c ",letter[bestP[i]]);
printf("-> %d\n",bestW);
}
return 0;
}
首先这个题目的注意要点有以下几个:
1)双向映射的程序写法,使用strchr函数进行letter和id数组的赋值,而且进行了潜在的排序操作。
2)图的表示,这里我用了邻接矩阵表示,便于进行剪枝的有关计算,这里在dfs中进行了两个剪枝的操作(根据书上的提示):
a)当当前访问的节点位置cur与前面的位置,之间的带宽大于等于当前最大带宽,剪枝;
b)当与当前访问节点相邻的不确定的节点数量大于等于当前带宽,剪枝。
网上的一些方法使用next_permutation()函数,在代码规模上较小,但是在剪枝情况上不如直接通过dfs回溯方法操作的好。当然这对算法的正确性并没有影响,但是作为训练,我依然使用了这种dfs的方法。
UVa1354
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int n = 6;
const int maxn = 1<<n;
int kcase;
double r;
int s;
double w[n];
double sumw[maxn];
struct Node{
double l,r;
Node(double ll,double rr):l(ll),r(rr){}
};
vector<Node> tree[maxn];
int vis[maxn];
int bitcount(int x) {
if (x == 0) return 0;
return bitcount(x / 2) + (x & 1);
}
void dfs(int u)
{
if(vis[u]) return;
vis[u]=1;
if(bitcount(u)==1)
{
tree[u].push_back(Node(0,0));
return;
}
for (int l = (u - 1)&u; l > 0; l = (l - 1)&u) //枚举二叉树的子树
{
int r = l^u;
dfs(l); dfs(r);
for (int i = 0; i < tree[l].size(); i++) {
for (int j = 0; j < tree[r].size(); j++) {
double ll = max(sumw[r] / (sumw[l] + sumw[r]) + tree[l][i].l, -sumw[l] / (sumw[l] + sumw[r]) + tree[r][j].l);
double rr = max(sumw[l] / (sumw[l] + sumw[r]) + tree[r][j].r, -sumw[r] / (sumw[l] + sumw[r]) + tree[l][i].r);
tree[u].push_back(Node(ll, rr));
}
}
}
}
double solve()
{
int root = (1<<s)-1;
double ans = -1;
dfs(root);
for(int i = 0;i<tree[root].size();i++)
{
double tmp = tree[root][i].l+tree[root][i].r;
if(ans<tmp&&tmp<r) ans = tmp;
}
if(ans==-1) printf("-1\n");
else printf("%.10lf\n",ans);
}
int main()
{
scanf("%d",&kcase);
while(kcase--)
{
scanf("%lf",&r);
scanf("%d",&s);
for(int i =0;i<s;i++) scanf("%lf",&w[i]);
memset(vis,0,sizeof(vis));
memset(tree,0,sizeof(tree));
for(int i = 0;i<(1<<s);i++)
{
sumw[i]=0;
for(int j = 0;j<s;j++) if(i&(1<<j)) sumw[i]+=w[j];
}
solve();
}
return 0;
}
这道题的注意点有以下几个:
1)二进制方法枚举子集
2)对树的节点构建一个表,每一行对应一个节点集合,每个元素对应每种情况下的左右节点。
3)二进制枚举二叉树的子树
以上两个题目的解答方法都借鉴了一些网上的解法,但在很多细节处与网上的代码有所不同。
谢谢大家的阅读。