【题解】Informacije [COCI2012]
传送门:官方题面
【题目描述】
有一个长度为 \(n\) 的 序列 \(a\)(由 \([1,n]\) 中的数组成,且每个数只会出现一次),现给出两个整数 \(n,m\) 和 \(m\) 个关于 \(a\) 的描述,格式如下:
\(1\ l\ r\ v\) 表示 \(max\{a[l],a[l+1]...a[r]\}=v\),
\(2\ l\ r\ v\) 表示 \(min\{a[l],a[l+1]...a[r]\}=v\)。
请输出一个满足上面 \(m\) 个描述的序列,如果多种答案,输出任意一种,无解则输出 \(-1\)。
【样例】
样例输入: 3 2 1 1 1 1 2 2 2 2 样例输出: 1 2 3 样例输入: 4 2 1 1 1 1 2 3 4 1 样例输出: -1 样例输入: 5 2 1 2 3 3 2 4 5 4 样例输出: 1 2 3 4 5
【数据范围】
\(100 \%:\) \(1 \leqslant n \leqslant 200,\) \(0 \leqslant m \leqslant 40000\)
【分析】
\(n \leqslant 200\),一开始只是觉得可以写 \(n^3\) 的算法,比如矩阵乘法之类的,但看到 \(m \leqslant 40000\) 时,瞬间想到建一张完全图跑图论。事实证明这一直觉是正确的。
用 \(pan[i][j]\) 表示整数 \(i\) 是否可以填在 \(j\) 这个位置(只需要满足给出的 \(m\) 个条件即可)。
如果 \(pan[i][j]\) 为 \(1\),那么 \(i\) 向 \(j\) 连一条有向边,然后跑一遍二分图最大匹配,\(match\) 数组即为答案。
匈牙利算法的时间复杂度为:\(O(|V|*|E|)\),其中 \(|E| \leqslant |V|^2\),\(200\) 个点的完全图完全不是问题。
如何求 \(pan\) 数组?
最开始 \(yy\) 了一种 \(mn\) 的预处理方法:
\((1).\) \(Lw[x],Rw[x]\) 分别表示整数 \(x\) 必须要放的位置所在区间左右端点。
对于所有的 \(l,r,v\),\(Lw[v]=max\{l,Lw[v]\},Rw[v]=min\{r,Rw[v]\}\)。
\((2).\) \(Ls[i],Rs[i]\) 分别表示位置 \(i\) 可放置的整数范围。
对于 \(1,l,r,v\),\(Rs[i]=min\{v,Rs[i]\} (i \in [l,r])\),
对于 \(2,l,r,v\),\(Ls[i]=max\{v,Ls[i]\} (i \in [l,r])\)。
但仔细想想觉得不太对,所以就改成了 \(mn^2\) 的暴力枚举,本以为会超时,结果加了剪枝后居然轻松跑过了。
后来发现官方题解给的就是 \(mn\) 的做法,而且和我之前想的一模一样。。。
【Code】
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #define Re register int using namespace std; const int N=203,M=40003,inf=2e9; int n,m,op[M],L[M],R[M],val[M],pan[N][N]; int o,ans,vis[N],head[N],match[N]; struct QAQ{int to,next;}a[N*N]; inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;} inline void in(Re &x){ int f=0;x=0;char c=getchar(); while(c<'0'||c>'9')f|=c=='-',c=getchar(); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar(); x=f?-x:x; } inline int judge(Re i,Re L,Re R){//判断在[L,R]这个区间内是否有1 for(Re j=L;j<=R;++j)if(pan[i][j])return 1; return 0; } inline void add_(Re i,Re L,Re R){//将[L,R]全部变为0 for(Re j=L;j<=R;++j)pan[i][j]=0; } inline void add(Re i,Re L,Re R){//将[L,R]以外的全部变为0 for(Re j=1;j<L;++j)pan[i][j]=0; for(Re j=R+1;j<=n;++j)pan[i][j]=0; } inline void Print(){ for(Re i=1;i<=n;++i){ printf("pan[%d]: ",i); for(Re j=1;j<=n;++j)if(pan[i][j])printf("%d ",j); puts(""); } puts(""); } inline int sakura(){ for(Re i=1;i<=m;++i){ if(op[i]>1){//L[i]~R[i]的最小值为val[i] if(!judge(val[i],L[i],R[i]))return 0; add(val[i],L[i],R[i]); for(Re j=1;j<val[i];++j){//比val小的数 Re flag1=judge(j,1,L[i]-1),flag2=judge(j,R[i]+1,n); if(!flag1&&!flag2)return 0;//如果没有可放的位置就直接return else if(flag1&&!flag2)add(j,1,L[i]-1);//删掉左边 else if(flag2&&!flag1)add(j,R[i]+1,n);//删掉右边 else add_(j,L[i],R[i]);//删掉左右两边 } } else{//L[i]~R[i]的最大值为val[i] if(!judge(val[i],L[i],R[i]))return 0; add(val[i],L[i],R[i]); for(Re j=val[i]+1;j<=n;++j){//比val大的数 Re flag1=judge(j,1,L[i]-1),flag2=judge(j,R[i]+1,n); if(!flag1&&!flag2)return 0; else if(flag1&&!flag2)add(j,1,L[i]-1); else if(flag2&&!flag1)add(j,R[i]+1,n); else add_(j,L[i],R[i]); } } } return 1;//最后还要return 1 } inline int dfs(Re x){ for(Re i=head[x],to;i;i=a[i].next) if(!vis[to=a[i].to]){ vis[to]=1; if(!match[to]||dfs(match[to])){ match[to]=x;return 1; } } return 0; } int main(){ // freopen("informacije.in","r",stdin); // freopen("informacije.out","w",stdout); in(n),in(m); for(Re i=1;i<=m;++i)in(op[i]),in(L[i]),in(R[i]),in(val[i]); for(Re i=1;i<=n;++i) for(Re j=1;j<=n;++j) pan[i][j]=1; if(!sakura())puts("-1"); else{ // Print(); for(Re i=1;i<=n;++i) for(Re j=1;j<=n;++j) if(pan[i][j])add(i,j); for(Re i=1;i<=n;++i){ memset(vis,0,sizeof(vis)); if(!dfs(i))return !puts("-1"); } for(Re i=1;i<=n;++i)printf("%d ",match[i]); } fclose(stdin); fclose(stdout); return 0; }