4.10 模拟赛 最小生成树 克鲁斯卡尔思想 优化建图
这道题超级的妙。
30分可以发现题目中的建图最多建n条 剩下的都没用 所以暴力建nQ条边跑克鲁斯卡尔即可。
对于Q<=50 发现此时边数为为2e7 跑克鲁斯卡尔必然GG.
考虑prim求最小生成树 复杂度nlogn+2e7.
这样就可以暴力得到50分了。
最后考虑一下(ai-bi)<=1的情况。
可以发现此时连边有特点 对于ai<bi 连边是 2,1 3,2 4,3....f[i][0]表示i这个点和其父亲连的边权即可。
对于ai==bi 和上面情况几乎一样。
对于ai>bi 其中一种情况和上面一样 还会发现有 4,2 5,3 6,4这种连边方式 f[i][1]表示i这个点和其父亲的父亲的连的边权即可。
利用f数组进行更新其他的f值 可以发现存在环 所以dp两次即可 我当时傻了 直接开堆放f数组 最小的来更新了 也行。
然后再做最小生成树 复杂度nlogn.
80分代码:
const int maxn=200010;
int n,flag,Q,vis[maxn];
vector<pii>g[maxn];
struct wy{int x,y,z;}t[maxn];
int d[maxn];ll sum;
priority_queue<pii>q;
int f[maxn][2];
inline void add(int x,int y,int z)
{
if(z>=INF)return;
if(x==y)return;
g[x].pb(mk(y,z));
g[y].pb(mk(x,z));
}
inline void prim()
{
rep(1,n,i)d[i]=INF,vis[i]=0;
d[1]=0;q.push(mk(-d[1],1));
while(q.size())
{
int x=q.top().S;q.pop();
if(vis[x])continue;
vis[x]=1;sum+=d[x];
for(int j=0;j<g[x].size();++j)
{
int tn=g[x][j].F,e=g[x][j].S;
if(d[tn]>e)
{
d[tn]=e;
q.push(mk(-d[tn],tn));
}
}
}
}
int main()
{
freopen("spanning.in","r",stdin);
freopen("spanning.out","w",stdout);
get(n);get(Q);
rep(1,Q,i)
{
int x,y,z;
get(x);get(y);get(z);
t[i]=(wy){x,y,z};
if(abs(x-y)>1)flag=1;
}
if(!flag)
{
rep(0,n-1,i)f[i][0]=f[i][1]=INF;
rep(1,Q,i)
{
int x,y,z;
x=t[i].x;y=t[i].y;z=t[i].z;
if(x==y){f[(x+1)%n][0]=min(f[(x+1)%n][0],z+1);continue;}
if(x<y)f[y][0]=min(f[y][0],z);
else
{
f[x][0]=min(f[x][0],z);
f[(x+1)%n][1]=min(f[(x+1)%n][1],z+1);
}
}
rep(0,n-1,i)
{
if(f[i][1]!=INF)q.push(mk(-f[i][1],i));
if(f[i][0]!=INF)q.push(mk(-f[i][0],i));
}
while(q.size())
{
int x=q.top().S;
int ww=-q.top().F;
q.pop();
if(ww==f[x][0])
{
int tn=(x+1)%n;
if(f[tn][0]>f[x][0]+2)
{
f[tn][0]=f[x][0]+2;
q.push(mk(-f[tn][0],tn));
}
}
if(ww==f[x][1])
{
int tn=(x+1)%n;
if(f[tn][1]>f[x][1]+2)
{
f[tn][1]=f[x][1]+2;
q.push(mk(-f[tn][1],tn));
}
}
}
rep(1,n-1,i)
{
add(i+1,i,f[i][0]);
if(i>=2)add(i+1,i-1,f[i][1]);
}
add(1,n,f[0][0]);
add(1,n-1,f[0][1]);
add(2,n,f[1][1]);
prim();putl(sum);
return 0;
}
if(flag)
{
if((ll)n*Q<=10000000)
{
rep(1,Q,i)
{
int x=t[i].x;int y=t[i].y;int z=t[i].z;
int s1=0,s2=0;add(x+1,y+1,z);
rep(1,n*2-1,j)
{
if(j&1)++s1;else ++s2;
add((s1+x)%n+1,(s2+y)%n+1,z+j);
}
}
sum=0;prim();putl(sum);
}
}
return 0;
}
考虑100分 我们还是可以发现这是一个优化建图的问题。
考虑克鲁斯卡尔的过程 对于边a,b,c a向b连了一条c的边 对于下一条边 a+1,b,c+1 对于这条边我们可以设想一下 在跑克鲁斯卡尔的时候 由于上一条边比当前边权小 上一条边被便利的时候 a,b必然形成了一个联通快。
此时 对于a+1,b,c+1这条边就等价于 a+1,a,c+1 而下条边 a+1,b+1,c+2由于同样的原因可以转换为 b,b+1,c+2.
设f[i]表示i点和其父亲连边的边权最小值 dp一下即可 可以发现这样做所有边都被简化了。
然后跑克鲁斯卡尔即可 总边数Q+n.复杂度(Q+n)log 。
const int MAXN=400010;
int n,cnt,Q;ll sum;
struct wy
{
int x,y,z;
}t[MAXN<<1];
int f[MAXN];
inline int cmp(wy a,wy b){return a.z<b.z;}
inline int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);}
int main()
{
freopen("spanning.in","r",stdin);
freopen("spanning.out","w",stdout);
memset(f,0x3f,sizeof(f));
get(n);get(Q);
rep(1,Q,i)
{
int x,y,z;
get(x);get(y);get(z);
t[++cnt]=(wy){x+1,y+1,z};
f[(x+1)%n]=min(f[(x+1)%n],z+1);
f[(y+1)%n]=min(f[(y+1)%n],z+2);
}
rep(1,n-1,i)f[i]=min(f[i-1]+2,f[i]);
f[0]=min(f[n-1]+2,f[0]);
rep(1,n-1,i)f[i]=min(f[i-1]+2,f[i]);
rep(1,n-1,i)t[++cnt]=(wy){i,i+1,f[i]};
t[++cnt]=(wy){1,n,f[0]};
rep(1,n,i)f[i]=i;
sort(t+1,t+1+cnt,cmp);
rep(1,cnt,i)
{
int xx=getfather(t[i].x);
int yy=getfather(t[i].y);
if(xx==yy)continue;
f[xx]=yy;sum+=t[i].z;
}
putl(sum);
return 0;
}