1. 程式人生 > >[NOIP模擬][連結串列]裁剪表格

[NOIP模擬][連結串列]裁剪表格

題目描述:
題目大意:
給一個n*m的矩陣,每個格子都有一個數字v,每次交換兩個大小相同的不重疊的子矩陣,輸出最後的矩陣。
輸入格式:
第一行三個整數n,m,q代表表格的行數和列數和操作次數;
接下來n行,每行m個整數,表示格中的數字。
接下來q行,每行六個數字,r1,c1,r2,c2,h,w,分別表示第一個矩形左上角所在行、所在列,第二個矩形左上角所在行、所在列,這兩個矩形的高度和寬度(保證著兩個矩形都在原有表格內)。
輸出格式:
輸出n行,每行m個整數,表示最後的矩陣。
樣例輸入:

4 4 2
1 1 2 2
1 1 2 2
3 3 4 4
3 3 4 4
1 1 3 3 2 2
3 1 1 3 2 2

樣例輸出:

4 4 3 3
4 4 3 3
2 2 1 1
2 2 1 1
資料範圍:
對於10%的資料,所有的v=1;
對於30%的資料,n,m≤300,q≤300;
對於60%的資料,n,m≤1000,q≤500;
對於100%的資料,2≤n,m≤1000,1≤q≤500;1≤v≤1000000。

題目分析:
先吐槽一下這道題,因為題面中提到高階資料結構,然後有許多人寫了splay,出題人為了卡那些人,後面的資料的詢問就專門針對splay,結果導致暴力踩標算,直接拿滿(暴力就是指直接模擬交換)。後來後面資料改了一下還是有80分,加register還是可以滿分。(主要是資料很難兼顧同時卡暴力和splay)。
分析:

這道題其實是一道連結串列題,我們可以記錄四個方向(上下左右),每次交換,其實只用修改兩個矩形的四周那一圈的連線。於是時間複雜度O(n*q)。
具體操作:先從起點(初始為(1,1),如果(1,1)與其它點交換,則這個點就成為了起點(因為實際上它被換到了(1,1)的位置))出發,分別走到這兩個矩形的左上角,然後繞矩形走一圈(在兩個矩形上的點是對應著一起走),每次交換兩個矩形對應點連出去的指向和外面被連線的點所連回來的指向。這樣就相當於交換整個矩形。
還可以優化:只記錄兩個指向,右、下,因為你輸出只根據向右和向下就可以輸出,而向右和向下就可以表示4個方向(上面的情況是在雙向修改,這裡就是單向的)。
連結串列可以一般寫法,也可以指標寫。
下附4份程式碼:1、暴力,2、我寫的記錄四個方向的連結串列,3、標算的記錄兩個方向的連結串列,4、記錄兩個方向的連結串列的指標寫法。
附程式碼:

1、暴力+register:
是我考試程式碼直接加的register,所以還有特判部分

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
using namespace std;

const int N=1e3+100;
int n,m,q,a[N][N];
bool check;

int readint()
{
    char ch;int i=0,f=1;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') {ch=getchar();f=-1;}
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
    return i*f;
}

int main()
{
    freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);

    n=readint();m=readint();q=readint();
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=m;j++)
        {
            a[i][j]=readint();
            if(a[i][j]!=1) check=true;
        }
    if(check==false)
    {
        for(register int i=1;i<=n;i++)
        {
            for(register int j=1;j<=m;j++)
                cout<<a[i][j]<<" ";//printf("%d ",a[i][j]);
            printf("\n");
        }   
        return 0;
    }
    int r1,c1,r2,c2,h,w;
    while(q--)
    {
        r1=readint();c1=readint();
        r2=readint();c2=readint();
        h=readint();w=readint();
        for(register int i=0;i<h;i++)
            for(register int j=0;j<w;j++)
                swap(a[i+r1][j+c1],a[i+r2][j+c2]);
    }
    for(register int i=1;i<=n;i++)
        {
            for(register int j=1;j<=m;j++)
                cout<<a[i][j]<<" ";//printf("%d ",a[i][j]);
            printf("\n");
        }   

    return 0;
}

