开始挖老祖宗(ycx)留下来的东西.jpg
本来想水一道紫题作为 AC 的第 500 道紫题的,结果发现点开了道神题。
首先先讲一个我想出来的暴力做法。条件一和条件二直接扫一遍判断掉。先将所有点按照 \(a_{i,j}\) 按权值大小从小到大排序并依次插入这些点,我们实时维护一个 \(n\times n\) 的 bool 数组 \(vis\),\(vis_{i,j}\) 表示第 \(i\) 行第 \(j\) 列的数是否被访问了。当我们插入某个 \(a_{i,j}\) 时,如果 \(\exist k\in[1,n]\) 使得 \(vis_{i,k}=vis_{j,k}=1\),那么意味着 \(a_{i,k},a_{j,k}\) 均小于 \(a_{i,j}\),也就不符合条件三了。这个将 bool 数组换成 bitset
可以实现 \(\mathcal O(\dfrac{n}{\omega})\) 判断。还有一个小问题,那就是如果有权值重复的,比如说 \(a_{i,j}=a_{i',j'}\),假设 \(a_{i',j'}\) 比 \(a_{i,j}\) 后访问,那么在访问 \(a_{i',j'}\) 的时候 \(vis_{i,j}\) 已经等于 \(1\) 了。如果在这种情况下 \(i'=i\),并且 \(vis_{j',j}\) 刚好等于 \(1\),那么程序就会认为这种情况不满足条件三,而实际上 \(\max(a_{i,j},a_{j',j})=a_{i',j'}\)。这个问题也异常容易解决,直接 two pointers 扫描一遍权值相同的数,对于这些数,先一起判断掉再一起插入就行了。
时间复杂度 \(\dfrac{n^3}{\omega}\),荣膺最劣解。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=1;
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x*=neg;
}
const int MAXN=2500;
int n,a[MAXN+5][MAXN+5];pair<int,pii> p[MAXN*MAXN+5];
bitset<MAXN+5> bt[MAXN+5];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);p[(i-1)*n+j]=mp(a[i][j],mp(i,j));
} sort(p+1,p+n*n+1);
for(int i=1;i<=n;i++) if(a[i][i]) return printf("NOT MAGIC\n"),0;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(a[i][j]!=a[j][i])
return printf("NOT MAGIC\n"),0;
for(int l=1,r=1;l<=n*n;l=r){
while(p[r].fi==p[l].fi&&r<=n*n){
int x=p[r].se.fi,y=p[r].se.se;
if((bt[x]&bt[y]).count()) return printf("NOT MAGIC\n"),0;
r++;
} for(int i=l;i<r;i++){int x=p[i].se.fi,y=p[i].se.se;bt[x][y]=1;}
} printf("MAGIC\n");
return 0;
}
事实上我们发现这个东西和图论关系非常密切。如果我们把这个矩阵看作邻接矩阵,那么条件一意味着图中不存在自环,条件二意味着 \(w_{u,v}=w_{v,u}\),也就是说满足条件一和条件二的矩阵是一张无向带权完全图的邻接矩阵。我们再来分析条件三。比方说有四个点 \(i,j,k,l\),根据条件三 \(w_{i,k}\leq\max(w_{i,j},w_{j,k}),w_{i,l}\leq\max(w_{i,k},w_{k,l})\leq\max\{w_{i,j},w_{j,k},w_{k,l}\}\),这意味着 \(\forall i,j\in[1,n]\),任意一条 \(i,j\) 路径上的边权的最大值 \(\geq w_{i,j}\)。故 \(\forall i,j\in[1,n]\),\(i,j\) 之间路径上最大权值的最小值 \(\geq w_{i,j}\)。看到这个条件很容易想到最小瓶颈路——构建出最小生成树,那么 \(i,j\) 之间路径上最大权值的最小值就是最小生成树上 \(i,j\) 路径上权值的最大值。于是求一遍最小生成树,然后对于每个点进行一遍 DFS 求出它到每个点的路径上权值的最大值就行了。注意到这张图是一张稠密图,\(m=n^2\),故 Kruskal、加堆优化的 Prim 都是 \(m\log n=n^2\log n\) 的,而不加堆优化的 Prim 反而是 \(n^2\) 的,故此题用 Prim 效率反而更高。
然而这才是我第二次写 Prim(第一次是两年以前) 啊……习惯了 Kruskal 的我都忘了 Prim 咋写了……
code(Kruskal):
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=1;
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x*=neg;
}
const int MAXN=2500;
int n,a[MAXN+5][MAXN+5],f[MAXN+5];
pair<int,pii> p[MAXN*MAXN+5];
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
bool merge(int x,int y){
x=find(x);y=find(y);
if(x!=y) return f[x]=y,1;
return 0;
}
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
void dfs(int x,int rt,int f,int mx){
if(f&&a[x][rt]>mx){printf("NOT MAGIC\n");exit(0);}
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs(y,rt,x,max(mx,a[x][y]));
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);p[(i-1)*n+j]=mp(a[i][j],mp(i,j));
}
for(int i=1;i<=n;i++) if(a[i][i]) return printf("NOT MAGIC\n"),0;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(a[i][j]!=a[j][i])
return printf("NOT MAGIC\n"),0;
sort(p+1,p+n*n+1);
for(int i=1;i<=n*n;i++) if(merge(p[i].se.fi,p[i].se.se))
adde(p[i].se.fi,p[i].se.se),adde(p[i].se.se,p[i].se.fi);
for(int i=1;i<=n;i++) dfs(i,i,0,-1);
printf("MAGIC\n");
return 0;
}
code(Prim):
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=1;
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x*=neg;
}
const int MAXN=2500;
const int INF=0x3f3f3f3f;
int n,a[MAXN+5][MAXN+5],hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int mn[MAXN+5],fa[MAXN+5];bool vis[MAXN+5];
void prim(){
vis[1]=1;for(int i=2;i<=n;i++) mn[i]=a[1][i],fa[i]=1;
for(int i=1;i<=n-1;i++){
int mnv=INF,k=0;
for(int j=1;j<=n;j++) if(!vis[j]&&mn[j]<mnv) mnv=mn[j],k=j;
vis[k]=1;
for(int j=1;j<=n;j++) if(!vis[j]&&mn[j]>a[k][j]) mn[j]=a[k][j],fa[j]=k;
}
}
void dfs(int x,int rt,int f,int mx){
if(f&&a[x][rt]>mx){printf("NOT MAGIC\n");exit(0);}
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs(y,rt,x,max(mx,a[x][y]));
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++) if(a[i][i]) return printf("NOT MAGIC\n"),0;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(a[i][j]!=a[j][i])
return printf("NOT MAGIC\n"),0;
prim();for(int i=2;i<=n;i++) adde(i,fa[i]),adde(fa[i],i);
for(int i=1;i<=n;i++) dfs(i,i,0,-1);printf("MAGIC\n");
return 0;
}
(午安,Kruskal 人)