3 条题解
-
1
P10949 四叶草魔杖
来源:算法进阶图论(练习):四叶草魔杖
解法说明:
提示:此题样例应输出 。
根据题目的数据范围,不难想到可以用状态压缩来解。
可是两个宝石能量的传递应该如何解决?我们把题目简化为在宝石 和宝石 之间传递能量的代价为 ,结果发现这和最小生成树的模版题很像。
我们定义一个数组, 表示当前宝石集合为 传递完毕时花费的最小代价。其中 是一个二进制数,它的第 位为 代表着 集合中有宝石 。
状态转移就应该是:
其中 和 是 的子集, 为 。 表示 集合中宝石传递完毕的最小代价,可以用最小生成树计算出 的值。
可是我们发现:把每个宝石集合都计算一遍最小生成树,最后计算出来的总最小代价却有可能不是最优的。且最初的图不一定连通,一个个求最小生成树会无端多出很多时间。
我们考虑只计算宝石能量值总和为 的集合,这样最后计算出的代价一定最优,且算法也更简洁。
还有点小细节,写在代码注释了。
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e5+10, inf=0x3f3f3f3f; struct node{int x, y, c;} e[N]; int s[N], id[N], a[N], d[N], n, fa[N], m, dp[N]; //数组含义见上面文字 bool cmp(node n1, node n2) {return n1.c<n2.c;} int findfa(int x) {return (fa[x]==x)? fa[x]: fa[x]=findfa(fa[x]);} int Kruskal(int S){ memset(fa, 0, sizeof(fa)); int ans=0; for(int i=1; i<=n; i++) if((1<<(i-1))&S) fa[i]=i; //只有宝石i在当前集合S中时才执行 for(int i=1; i<=m; i++){ int x=e[i].x, y=e[i].y, c=e[i].c; if((!((1<<(x-1))&S)) || (!((1<<(y-1))&S))) //不在当前集合对答案无意义 continue; int tx=findfa(x), ty=findfa(y); if(tx!=ty){ ans+=c; fa[tx]=ty; } } int flag=-1; for(int i=1; i<=n; i++) if((1<<(i-1))&S){ if(flag==-1) flag=findfa(i); else if(flag!=findfa(i)) return inf; //当前集合不连通,无解 } return ans; } int main(){ scanf("%d%d", &n, &m); for(int i=1; i<=n; i++) scanf("%d", &a[i]); for(int i=1; i<=m; i++){ scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].c); e[i].x++; e[i].y++; } if(m==0) {printf("0\n"); return 0;} //防止hack数据 memset(s, 0, sizeof(s)); for(int i=0; i<(1<<n); i++) for(int j=1; j<=n; j++) if((1<<(j-1))&i) s[i]+=a[j]; //s[i]存的是i集合中所有宝石的能量总和 int len=0; memset(d, 0x3f, sizeof(d)); sort(e+1, e+m+1, cmp); //排序之后就能保证是最小生成树 for(int i=1; i<(1<<n); i++) if(s[i]==0){ id[++len]=i; //当前集合i宝石的能量总和为0,记录一下 d[i]=Kruskal(i); } memset(dp, 0x3f, sizeof(dp)); dp[0]=0; for(int x=1; x<=len; x++){ int i=id[x]; for(int j=i; j; j=(j-1)&i) //这里的j枚举的是i所有少一个1的状态 //因为之前记录是从小到大,所以i的子集如果合法的话一定在i之前更新了,可以放心用 dp[i]=min(dp[i], dp[i^j]+d[j]); //状态转移 //虽然最后dp[j]<=d[j],但这里dp[j]有可能还没被更新,所以先用d[j] } if(dp[(1<<n)-1]==inf) printf("Impossible\n"); else printf("%d\n", dp[(1<<n)-1]); //所有宝石传递完毕的最小代价 return 0; }感谢观看。
"In bocca al lupo."
-
0
洛谷P10949 四叶草魔杖 [最小生成树]
#include<bits/stdc++.h> using namespace std; const int N=1e5+10, inf=0x3f3f3f3f; struct node{int x, y, c;} e[N]; int s[N], id[N], a[N], d[N], n, fa[N], m, dp[N]; //数组含义见上面文字 bool cmp(node n1, node n2) {return n1.c<n2.c;} int findfa(int x) {return (fa[x]==x)? fa[x]: fa[x]=findfa(fa[x]);} int Kruskal(int S){ memset(fa, 0, sizeof(fa)); int ans=0; for(int i=1; i<=n; i++) if((1<<(i-1))&S) fa[i]=i; //只有宝石i在当前集合S中时才执行 for(int i=1; i<=m; i++){ int x=e[i].x, y=e[i].y, c=e[i].c; if((!((1<<(x-1))&S)) || (!((1<<(y-1))&S))) //不在当前集合对答案无意义 continue; int tx=findfa(x), ty=findfa(y); if(tx!=ty){ ans+=c; fa[tx]=ty; } } int flag=-1; for(int i=1; i<=n; i++) if((1<<(i-1))&S){ if(flag==-1) flag=findfa(i); else if(flag!=findfa(i)) return inf; //当前集合不连通,无解 } return ans; } int main(){ scanf("%d%d", &n, &m); for(int i=1; i<=n; i++) scanf("%d", &a[i]); for(int i=1; i<=m; i++){ scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].c); e[i].x++; e[i].y++; } if(m==0) {printf("0\n"); return 0;} //防止hack数据 memset(s, 0, sizeof(s)); for(int i=0; i<(1<<n); i++) for(int j=1; j<=n; j++) if((1<<(j-1))&i) s[i]+=a[j]; //s[i]存的是i集合中所有宝石的能量总和 int len=0; memset(d, 0x3f, sizeof(d)); sort(e+1, e+m+1, cmp); //排序之后就能保证是最小生成树 for(int i=1; i<(1<<n); i++) if(s[i]==0){ id[++len]=i; //当前集合i宝石的能量总和为0,记录一下 d[i]=Kruskal(i); } memset(dp, 0x3f, sizeof(dp)); dp[0]=0; for(int x=1; x<=len; x++){ int i=id[x]; for(int j=i; j; j=(j-1)&i) //这里的j枚举的是i所有少一个1的状态 //因为之前记录是从小到大,所以i的子集如果合法的话一定在i之前更新了,可以放心用 dp[i]=min(dp[i], dp[i^j]+d[j]); //状态转移 //虽然最后dp[j]<=d[j],但这里dp[j]有可能还没被更新,所以先用d[j] } if(dp[(1<<n)-1]==inf) printf("Impossible\n"); else printf("%d\n", dp[(1<<n)-1]); //所有宝石传递完毕的最小代价 return 0; } -
0
【题解】[洛谷P10949] 四叶草魔杖 [最小生成树]-CSDN博客
#include<bits/stdc++.h> using namespace std; const int N=1e5+10, inf=0x3f3f3f3f; struct node{int x, y, c;} e[N]; int s[N], id[N], a[N], d[N], n, fa[N], m, dp[N]; //数组含义见上面文字 bool cmp(node n1, node n2) {return n1.c<n2.c;} int findfa(int x) {return (fa[x]==x)? fa[x]: fa[x]=findfa(fa[x]);} int Kruskal(int S){ memset(fa, 0, sizeof(fa)); int ans=0; for(int i=1; i<=n; i++) if((1<<(i-1))&S) fa[i]=i; //只有宝石i在当前集合S中时才执行</p>for(int i=1; i<=m; i++){ int x=e[i].x, y=e[i].y, c=e[i].c; if((!((1<<(x-1))&S)) || (!((1<<(y-1))&S))) //不在当前集合对答案无意义 continue; int tx=findfa(x), ty=findfa(y); if(tx!=ty){ ans+=c; fa[tx]=ty; } } int flag=-1; for(int i=1; i<=n; i++) if((1<<(i-1))&S){ if(flag==-1) flag=findfa(i); else if(flag!=findfa(i)) return inf; //当前集合不连通,无解 } return ans;} int main(){ scanf("%d%d", &n, &m); for(int i=1; i<=n; i++) scanf("%d", &a[i]); for(int i=1; i<=m; i++){ scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].c); e[i].x++; e[i].y++; }
if(m==0) {printf("0\n"); return 0;} //防止hack数据 memset(s, 0, sizeof(s)); for(int i=0; i<(1<<n); i++) for(int j=1; j<=n; j++) if((1<<(j-1))&i) s[i]+=a[j]; //s[i]存的是i集合中所有宝石的能量总和 int len=0; memset(d, 0x3f, sizeof(d)); sort(e+1, e+m+1, cmp); //排序之后就能保证是最小生成树 for(int i=1; i<(1<<n); i++) if(s[i]==0){ id[++len]=i; //当前集合i宝石的能量总和为0,记录一下 d[i]=Kruskal(i); } memset(dp, 0x3f, sizeof(dp)); dp[0]=0; for(int x=1; x<=len; x++){ int i=id[x]; for(int j=i; j; j=(j-1)&i) //这里的j枚举的是i所有少一个1的状态 //因为之前记录是从小到大,所以i的子集如果合法的话一定在i之前更新了,可以放心用 dp[i]=min(dp[i], dp[i^j]+d[j]); //状态转移 //虽然最后dp[j]<=d[j],但这里dp[j]有可能还没被更新,所以先用d[j] } if(dp[(1<<n)-1]==inf) printf("Impossible\n"); else printf("%d\n", dp[(1<<n)-1]); //所有宝石传递完毕的最小代价 return 0;}
- 1
信息
- ID
- 1477
- 时间
- 1000ms
- 内存
- 64MiB
- 难度
- 8
- 标签
- 递交数
- 124
- 已通过
- 20
- 上传者