2、記錄四個方向的連結串列:
我之所以把這份程式碼發在前面,只是因為是我自己寫的且耗時很久。因為維護4個方向,寫起來複雜,寫完後又因為一些情況沒考慮,或則手抖寫錯某個地方,於是除錯了很久。

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
using namespace std;

int n,m,q,pos[2],now1[2],now2[2],r1,c1,r2,c2,h,w,dian1[2],dian2[2];
struct node{
    int w;
    int up[2],down[2],left[2],right[2];//例:up[0]是向上連線的點的橫座標,up[1]縱座標,其餘同理,包括now1,now2,pos,dain1,dian2
}map[1010][1010];


int readint()
{
    char ch;int i=0,f=1;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') {ch=getchar();f=-1;}
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
    return i*f;
}

int main()
{
    //freopen("table.in","r",stdin);
    //freopen("table.out","w",stdout);

    n=readint();m=readint();q=readint();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            map[i][j].w=readint();
    for(int i=0;i<=n+1;i++)//從0開始到n+1是把原本矩形的一週外面的點也連一次
        for(int j=0;j<=m+1;j++)
        {
             map[i][j].up[0]=i-1;map[i][j].up[1]=j;
             map[i][j].right[0]=i;map[i][j].right[1]=j+1;
             map[i][j].down[0]=i+1;map[i][j].down[1]=j;
             map[i][j].left[0]=i;map[i][j].left[1]=j-1;
        }
    pos[0]=1;pos[1]=1;//賦初始起點
    while(q--)
    {
        r1=readint();c1=readint();r2=readint();c2=readint();h=readint();w=readint();

        memcpy(now1,pos,8);memcpy(now2,pos,8);//這個函式是把第二個位置的賦給第一個位置,8代表賦的長度,以位元組計算,一個int4位元組
        for(int i=1;i<c1;i++)//向右  //找到矩形的兩個左上角頂點
            memcpy(now1,map[now1[0]][now1[1]].right,8);
        for(int i=1;i<r1;i++)//向下 
            memcpy(now1,map[now1[0]][now1[1]].down,8);
        for(int i=1;i<c2;i++)//向右 
            memcpy(now2,map[now2[0]][now2[1]].right,8);
        for(int i=1;i<r2;i++)//向下 
            memcpy(now2,map[now2[0]][now2[1]].down,8);

        if(pos[0]==now1[0]&&pos[1]==now1[1]) pos[0]=now2[0],pos[1]=now2[1];//判斷起點是否改變
        else if(pos[0]==now2[0]&&pos[1]==now2[1]) pos[0]=now1[0],pos[1]=now1[1];    
        for(int i=1;i<w;i++)//上邊 
        {
            memcpy(dian1,map[now1[0]][now1[1]].up,8);
            memcpy(dian2,map[now2[0]][now2[1]].up,8);
            swap(map[dian1[0]][dian1[1]].down,map[dian2[0]][dian2[1]].down);//交換指向
            swap(map[now1[0]][now1[1]].up,map[now2[0]][now2[1]].up);
            memcpy(now1,map[now1[0]][now1[1]].right,8);//走點
            memcpy(now2,map[now2[0]][now2[1]].right,8); 
        }
        memcpy(dian1,map[now1[0]][now1[1]].up,8);//在外面再維護一次是因為如矩形某邊為3,則只走兩次,而維護三次
        memcpy(dian2,map[now2[0]][now2[1]].up,8);
        swap(map[dian1[0]][dian1[1]].down,map[dian2[0]][dian2[1]].down);
        swap(map[now1[0]][now1[1]].up,map[now2[0]][now2[1]].up);
        for(int i=1;i<h;i++)//右邊 
        {   
            memcpy(dian1,map[now1[0]][now1[1]].right,8);
            memcpy(dian2,map[now2[0]][now2[1]].right,8);
            swap(map[dian1[0]][dian1[1]].left,map[dian2[0]][dian2[1]].left);
            swap(map[now1[0]][now1[1]].right,map[now2[0]][now2[1]].right);
            memcpy(now1,map[now1[0]][now1[1]].down,8);
            memcpy(now2,map[now2[0]][now2[1]].down,8);   
        }
        memcpy(dian1,map[now1[0]][now1[1]].right,8);
        memcpy(dian2,map[now2[0]][now2[1]].right,8);
        swap(map[dian1[0]][dian1[1]].left,map[dian2[0]][dian2[1]].left);
        swap(map[now1[0]][now1[1]].right,map[now2[0]][now2[1]].right);
        for(int i=1;i<w;i++)//下邊 
        {   
            memcpy(dian1,map[now1[0]][now1[1]].down,8);
            memcpy(dian2,map[now2[0]][now2[1]].down,8);
            swap(map[dian1[0]][dian1[1]].up,map[dian2[0]][dian2[1]].up);
            swap(map[now1[0]][now1[1]].down,map[now2[0]][now2[1]].down);
            memcpy(now1,map[now1[0]][now1[1]].left,8);
            memcpy(now2,map[now2[0]][now2[1]].left,8);
        }
        memcpy(dian1,map[now1[0]][now1[1]].down,8);
        memcpy(dian2,map[now2[0]][now2[1]].down,8);
        swap(map[dian1[0]][dian1[1]].up,map[dian2[0]][dian2[1]].up);
        swap(map[now1[0]][now1[1]].down,map[now2[0]][now2[1]].down);
        for(int i=1;i<h;i++)//左邊 
        {
            memcpy(dian1,map[now1[0]][now1[1]].left,8);
            memcpy(dian2,map[now2[0]][now2[1]].left,8);
            swap(map[dian1[0]][dian1[1]].right,map[dian2[0]][dian2[1]].right);
            swap(map[now1[0]][now1[1]].left,map[now2[0]][now2[1]].left);
            memcpy(now1,map[now1[0]][now1[1]].up,8);
            memcpy(now2,map[now2[0]][now2[1]].up,8);
        }
        memcpy(dian1,map[now1[0]][now1[1]].left,8);
        memcpy(dian2,map[now2[0]][now2[1]].left,8);
        swap(map[dian1[0]][dian1[1]].right,map[dian2[0]][dian2[1]].right);
        swap(map[now1[0]][now1[1]].left,map[now2[0]][now2[1]].left);
    }
    memcpy(now1,pos,8);
    for(int i=0;i<n;i++)//輸出
    {
        memcpy(now2,now1,8);
        for(int j=0;j<m;j++)
        {
            cout<<map[now2[0]][now2[1]].w<<" ";
            memcpy(now2,map[now2[0]][now2[1]].right,8);
        }
        cout<<endl;
        memcpy(now1,map[now1[0]][now1[1]].down,8);
    }   
    return 0;
}

