[BZOJ3714]Kuglarz(图论+最小生成树)
Description
魔术师的桌子上有n个杯子排成一行,编号为1,2,…,n,其中某些杯子底下藏有一个小球,如果你准确地猜出是哪些杯子,你就可以获得奖品。花费c_ij元,魔术师就会告诉你杯子i,i+1,…,j底下藏有球的总数的奇偶性。
采取最优的询问策略,你至少需要花费多少元,才能保证猜出哪些杯子底下藏着球?
Input
第一行一个整数n(1<=n<=2000)。
第i+1行(1<=i<=n)有n+1-i个整数,表示每一种询问所需的花费。其中c_ij(对区间[i,j]进行询问的费用,1<=i<=j<=n,1<=c_ij<=10^9)为第i+1行第j+1-i个数。
Output
输出一个整数,表示最少花费。
Sample Input
5
Sample Output
7
题意
每次可以花费\(c_{i,j}\)来询问\(f_{i,j}\)
其中
求最小花费还原\(s\)
分析
我们可以发现,如果没有按位与1,那么这个\(f\)就是一个前缀和。按位与限制了他不能进位,那么其实就是二进制下不进位的加法——异或。
所以\(f_{i,j}=f_{1,i-1}\oplus f_{1,j}\)就是一个异或前缀和,也可以写成这样\(f_{1,i-1}\oplus f_{i,j}=f_{1,j}\)
然后再考虑会按位与,虽然他有按位与,但是\(s\)的取值也是01,所以\(i=j\)时我们就可以用\(f_{i,j}=f_{1,j}\oplus f_{1,i-1}\)来表示\(s_i\)
那么我们要还原\(s\)就是要知道所有的\(f_{i,i}\),退一步说,就是要知道所有的\(f_{1,i}\)
我们需要用最小的花费来求出\(f_{1,i}\),这个值除了直接询问之外,还可以用前缀和的性质拼出来。
那么我们就可以把这个问题抽象成图论:
我们用询问\([i,j]\)的信息,得到\(f_{i,j}\),实际上就是走了一条\((s=i-1,t=j,w=c_{i,j})\)的边。
那么我们从\(0\)出发,走到点\(j\),的最短路就是我得知\(f_{1,j}\)的最小花费。
现在我要到达所有点,求最小花费,那么就是最小生成树了。
考虑到边数远远大于点数,考虑使用贪点的方法跑最小生成树。
总结
- \(\forall 1<i\leq j \leq n\),建立一条\((s=i-1,t=j,w=c_{i,j]})\)的无向边
- 以\(0\)为根,跑最小生成树。
- 最小生成树的边权和就是答案
#include <cstdio>
#include <cstring>
#include <queue>
#include <utility>
long long unsigned n,tmp;
struct Edge
{
long long unsigned u,v,w;
}edge[8000021];
long long unsigned head[2010];
inline void add(long long unsigned a,long long unsigned b,long long unsigned c)
{
static long long unsigned tot=0;
edge[++tot]=(Edge){head[a],b,c}; head[a]=tot;
edge[++tot]=(Edge){head[b],a,c}; head[b]=tot;
}
long long unsigned mst()
{
long long unsigned dis[2011],res=0;
bool vis[2011];
std::memset(vis,0x00,sizeof(vis));
std::memset(dis,0x7f,sizeof(dis));
std::priority_queue<long long unsigned,std::vector<std::pair<long long unsigned,long long unsigned> >,std::greater<std::pair<long long unsigned,long long unsigned> > > q;
q.push(std::make_pair(0,0));
while(not q.empty())
{
long long unsigned i=q.top().second, tmp=q.top().first; q.pop(); if(vis[i]) continue;
vis[i]=true; res+=tmp;
for(register long long unsigned j=head[i];j;j=edge[j].u)
{
if(dis[edge[j].v]>edge[j].w) q.push(std::make_pair(dis[edge[j].v]=edge[j].w,edge[j].v));
}
}
return res;
}
int main()
{
scanf("%llu",&n);
for(register long long unsigned i=1;i<=n;++i)
{
for(register long long unsigned j=i;j<=n;++j)
{
scanf("%llu",&tmp);
add(i-1,j,tmp);
}
}
printf("%llu\n",mst());
return 0;
}