魔法森林(神奇的解法)

在一个魔法森林中,有n个节点(n<=50000),m条边(m<=100000),每个节点有两个值ai,bi,1<=ai,bi<=50000。有一个精灵要从节点1到达节点n,一个节点i可以经过的要求是它携带的两个值A,B可满足A>=ai,B>=bi,求min(A+B)。 本题目的标准解法是LCT(link-cut-tree),这里讨论一种基于搜索算法的解决方法,其编程复杂性和理解难度略优于LCT做法。 如果每个节点只有一个值ai,则本题是一道标准的简单动态规划: dp[i]=max(min(dp[j]),ai)map[i][j]=1 可以使用spfa或其他最短路算法实现。当每个节点的值从1个变为2个时,最容易想到的做法是,枚举其中一个值A,然后用spfa求最小的B,利用A+B更新答案。代码实现如下:

;int n,m,i,j,a,b,best=100001;struct nod{int nex,a,b;};vector<nod> lin[50005];int dp[50001],q[50005];bool vis[50001];int ans[50001];int spfa(int a){if (ans[a]!=0) return ans[a];memset(vis,0,sizeof(vis));memset(dp,-1,sizeof(dp));dp[1]=0;int head,tail;head=tail=1;q[1]=1;vis[1]=1;int now,xia,b;nod nex;while (head<=tail){now=q[head%50003];vis[now]=0;for (int j=0;j<lin[now].size();j++){nex=lin[now][j];xia=nex.nex;if (nex.a>a) continue;b=max(dp[now],nex.b);if (dp[xia]==-1 || b<dp[xia]){dp[xia]=b;if (vis[xia]==0){vis[xia]=1;tail++;q[tail%50003]=xia;}}}head++;}if (dp[n]==-1) ans[a]=-1; else ans[a]=dp[n]+a;if (dp[n]==-1) return -1;return dp[n]+a;}int main(){//freopen(“1.txt”,”r”,stdin);memset(ans,0,sizeof(ans));scanf(“%d%d”,&n,&m);for (int i=1;i<=n;i++) lin[i].clear();nod temp;for (int k=1;k<=m;k++){scanf(“%d%d%d%d”,&i,&j,&a,&b);//cout<<i<<‘ ‘<<j<<endl;if (i==j) continue;temp.nex=j; temp.a=a; temp.b=b;lin[i].push_back(temp);temp.nex=i;lin[j].push_back(temp);}if (spfa(50000)==-1){cout<<-1<<endl;return 0;}for (int i=1;i<=50000;i++){int temp=spfa(i);if (temp!=-1) best=min(best,temp);}cout<<best<<endl;}

这个做法很容易想到,但极限情况下需要做50000次spfa,只能得到25分,需要考虑其是否有优化点。(提交记录见) 优化1: 在起初时,需要枚举的区间是[1,50000]中的每一个A,假设在A=25000时,B=15000。最终答案ans必然满足ans<=40000,因此,A 在[40000,50000]这个区间不可能产生最优解,可以迅速剪去。同理,假设当前最优解best是30000,由于当A<=25000时,满足条件A的边数会比25000时有所减少,B必然会满足B>=15000, 因此,当A在[30000-15000,25000]=[15000,25000]这个区间时,也不可能产生最优解,可以剪去。 利用这个思路,可以使用dfs的思想去按照中点的顺序枚举每一个节点,思路如下: 1.搜索区间(l,r),首先对中点mid求其spfa后的结果temp,并更新全局当前最优解best。 2.搜索区间(l,min(mid,best-(temp-mid)))。 3.搜索区间(mid+1,min(r,best))。 实现的过程中需要注意使用dp[i]记录每一个spfa(i)的值,避免重复运算。

int dfs(int l,int r){//cout<<l<<‘ ‘<<r<<‘ ‘<<best<<endl;if (l==r){int temp=spfa(l);if (temp!=-1) best=min(best,temp);return 0;}if (l>r) return 0;int mid=(l+r)/2;int temp=spfa(mid);if (temp==-1){dfs(mid+1,r);return 0;}best=min(best,temp);lef=best-(temp-mid);dfs(l,min(lef,mid));dfs(mid+1,min(r,best));return 0;}

加上这个优化后,程序的效率有显著的提高,可以得到50分。(提交记录见) 优化2: 在spfa的过程中,会枚举每个节点i的每一条边,由于存边的时候是杂乱无序的,因此只能枚举每个节点所有的边。为了优化枚举的过程,我们可以将每个节点对应的边按照a的值从小到大排序,在spfa(a)的过程中,一旦枚举到某一个大于a的边时,就break掉,缩小的枚举的量。这个优化是针对spfa实现过程中的一个优化,但是效果显著,可以得到70分。(提交记录见) 优化3: 使用了spfa算法实现,当程序效率出现问题时,可以考虑spfa算法的两个优化,本处只考虑其中一种优化。当加入队尾的节点的距离比当前队首节点的距离大时,交换两个节点在队列中的位置。 if (dp[q[(head+1)%50003]]>dp[q[tail%50003]]) { swap(q[(head+1)%50003],q[tail%50003]); } 加上这个优化,本题已经取得97分,耗时3858ms,通过了NOI比赛时的所有正式数据,OJ附加数据中一组超时。(提交记录见) 优化4: 当A变化时,B随之变化的图像并不是连续的,而是一些离散的点,例如: 当A=1,2,3…20时,B=10000, 当A=21,22,23…30时,B=9500, … 即:当A变化时,B会在某些点产生突变,而不是随着A的变化连续变化。 那么,,对于一个搜索区间[l,r],如果spfa(l)==spfa(r),则不需要对这个区间进行枚举了。 在以上基础上应用此优化,本题中取得了97分,耗时2063ms,在之前的基础上效率得到了显著提升。(提交记录见) 优化5: 在搜索算法无论如何也不能在有限时间求出结果时,可以采用卡时的策略,在程序即将超过时间限制时,停止运算,将当前最优解输出,有一定概率得到正确的结果。 修改代码提交后,本题终于取得了100分,并通过了附加的多组数据,总耗时1758ms,在(提交记录见) 实际上,本题目在此基础上还有很多优化,例如:对A,B进行离散化,缩小枚举的范围;使用spfa算法的两个优化;把spfa算法改为最小生成树等。都可以使效率得到提升,请读者自行尝试。

/*AUTHOR:aqxPROG:魔法森林LANG:c++using namespace std;int cnt=0;int n,m,i,j,a,b,best=100001;struct nod{int nex,a,b;};vector<nod> lin[50005];int dp[50001],q[50005];bool vis[50001];int ans[50001];int cmp(nod x,nod y){return x.a<y.a;}inline int spfa(int a){if (ans[a]!=0) return ans[a];memset(vis,0,sizeof(vis));memset(dp,-1,sizeof(dp));dp[1]=0;int head,tail;head=tail=1;q[1]=1;vis[1]=1;int now,xia,b;nod nex;while (head<=tail){now=q[head%50003];vis[now]=0;if (dp[n]!=-1 && dp[now]>=dp[n]){head++;continue;}for (int j=0;j<lin[now].size();j++){nex=lin[now][j];xia=nex.nex;if (nex.a>a) break;b=max(dp[now],nex.b);if (dp[xia]==-1 || b<dp[xia]){dp[xia]=b;if (vis[xia]==0){vis[xia]=1;tail++;q[tail%50003]=xia;if (dp[q[(head+1)%50003]]>dp[q[tail%50003]]){swap(q[(head+1)%50003],q[tail%50003]);}}}}head++;}if (dp[n]==-1) ans[a]=-1; else ans[a]=dp[n]+a;if (dp[n]==-1) return -1;return dp[n]+a;}int dfs(int l,int r){cnt++;if (cnt>1000) return 0;//cout<<l<<‘ ‘<<r<<‘ ‘<<best<<endl;if (l==r){int temp=spfa(l);if (temp!=-1) best=min(best,temp);return 0;}if (l>r) return 0;int mid=(l+r)/2;int temp=spfa(mid);if (temp==-1){dfs(mid+1,min(r,best));return 0;}best=min(best,temp);//左端点 ll+temp//A的上限int lef=best-(temp-mid);if (spfa(l)-l!=spfa(mid)-mid) dfs(l,min(lef,mid));else{int temp=spfa(l);if (temp!=-1) best=min(best,temp);}if (spfa(r)-r!=spfa(mid)-mid) dfs(mid+1,min(r,best));return 0;}int main(){int zuida=0;//freopen(“1.txt”,”r”,stdin);memset(ans,0,sizeof(ans));scanf(“%d%d”,&n,&m);for (int i=1;i<=n;i++) lin[i].clear();nod temp;for (int k=1;k<=m;k++){scanf(“,&i,&j,&b,&a);//cout<<i<<‘ ‘<<j<<endl;if (i==j) continue;zuida=max(zuida,a);temp.nex=j; temp.a=a; temp.b=b;lin[i].push_back(temp);temp.nex=i;lin[j].push_back(temp);}for (int i=1;i<=n;i++){sort(lin[i].begin(),lin[i].end(),cmp);}if (spfa(50000)==-1){cout<<-1<<endl;return 0;}dfs(1,zuida);cout<<best<<endl;}

初初尝试着拥抱的人,一派新鲜幸福都来不及沉浸,

魔法森林(神奇的解法)

相关文章:

你感兴趣的文章:

标签云: