省赛血炸,shl一个点分治写了2小时没过样例就tm离谱,我一个dfs找了半天错误找不到,byf重写一遍过了。
结果这题没写,数位DP那题没写,太惨了,18一等,19二等,反向训练选手石锤
这题我们知道对于每一相邻两行的比较,所有上面>下面的位置一定在某一个上面<下面的位置后面,于是我们建图,设左边m个点为m列元素,右边n-1个点为每一相邻两行,对于上面<下面的,左边连到右边,且右边的n-1个点只要被遍历一次,就激活了,形象地说,只要有一个上面<小面的出现,后面就随便排了,满足si-1<=si。
那么对于上面>下面的位置,从右边的si连到左边,只有右边的si被激活了,在这一相邻两行中,他不必被考虑了,所以所有入度减1,那么只有当每一相邻都不必考虑他了,这个位置才能进优先队列,也就是rudu[i]=0时进优先队列。
优先队列里面只放左边的点,由于要字典序最小,所以每次取出可取的最小的点。
对于左边的点,入度为0时入队,对于右边的点,只要访问到他就被激活,vis[i]=true,因为任意上面<下面出现了这一行后面都不用管了。
按照这样的方式拓扑排序即可,边数最大为n*m,所以复杂度O(n*m+nlogn)。
#include<bits/stdc++.h>
using namespace std;
const int maxl=2e3+10;
int n,m;
int rudu[maxl],ans[maxl];
int a[maxl][maxl];
vector <int> em[maxl],en[maxl];
priority_queue <int,vector<int>,greater<int> >q;
bool vis[maxl];
inline void prework()
{
for(int i=1;i<=n;i++)
en[i].clear(),vis[i]=false;
for(int i=1;i<=m;i++)
em[i].clear(),rudu[i]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(int i=2;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i-1][j]<a[i][j])
em[j].push_back(i);
else if(a[i-1][j]>a[i][j])
en[i].push_back(j),rudu[j]++;
}
inline void mainwork()
{
while(!q.empty())
q.pop();
for(int i=1;i<=m;i++)
if(rudu[i]==0)
q.push(i);
int u;ans[0]=0;
while(!q.empty())
{
u=q.top();ans[++ans[0]]=u;
q.pop();
for(int v : em[u])
if(!vis[v])
{
vis[v]=true;
for(int k : en[v])
{
rudu[k]--;
if(rudu[k]==0)
q.push(k);
}
}
}
}
inline void print()
{
if(ans[0]!=m)
puts("-1");
else
for(int i=1;i<=m;i++)
printf("%d%c",ans[i],(i==m)?'\n':' ');
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
prework();
mainwork();
print();
}
return 0;
}