题目给出了一个图,每个点和他的前后5个点连边,边的权值是由一个公式计算得出的。求最小生成树的边权和。
我们观察公式,发现其有一个循环节,为54。就是说点i上的边的权值和点i+54上的边的权值是完全相同的。然后我们就可以把这个图缩成5+54+54+53+5这么多个点的图,求最小生成树了。
左右的5是因为这5个点和其他点不同,其左右不是都有5条边。第一个54为添加不需重复的边准备,即可能有一些边是不需要和循环节一起重复很多次的。第二个54为循环节的54。后边的53为剩余的53,即总点数减去10然后模54的余数。
然后就是求最小生成树,对边排序后按序添加。如果两条边权值一样,那么将不是循环节的那条(即不会被复制多次的)放在前边,求最小生成树之后计算结果的时候,要将是循环节的边乘以循环节的个数,即拷贝多份。
标准解法不是这样..是DP+矩阵加速...现在这个解法我没有严格的证明..不过感觉上是对的,并且也A了...
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int mod=2333333;
const int XX=5;
inline bool notin(int i) {
if (i<=XX+54||i>XX+54+54) return true;
return false;
}
struct Edge {
int x,y;
long long v;
void clear(int xx,int yy,long long vv) {
x=xx;y=yy;v=vv;
}
friend bool operator < (const Edge &a,const Edge &b) {
if (a.v!=b.v) return a.v<b.v;
if (notin(a.x)) return true;
return false;
}
};
struct DisjoinSet {
int a[600];
void clear(int n) {
for (int i=1;i<=n;i++) a[i]=i;
}
int get(int i) {
if (a[i]==i) return i;
return a[i]=get(a[i]);
}
void tosame(int x,int y) {
x=get(x);
y=get(y);
a[x]=y;
}
};
DisjoinSet c;
Edge b[1000];
int bp,n,pp;
long long ans;
int main() {
int i,j,seed;
while (scanf("%d%d",&n,&seed)!=EOF) {
pp=1;
while (n-54>=54+54+XX+XX) {
n-=54;
pp++;
}
c.clear(n);
bp=0;
long long x=seed;
for (i=2;i<=n;i++) {
x=x*907%mod;
long long t=x;
for (j=max(1,i-5);j<i;j++) {
x=x*907%mod;
long long w=t^x;
b[bp++].clear(i,j,w);
}
}
sort(b,b+bp);
ans=0;
for (i=0;i<bp;i++) {
if (c.get(b[i].x)!=c.get(b[i].y)) {
c.tosame(b[i].x,b[i].y);
if (notin(b[i].x)) ans+=b[i].v;
else ans+=b[i].v*pp;
}
}
printf("%lld\n",ans);
}
return 0;
}