3、標算的記錄兩個方向的連結串列:
簡潔,推薦。

#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
const int MaxN = 1005003;
int n,m,q,num,v[MaxN],f[MaxN][5],lab[N][N];

inline int Readint(){
    register char ch=getchar();
    register int x=0;
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=((x+(x<<2))<<1)+ch-'0',ch=getchar();
    return x;
}

inline int getlab(int a,int b){
    int p=lab[0][0];
    for(register int i=1;i<=a;i++) p=f[p][1];
    for(register int i=1;i<=b;i++) p=f[p][0];
    return p;
}

int main(){
//  freopen("lx.in","r",stdin);
    n=Readint(),m=Readint(),q=Readint();
    for(register int i=1;i<=n;i++)
      for(register int j=1;j<=m;j++)
        v[lab[i][j]=++num]=Readint();
    for(register int i=0;i<=m+1;i++) lab[0][i]=++num,lab[n+1][i]=++num;
    for(register int i=1;i<=n;i++) lab[i][0]=++num,lab[i][m+1]=++num;
    for(register int i=0;i<=n;i++)
      for(register int j=0;j<=m;j++)
        f[lab[i][j]][0]=lab[i][j+1],f[lab[i][j]][1]=lab[i+1][j];
    for(int a,b,c,d,h,w,i=1;i<=q;i++){
        a=Readint(),b=Readint(),c=Readint();
        d=Readint(),h=Readint(),w=Readint();
        register int p1=getlab(a-1,b-1),p2=getlab(c-1,d-1);
        register int t1,t2,ww,hh;
        for(t1=p1,t2=p2,ww=1;ww<=w;ww++)
          swap(f[t1=f[t1][0]][1],f[t2=f[t2][0]][1]);
        for(hh=1;hh<=h;hh++)
          swap(f[t1=f[t1][1]][0],f[t2=f[t2][1]][0]);
        for(t1=p1,t2=p2,hh=1;hh<=h;hh++)
          swap(f[t1=f[t1][1]][0],f[t2=f[t2][1]][0]);
        for(ww=1;ww<=w;ww++)
          swap(f[t1=f[t1][0]][1],f[t2=f[t2][0]][1]);
    }
    for(int i=1,p=lab[0][0];i<=n;i++)
    {
        for(int j=1,t=p=f[p][1];j<=m;j++)
            cout<<v[t=f[t][0]]<<" ";
        cout<<'\n';
    }
    return 0;
}

