NOIP2017模拟 玩游戏
题目大意
给出一个n个节点m条边的无向连通图,定义两点间最短路径的长度为所有路径中最长边权值的最小值。现在有一些加边操作和询问操作,共计q次。询问操作是要求判断两组点对间的最短距离是否相等(原题中以最基本的Nim游戏的形式给出)。
数据范围
对于90%的数据,n<=5000,m<=100000,q<=150000,边权<=1e15,加边操作不超过1000次。
另有10%数据,每次加边的权值相同且已知,加边操作不超过5000次,n=1000,m=100000,q=100000。(也就是特殊处理即可)
原本的题目描述不是很容易读,这里简化了。如果能看出来这样的“最短距离”就是最小生成树上两点路径中边权的最大值,那么本题就基本上做完了。
树上讨论两点间的路径果断LCA,至于边权最大值显然可以用倍增处理。建好初始的生成树时间复杂度 O(mlogm) ,预处理出倍增数组的时间复杂度 O(nlogn) ,如果没有加边操作,询问可以在 O(logn) 内完成。
如何处理加边操作?我的方法可能不够优秀,在考场上由于一个优化没加只有70分。加了优化之后勉强能过。
如果暴力重新建树,每次加边操作时间复杂度都是 O(mlogm+nlogn) ,这样显然是过不了的,考虑优化。
首先,Kruskal算法的时间复杂度 O(mlogm) 是因为排序耗了太多时间,如果边已经排好序了,剩下就可以在线性时间内得到答案。每次我们只是加入一个数,而之前的数组都是排好序的,所以插入排序就可以把加边时的Kruskal的时间复杂度降为 O(m) 。
然而这样还是过不了。有一个显然的优化:如果目前的最小生成树中,最长边的权值都比新加的边要小,那么加这条边显然是没有意义的。加了这个优化似乎就能AC了。
如果用LCT那这道题就是大水题,可惜当时不会。
不优秀的代码:
#include<stdio.h>
#include<algorithm>
#include<cstring>
#define MAXN 5005
#define MAXM 400005
#define ll long long
using namespace std;
int N,M,TOT;
ll MaxLen;
struct edge{int St,En;ll Len;}E[MAXM];
bool operator<(edge a,edge b){return a.Len<b.Len;}
int tot,en[MAXM],nex[MAXM],las[MAXN];
ll len[MAXM];
void Add(int x,int y,ll z)
{
en[++tot]=y;
nex[tot]=las[x];
las[x]=tot;
len[tot]=z;
}
/*-----------------------MST--------------------------*/
void Ins(int a,int b,ll c)//插入排序
{
int i;
TOT++;
E[TOT].St=a;E[TOT].En=b;E[TOT].Len=c;
for(i=TOT-1;i;i--)
{
if(E[i+1]<E[i])swap(E[i],E[i+1]);
else return;
}
}
int Fa[MAXN];
int gf(int x)
{
if(Fa[x]!=x)Fa[x]=gf(Fa[x]);
return Fa[x];
}
void Kruskal()
{
int i,x,y,cnt=0;
for(i=1;i<=N;i++)Fa[i]=i;
memset(las,0,sizeof(las));
tot=0;
for(i=1;i<=TOT&&cnt!=N-1;i++)
{
x=E[i].En;y=E[i].St;
x=gf(x);y=gf(y);
if(x==y)continue;
Add(E[i].En,E[i].St,E[i].Len);
Add(E[i].St,E[i].En,E[i].Len);
MaxLen=max(MaxLen,E[i].Len);
cnt++;
Fa[x]=y;
}
}
/*-------------------------LCA-----------------------*/
int fa[MAXN][14],dep[MAXN];
ll v[MAXN][14];
void DFS(int x,int f,ll z)
{
int i,y;
fa[x][0]=f;
dep[x]=dep[f]+1;
v[x][0]=z;
for(i=1;i<14;i++)fa[x][i]=fa[fa[x][i-1]][i-1],v[x][i]=max(v[fa[x][i-1]][i-1],v[x][i-1]);
for(i=las[x];i;i=nex[i])
{
y=en[i];
if(y==f)continue;
DFS(y,x,len[i]);
}
}
ll LCA(int x,int y)
{
int i,d,t;
ll ans=0;
if(dep[x]<dep[y])t=x,x=y,y=t;
d=dep[x]-dep[y];
for(i=0;i<14;i++)if((d>>i)&1)ans=max(ans,v[x][i]),x=fa[x][i];
if(x==y)return ans;
for(i=13;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
ans=max(ans,v[x][i]);ans=max(ans,v[y][i]);
x=fa[x][i];y=fa[y][i];
}
ans=max(v[x][0],ans);ans=max(ans,v[y][0]);
return ans;
}
int main()
{
int i,x,y,cnt=0;
ll z;
char op[6];
scanf("%d%d",&N,&M);
TOT=M;
for(i=1;i<=M;i++)
{
scanf("%d%d%lld",&x,&y,&z);
E[i].St=x;E[i].En=y;E[i].Len=z;
}
sort(E+1,E+M+1);
Kruskal();
DFS(1,0,0);
int Q,m1,m2,b1,b2;
ll vb,vm;
scanf("%d",&Q);
while(Q--)
{
scanf("%s",op);
if(op[0]=='a')
{
scanf("%d%d%lld",&x,&y,&z);
cnt++;
if(cnt>1000||MaxLen<z)continue;
//cnt>1000是为了解决特判的数据点。这里我懒得专门写一种情况了
Ins(x,y,z);
Kruskal();
DFS(1,0,0);
}
else
{
scanf("%d%d%d%d",&m1,&m2,&b1,&b2);
vm=LCA(m1,m2);
vb=LCA(b1,b2);
if(vb!=vm)puts("madoka");
else puts("Baozika");
}
}
}