2019华工校赛 B - 修仙时在做什么?有没有空?可以来炼丹吗?
Posted clno1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2019华工校赛 B - 修仙时在做什么?有没有空?可以来炼丹吗?相关的知识,希望对你有一定的参考价值。
题目链接:https://ac.nowcoder.com/acm/contest/625/B
解法:这题其实就是求2^18个点内最近的两个点的距离。我们可以容易想到朴素解法:把每个点作为源点跑最短路取最小值。也很容易想到这个做法严重超时。
对于这种构图,这里有一个比较套路的方法:枚举2进制位数k,按二进制k位的0/1分成两组跑最短路。为什么是正确的,因为一旦两个数不等,那么这两个数必定在至少一个二进制位上不同,我们枚举了所有的二进制位,那么任意两个数都会被至少一次分到不同组中,亦即所有情况的最短路都考虑到了,那么当然是正确的。
这道题能得到一个启发:像这种对于一个很大很大的图,但是其实只有很少点是我们要计算的,我们可以考虑想出一种分组方式能使得任两个数都至少一次分到不同组,这就能减少时间而且不遗漏。
细节详见代码:
#include<bits/stdc++.h> #define int long long using namespace std; const long long P=19260817; const long long INF=(long long)1<<60; typedef long long LL; const int N=2e5+10; const int M=18; typedef pair<int,int> pii; int n,ans,a[N],cost[(1<<M)+10][M],d[(1<<M)+10],mark[(1<<M)+10]; bool vis[(1<<M)+10]; inline LL PowMod(LL a, LL b) { LL r=1; while(b) { if(b&1) r=r*a%P; a=a*a%P, b>>=1; } return r; } void prework() { for (int s = 0; s < (1<<18); ++s) { for(int i = 0; i < 18; ++i) { cost[s][i] = PowMod( max(s,s^(1<<i))%P, 1<<i ) % P + 1; } } } priority_queue<pii> q; void Dijkstra(int k) { while (!q.empty()) q.pop(); memset(d,0x3f,sizeof(d)); memset(vis,0,sizeof(vis)); for (int i=1;i<=n;i++) if (a[i]>>k & 1) { //把n个数字 分成2组 跑最短路 q.push(make_pair(0,a[i])); d[a[i]]=0; } while (!q.empty()) { pii x=q.top(); q.pop(); if (mark[x.second] && x.first!=0) ans=min(ans,-x.first); if (-x.first>=ans) return; //当前最短路都比答案大,后面的不用看 if (vis[x.second]) continue; vis[x.second]=1; for (int i=0;i<18;i++) { int y=x.second^(1<<i); if (d[y]>d[x.second]+cost[x.second][i]) { d[y]=d[x.second]+cost[x.second][i]; q.push(make_pair(-d[y],y)); } } } } signed main() { prework(); scanf("%lld",&n); for (int i=1;i<=n;i++) scanf("%lld",&a[i]),mark[a[i]]=1; sort(a+1,a+n+1); for (int i=2;i<=n;i++) if (a[i]==a[i-1]) { puts("0"); return 0; } ans=INF; for (int i=0;i<18;i++) Dijkstra(i); cout<<ans<<endl; return 0; }
以上是关于2019华工校赛 B - 修仙时在做什么?有没有空?可以来炼丹吗?的主要内容,如果未能解决你的问题,请参考以下文章
2019年华南理工校赛(春季赛)--L--剪刀石头布(签到)
2019浙大校赛--J--Extended Twin Composite Number(毒瘤水题)