算法提高 盾神與條狀項鍊(靜態鏈表)

試題 算法提高 盾神與條狀項鍊

資源限制
時間限制:1.0s 內存限制:256.0MB
問題描述
  有一天,盾神撿到了好多好多五顏六色的珠子!他心想這些珠子這麼漂亮,可以做成一條項鍊然後送給他心儀的女生~於是他用其中一些珠子做成了長度爲n的項鍊。當他準備把項鍊首尾相接的時候,土方進來了。
  “哇這麼噁心的項鍊你也做得出來!!!”
  盾神自知審美不是他的長項,於是他很謙虛地請教土方,怎麼才能把項鍊做得漂亮。
  “這個嘛~ 首先你要在這裏加上一個這種顏色的珠子,然後在這裏去掉這個珠子,然後……,最後你看看是不是漂亮很多咧~”土方一下子說出了m個修改步驟。
  盾神覺得這個用人工做太麻煩了,於是交給了你。
輸入格式
  第一行兩個數,分別爲n,m。
  第二行n個數,表示盾神一開始的項鍊。第i個數表示第i顆珠子的顏色。
  接下來m行,爲以下形式之一:
  ADD P Q:表示在顏色爲P的珠子前面加上一個顏色爲Q的珠子。
  DEL P:表示把顏色爲P的珠子去掉,如果它不在端點處,則需要把它旁邊的兩顆珠子連起來。例如某時刻項鍊狀態爲1 4 5 8,則執行DEL 4會變成1 5 8,執行DEL 1會變成4 5 8。
  輸入保證在每次操作之前,項鍊有顏色爲P的珠子,且任意時刻珠子顏色互不相同
輸出格式
  第一行爲一個數len,爲做完所有操作後,項鍊的長度。
  第二行len個數,表示此時項鍊的狀態。第i個數表示第i顆珠子的顏色。
樣例輸入
10 5
1 2 3 4 5 6 7 8 9 10
DEL 5
ADD 7 5
DEL 10
ADD 4 20
ADD 20 12
樣例輸出
11
1 2 3 12 20 4 6 5 7 8 9
數據規模和約定
  表示顏色的數字不超過10^ 5的正數,1<=n<=10^ 4,1<=m<=10^4。

題解

一開始使用動態鏈表來做,因爲要不停的定位到珠子來進行增減操作,而鏈表只能從頭尾遍歷,這樣實現效率很低,於是轉向靜態鏈表,題目說表示顏色的數字不超過10^ 5,之前又說每個顏色的珠子只有一個,所以開一個10^ 5大小的數組node[]來模擬,這個數組是一個結構體數組,存放其左右結點的編號每次增加或者減少珠子就只要維護數組中的幾個元素就行了

ADD操作 比如要在 … a b… 中間插入c,只要:

node[a].right=c; node[c].left=a;
node[c].right=b; node[b].left=c;
*(如果要在串首增加元素,要特殊處理,詳見下面代碼)*

DEL操作 比如要在 … a b c … 刪除b,只要:

node[a].right=c; node[c].left=a;
*如果要嚴謹一點,還要加上*
node[b].left=node[b].right=-1;

AC代碼如下,有清晰的註釋

//算法提高 盾神與條狀項鍊 
#include<stdio.h>
#include<cstring>
using namespace std;

const int MAXN=1e5+1;
struct Node{
	int left,right;		//存儲珠子左右的編號 
	Node(){left=right=-1;}
};
Node node[MAXN];

