题解:CF1770H Koxia, Mahiru and Winter Festival

前言

本文同步自洛谷专栏,题目传送门

*3500 神仙题。

在学习 APIO2025 的讲课资料时做的这题,这是一个复杂度稍劣的 \(O(n^3)\) 调整法做法,实现难度可能要低一点。

解题

特殊情况

\(p_i=i,q_i=i\) 均成立的情况下,给出方案是容易的,此时每条边恰被覆盖一次。

进一步考虑,如果存在 \(p_i\ne i\)\(q_i\ne i\),那么所有路径总长一定增加,

根据鸽巢原理,最大堵塞程度至少为 \(2\),下面的一般情况将证明存在一种构造方式满足最大堵塞程度恰为 \(2\)

一般情况

考虑调整,如果从最优情况开始调整,需要额外增加一些边,这些边是否满足条件并不好确定。

所以考虑从最劣情况开始调整,猜测:\(p_i=n-i+1,q_i=n-i+1\) 即为最劣情况。(完全逆序)

构造

考虑最劣情况的答案怎么构造。

发现只有四个角会被作为两次起点(或终点),

我们可以利用最外层的边满足四个角的限制(每条边恰好两次),然后其余点都向中心走一步。

发现剩余部分问题形式相同,即问题规模减小 \(2\),且最终的边界情况是容易的。

$\red{\text{注}}$

事实上,将这种构造方法推广到任意情况,维护相对顺序,即是官方题解给出的归纳法,

实现稍复杂,但复杂度可以做到 \(O(n^2)\)


此时,被覆盖两次的边呈一个涟漪状(虽然是方的):

图

注:\([-6,-1],[0,6]\) 两个部分分别对应 \(n=6,7\) 的情况。

调整

考虑从最劣情况开始交换调整,我们希望不增加新的边,

那么每次交换时,两条原来的路径需要有一个交点,将交点之后的路径交换,即可交换两条路径的终点,单次交换 \(O(n)\)

可以任意地交换吗?

如果想要利用任意交换,做到 \(O(n)\) 次交换,那么手玩一下样例二就会发现不太可能。

任意交换容易出问题,不妨考虑弱化成邻项交换,那么可以考虑冒泡排序

考虑如下的冒泡排序过程:

对每个终点 \(i\) 记录其当前的起点 \(id_i\),记 \(f_i=p_{id_i}\) 表示当前起点的目标终点

每次邻项考虑是否交换时,如果 \(f_j>f_{j+1}\),就交换 \(id_j,id_{j+1},f_j,f_{j+1}\) 并更新路径,

最后 \(f_i=i\),即每个终点就能够对应正确的起点。

$\red{\text{正确性证明}}$

初始(最劣)情况下,\(id\)完全逆序的,而任意点对在冒泡排序过程中最多交换一次,则每次交换前 \(id\) 是逆序的,而逆序对在网格上对应必然有交,证毕。

在交换的过程中,边集总和没有发生变化,也就是和最劣情况时的构造结果相同,能够满足题意。


实现

复杂度 \(O(n^3)\),但是跑得很快。

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define N 205
#define pc(a) ((wrp==obuf+IO&&(fwrite(obuf,1,IO,stdout),wrp=obuf)),(*wrp++)=a)

const int IO=1<<20;
char obuf[IO+1],*wrp=obuf;
int n,H[N],W[N],id[N],mp[N][N];//h 横向,W 纵向
vector<pair<int,int>>E[N];//存路径上的点

inline void write(int x){
    int sk[20],top=0;
    do{
        sk[++top]=x%10,x/=10;
    }while(x);
    while(top) pc(sk[top--]+'0');
    pc(' ');
}

bool check(){//特判答案为 1 的情况
    for(int i=1;i<=n;i++){
        if(H[i]!=i) return 0;
    }
    for(int i=1;i<=n;i++){
        if(W[i]!=i) return 0;
    }
    for(int i=1;i<=n;pc('\n'),i++){
        write(n);
        for(int j=1;j<=n;j++) write(j),write(i);
    }
    for(int i=1;i<=n;pc('\n'),i++){
        write(n);
        for(int j=1;j<=n;j++) write(i),write(j);
    }
    return 1;
}

inline void myswap(int &u,int &v){
    int iu,iv;
    for(int i=E[u].size()-1;i>=0;i--){
        mp[E[u][i].first][E[u][i].second]=i+1;
    }
    for(int i=E[v].size()-1;i>=0;i--){
        iu=mp[E[v][i].first][E[v][i].second];
        if(iu){iv=i+1;break;} 
    }
    for(int i=E[u].size()-1;i>=0;i--){
        mp[E[u][i].first][E[u][i].second]=0;
    }
    vector<pair<int,int>>tmp;
    tmp.insert(tmp.end(),E[v].begin()+iv,E[v].end());
    E[v].erase(E[v].begin()+iv,E[v].end());
    E[v].insert(E[v].end(),E[u].begin()+iu,E[u].end());
    E[u].erase(E[u].begin()+iu,E[u].end());
    E[u].insert(E[u].end(),tmp.begin(),tmp.end());
    tmp.clear(),swap(u,v);
}

void workw(){//从最劣状况开始调整,构造最劣情况
    for(int i=1,x,y,f;i<=n;i++){
        x=1,y=i,f=i<n-i+1?1:-1,E[i].emplace_back(x,y);
        for(int j=min(i-1,n-i);j>=1;j--){//向下走 min(i,n-i+1)-1 步,
            x++,E[i].emplace_back(x,y);
        }//下面是走到对称位置
        for(;y!=n-i+1;) y+=f,E[i].emplace_back(x,y);
        for(;x<n;) x++,E[i].emplace_back(x,y); 
    }
    for(int i=1;i<=n;i++) id[i]=n-i+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n-i;j++){
            if(W[id[j]]>W[id[j+1]]) myswap(id[j],id[j+1]);
        }//需要交换时却无交的情况是不会发生的
    }//因为初始情况下是完全的逆序,未发生交换的两条路径之间一定有交
    for(int i=1;i<=n;pc('\n'),i++){
        write(E[i].size());
        for(auto &[x,y]:E[i]) write(x),write(y);
    }
}

void workh(){
    for(int i=1;i<=n;i++) E[i].clear();
    for(int i=1,x,y,f;i<=n;i++){
        x=i,y=1,f=i<n-i+1?1:-1,E[i].emplace_back(x,y);
        for(int j=i-1;j>=1;j--){
            y++,E[i].emplace_back(x,y);
        }
        for(;x!=n-i+1;) x+=f,E[i].emplace_back(x,y);
        for(;y<n;) y++,E[i].emplace_back(x,y); 
    }
    for(int i=1;i<=n;i++) id[i]=n-i+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n-i;j++){
            if(H[id[j]]>H[id[j+1]]) myswap(id[j],id[j+1]);
        }//需要交换时却无交的情况是不会发生的
    }//因为初始情况下是完全的逆序,未发生交换的两条路径之间一定有交
    for(int i=1;i<=n;pc('\n'),i++){
        write(E[i].size());
        for(auto &[x,y]:E[i]) write(x),write(y);
    }
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>W[i];
    for(int i=1;i<=n;i++) cin>>H[i];
    if(!check()) workw(),workh();
    return fwrite(obuf,1,wrp-obuf,stdout),0;
}

posted @ 2026-06-05 17:41  Wxb2010  阅读(6)  评论(0)    收藏  举报