4、記錄兩個方向的連結串列的指標寫法:
簡潔優美。

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}
const int Maxn=1e3+50;
struct node{
    node *right,*down;
    int val;
}Pool[Maxn*Maxn],*pool=Pool,*pos[Maxn][Maxn];
inline node* newnode(int val){
    ++pool;
    pool->right=pool->down=NULL;
    pool->val=val;
    return pool;
}
int n,m,q;
inline node* findpos(int x,int y){
    node *now=pos[0][0];
    for(register int i=1;i<=x;i++)now=now->down;
    for(register int i=1;i<=y;i++)now=now->right;
    return now;
}
inline void W(int x){
    static int buf[50];
    if(!x){putchar('0');return;}
    if(x<0){putchar('-');x=-x;}
    while(x){buf[++buf[0]]=x%10;x/=10;}
    while(buf[0])putchar(buf[buf[0]--]+'0');
}
int main(){
    n=read(),m=read(),q=read();
    pos[0][0]=newnode(0);
    for(register int i=1;i<=m;i++){
        pos[0][i]=newnode(0);
        pos[0][i-1]->right=pos[0][i];
    }
    for(register int i=1;i<=n;i++){
        pos[i][0]=newnode(0);
        pos[i-1][0]->down=pos[i][0];
        for(register int j=1;j<=m;j++){
            pos[i][j]=newnode(read());
            pos[i-1][j]->down=pos[i][j];
            pos[i][j-1]->right=pos[i][j];
        }
    }
    for(register int i=1;i<=q;i++){
        int dx1=read(),dy1=read(),dx2=read(),dy2=read(),h=read(),w=read();
        node *lup1=findpos(dx1-1,dy1),*lup2=findpos(dx2-1,dy2);
        node *lup3=findpos(dx1,dy1-1),*lup4=findpos(dx2,dy2-1);
        node *ldown1=findpos(dx1+h-1,dy1),*ldown2=findpos(dx2+h-1,dy2);
        node *rup1=findpos(dx1,dy1+w-1),*rup2=findpos(dx2,dy2+w-1);
        for(register int j=1;j<=w;j++){
            swap(lup1->down,lup2->down);
            lup1=lup1->right,lup2=lup2->right;
        }
        for(register int j=1;j<=h;j++){
            swap(lup3->right,lup4->right);
            lup3=lup3->down,lup4=lup4->down;
        }
        for(register int j=1;j<=w;j++){
            swap(ldown1->down,ldown2->down);
            ldown1=ldown1->right,ldown2=ldown2->right;
        }
        for(register int j=1;j<=h;j++){
            swap(rup1->right,rup2->right);
            rup1=rup1->down,rup2=rup2->down;
        }
    }
    for(register int i=1;i<=n;i++){
        node *p=pos[i][0];
        for(register int j=1;j<=m;j++){
            p=p->right;
            W(p->val);putchar(' ');
        }
        putchar('\n');
    }
}