int main(){
	char q[5];
	int n,m;		//n個珠子,m次操作 
	int p=0,c;		//p-上一次珠子  c-當前珠子編號 
	int begin,len;	//begin-串開頭的珠子  len-珠子的總數量 
	
	scanf("%d%d",&n,&m);
	len=n;				//開始的時候串長爲n 
	for(int i=0;i<n;i++){
		scanf("%d",&c);
		if(i==0){		//當輸入第一個珠子時,特殊處理 
			begin=c;
			p=c;
			continue;	
		}
		node[p].right=c;	//更改前一個珠子的右邊(下一個)珠子的編號 
		node[c].left=p;		//更改當前珠子的左邊(前一個)珠子的編號 
		p=c;				//更新p 
	}
	while(m--){
		scanf("%s",q);
		if(!strcmp(q,"ADD")){		//添加操作 
			len++;				//串長度加 1 
			int a,b;
			scanf("%d%d",&a,&b);
			if(node[a].left==-1){		//如果是在第一個珠子前面增加 
				begin=b;			//更改串首珠子的編號 
				node[b].right=a;
				node[a].left=b;
				continue;
			} 
				//把b插在a和a左邊珠子中間 
			node[node[a].left].right=b;		//原本a左邊珠子的右邊爲b 
			node[b].left=node[a].left;		//b的左邊珠子爲原本a左邊的珠子 
			node[a].left=b;					//a的左邊珠子爲b 
			node[b].right=a;				//b的右邊珠子爲a 
		}
		else if(!strcmp(q,"DEL")){		//刪除操作 
			len--;				//串長度減 1 
			int a;
			scanf("%d",&a);
			if(node[a].left==-1){		//如果是第一個珠子 
				begin=node[a].right;		//更新串首珠子的編號 
				node[node[a].right].left=-1;	//更新a右邊的珠子的左邊編號
				node[a].left=node[a].right=-1; 
				continue;
			}
				//刪除a,更新其左右信息 
			node[node[a].left].right=node[a].right;		//更新a左邊珠子的右邊信息 
			node[node[a].right].left=node[a].left;		//更新a右邊珠子的左邊信息 
			node[a].left=node[a].right=-1; 
		}
	}
	printf("%d\n",len);
	printf("%d",begin);
	while(node[begin].right!=-1){
		printf(" %d",node[begin].right);
		begin=node[begin].right;	
	}
	
	return 0;
}

PS. 劉汝佳著的紫書中有一題,和這題思路差不多,與大家分享:
移動盒子 (Boxes in a Line,UVa 12657)
你有一行盒子,從左到右依次編號爲1,2,3,…,n。可以執行以下4種指令:
1 x y:表示把盒子x移動到盒子y的左邊(如果x已經在y的左邊則忽略此指令)。
2 x y:表示把盒子x移動到盒子y的右邊(如果x已經在y的右邊則忽略此指令)。
3 x y:表示交換盒子x和y的位置。
4:表示反轉整條鏈。

指令保證合法,即x不等於y。

例如當n=6時在初始狀態盒子序列爲爲:1 2 3 4 5 6;
  執行1 1 4後,盒子序列爲:2 3 1 4 5 6;
  接下來執行2 3 5,盒子序列變爲:2 1 4 5 3 6;
  再執行3 1 6,盒子序列變爲:2 6 4 5 3 1;
  最終執行4,盒子序列變爲:1 3 5 4 6 2。

【輸入格式】
輸入包含不超過10組數據,每組數據第一行爲盒子數n和指令m,以下m行每行包含一條指令。
【輸出格式】
每組數據輸出一行,即所有奇數位置的盒子編號之和。位置從左到右編號爲1~n。
【輸入樣例】
6 4
1 1 4
2 3 5
3 1 6
4
6 3
1 1 4
2 3 5
3 1 6
100000 1
4
【輸出樣例】
12
9
2500050000
【數據範圍】
N,M<=100000

// UVa12657 Boxes in a Line
// Rujia Liu
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 100000 + 5;
int n, left[maxn], right[maxn];

inline void link(int L, int R) {
  right[L] = R; left[R] = L;
}

int main() {
  int m, kase = 0;
  while(scanf("%d%d", &n, &m) == 2) {
    for(int i = 1; i <= n; i++) {
      left[i] = i-1;
      right[i] = (i+1) % (n+1);
    }
    right[0] = 1; left[0] = n;
    int op, X, Y, inv = 0;

    while(m--) {
      scanf("%d", &op);
      if(op == 4) inv = !inv;
      else {
        scanf("%d%d", &X, &Y);
        if(op == 3 && right[Y] == X) swap(X, Y);
        if(op != 3 && inv) op = 3 - op;
        if(op == 1 && X == left[Y]) continue;
        if(op == 2 && X == right[Y]) continue;

        int LX = left[X], RX = right[X], LY = left[Y], RY = right[Y];
        if(op == 1) {
          link(LX, RX); link(LY, X); link(X, Y);
        }
        else if(op == 2) {
          link(LX, RX); link(Y, X); link(X, RY);
        }
        else if(op == 3) {
          if(right[X] == Y) { link(LX, Y); link(Y, X); link(X, RY); }
          else { link(LX, Y); link(Y, RX); link(LY, X); link(X, RY); }
        }
      }
    }

    int b = 0;
    long long ans = 0;
    for(int i = 1; i <= n; i++) {
      b = right[b];
      if(i % 2 == 1) ans += b;
    }
    if(inv && n % 2 == 0) ans = (long long)n*(n+1)/2 - ans;
    printf("Case %d: %lld\n", ++kase, ans);
  }
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章