第33期寧波市小學生複賽題解(花式AC)

第33期寧波市小學生複賽 題解(花式AC)

T1 數列遊戲

題目描述1

小明最近爲了鍛鍊智力,在玩一個數列求和的遊戲。設數列的長度爲n,每個 數字都是整數,且在±1000範圍內。小明能夠從這個數列裏面選一串任意長度的連續子串並求和,小明想知道子串和絕對值的最大值是多少,你能幫幫他嗎?node

題目大意1

求一個數列裏面字段和絕對值的最大值。算法

解題思路1

很顯然,這是一道 最大子段和 問題的變種,只是求的是 絕對值
最大子段和有以下方法:數組

方法1:大暴力

很是 暴力 的方法,很少說。
暴力 枚舉 字段 起點、終點 ,對於每一個子串 求和 ,最後求最大絕對值。
求和的過程能夠用 前綴和 優化,複雜度 \(O(N^2)\)\(70pt\) 到手。優化

方法2:分治

先別急着搞 \(O(N)\) ,實際上分治也很容易想到固然是萬物皆可 二分
若是用二分作法,那麼對於每一個區間,最大子段要麼就在 左邊一半,要麼在 右邊一半 ,要麼 橫跨中間
因此,顯然能夠這麼設計:spa

  1. 原問題:求 \(a\)\([L,R]\) 區間的最大子段和。
  2. 子問題1:求 \(a\)\([L,mid]\) 區間的最大子段和。
    子問題2:求 \(a\)\([mid+1,R]\) 區間的最大子段和。
  3. 基本狀況:當 \(L=R\) 時,返回 \(a[L]\)
  4. 合併:\(\max_{子問題1,子問題2,橫跨中間的}\)
    那麼,橫跨中間該怎麼搞?
    若是橫跨中間,那麼確定要包括 \(a[mid]\)\(a[mid+1]\),因此能夠把橫跨中間的字段從中點分紅兩半來看,兩邊分別線性求解以 \(a[mid]\) 爲右端點和以 \(a[mid+1]\) 爲左端點的最大字段和,再相加。
    代數式就是:\(\max_{\sum_{i=x}^{mid}a[i]}+\max_{\sum_{i=mid+1}^{y}a[i]} (l \leqslant x \leqslant mid, mid+1 \leqslant y \leqslant r)\)

核心代碼:設計

int solve ( int l, int r )
{
	
	if ( l == r ) return a [ l ];

	int mid = l + ( r - l >> 1 ), sum = 0, ansl = -1e9, ansr = -1e9;
	
	for ( int i = mid; i >= l; i -- ) ansl = max ( ansl, ( sum += a [ i ] ) );
	
	sum = 0;
	
	for ( int i = mid + 1; i <= r; i ++ ) ansr = max ( ansr, ( sum += a [ i ] ) );
	
	return max ( max ( solve ( l, mid ), solve ( mid + 1, r ) ), ansl + ansr );
	
}

方法3:貪心、DP

最大子段和的 \(O(N)\) 作法的確很基礎,但對於怎麼來的你或許不太清楚,因此這裏再提一下。
優化暴力通常分爲兩種:重複和沒必要要。
首先,若是是小於零的負數,那麼出現了沒必要要的狀況,取了還不如不取。
其次,若是畫一下圖,能夠看出不一樣起點的差值是固定的,因此只要捨棄 \([i,j]\) ,那麼全部 \([*,j]\) 均可以捨棄,直接從 \([j+1,*]\) 開始。這個算法就出來了。code

代碼很簡單,不寫了。隊列

T2 文明社會

題目描述2

在互聯網上,會有一些不文明的人發送不文明的言論。咱們的目的,就是要 自動過濾而且識別這些言論 給出一個字符串 S,表示那些可能不文明的言論。再給出一個字典 T,包含了 n 條違規的用語,T[1], T[2], …, T[n]。要在 S 中添加最少的,使得只要違規用語 T[i]在 S 中出現,就得在每一個 T[i]的字符之間添加。 好比說 S=aaabbssss,T[1]=abb,T[2]=bbss。那麼最後的符合規定的 S’就爲 aaabbssss。其中 abb 在第 3 到第 5 個字符之間出現,bbss 在第 4 到第 7 個字符出現。 數據保證字符串 S 不包括字符‘*’,且 T[i]的長度必定大於 1。 其中|S|≤1000,n≤10,1<|T[i]| ≤100;|S|表示 S 字符串的長度,|T[i]|表示 T[i] 字符串的長度。遊戲

題目大意2

\(S\) 中全部包含在 \(T\) 中的子段的每一個字符之間添加 ‘*’ ,並輸出 S’。字符串

解題思路2

這道題因爲 \(|S|≤1000,n≤10,1<|T[i]| ≤100\),能夠直接暴力作(而也沒有其餘方法)。
暴力匹配每一個 \(T[i]\) ,在匹配位置打上標記,輸出時在打標記位置輸出 ‘*’ 。

核心代碼:

for ( int i = 1; i <= n; i ++ )
      for ( int j = 1, pos = 1; j <= lens; j ++ )
      {
            if ( t [ i ] [ pos ] == s [ j ] ) pos ++;
            if ( pos == lent [ i ] )
                  for ( int k = j; k < j + lent [ i ] - 1; k ++ ) flag [ k ] = true;
      }
//輸出
for ( int i = 1; i <= lens; i ++ )
{
      printf ( "%c", s [ i ] );
      if ( flag [ i ] ) putchar ( '*' );
}

T3 陷阱

題目描述3

給定一個n*m 的網格地圖,格子有三種狀況:

  1. ‘.’表示空,能夠正常通行
  2. ‘#’表示有牆,不能通行
  3. 大寫英文字母(A~Z)表示有陷阱,能夠通行,但通過會扣必定的血量,並
    且不會消失

一共有k 個陷阱(編號從A 開始,ABCDE...),k<=26,而且給定起點,終點,
和初始血量H,行走方向只有上下左右四個方向,注意在行走過程當中不能有任意
時刻的血量小於等於0。輸出到達終點的最大血量。

題目大意3

額,真的沒有

解題思路3

算法1:dfs - 30pt

原題中有一個 「30%的樣例 k=0」,所以不考慮陷阱,能夠那 \(30pt\)
DFS很是暴力:

void dfs ( int x, int y )
{

	vis [ x ] [ y ] = true;

	if ( x == ex && y == ey ) { ok = true; return ; }

	for ( int i = 0; i < 4; i ++ )
	{
		tx = x + d [ i ] [ 0 ], ty = y + d [ i ] [ 1 ];
		if ( ! vis [ tx ] [ ty ] && tx > 0 && tx <= n && ty > 0 && ty <= m && map [ tx ] [ ty ] != '#' ) dfs ( tx, ty );
	}

}

輸出時若是ok,就輸出h,不然就直接輸出-1。

算法2:bfs - 30pt

雖然仍是 \(30pt\) ,但BFS時間更短,爲了後續方便仍是值得寫一下。

void bfs ( int sx, int sy )
{
      
      que.push ( node { sx, sy } );
      
      while ( ! que.empty ( ) )
      {
            
            t = que.front ( ); que.pop ( );

            if ( t.x == ex && t.y == ey ) { ok = true; return ; }
            
            for ( int k = 0; k < 4; k++ )
            {
                  tx = t.x + d [ k ] [ 0 ], ty = t.y + d [ k ] [ 1 ];
                  if ( tx >= 1 && tx <= n && ty >= 1 && ty <= m && map [ tx ] [ ty ] != '#' ) que.push ( node { tx, ty } );
            }

      }

}

算法3:DFS - 慢慢爆棧

再在算法1的基礎上增長參數h,能夠得出一個正確的算法:

void dfs ( int x, int y, int h )
{

	if ( x == ex && y == ey ) { ans = max ( ans, h ); return ; }

	for ( int i = 0; i < 4; i ++ )
	{
		tx = x + d [ i ] [ 0 ], ty = y + d [ i ] [ 1 ];
		if ( ! vis [ tx ] [ ty ] && tx > 0 && tx <= n && ty > 0 && ty <= m && map [ tx ] [ ty ] != '#' )
			vis [ tx ] [ ty ] = true, dfs ( tx, ty ), vis [ tx ] [ ty ] = false;
	}

}

額,TLE & MLE 祝你好運。

算法4:BFS - 真的AC了嗎

你會想,若是要求血量,不就記錄到每一個點的扣血,加入隊列時計算好了嗎:

void bfs ( int sx, int sy )
{
      
      que.push ( node { sx, sy } );
      
      while ( ! que.empty ( ) )
      {
            
            t = que.front ( ); que.pop ( );

            if ( t.x == ex && t.y == ey ) return ;
            
            for ( int k = 0; k < 4; k++ )
            {
                  tx = t.x + d [ k ] [ 0 ], ty = t.y + d [ k ] [ 1 ];
                  if ( tx >= 1 && tx <= n && ty >= 1 && ty <= m ) que.push ( node { tx, ty } ), dis [ tx ] [ ty ] = dis [ t.x ] [ t.y ] + a /*扣血,請開大一點並預處理(把'.'當作扣血0,把‘#’當作扣血∞)*/ [ map [ tx ] [ ty ] ];
            }

      }

}

BUT,這樣真的好了嗎?
設計一個數據:

S 1
7 1
E 1

額,你炸了。。。一個點能夠有多種方法到達,而最短的那一條不必定扣血最少。這道題和最短路徑沒有關係。
那咋辦呢?

鬆弛

實際上,求最短路徑有一個 「鬆弛」 算法:

if ( dis [ u ] > dis [ v ] + dam [ u ] )
      dis [ u ] = dis [ v ] + dam [ u ];

不難看出,「鬆弛」 就是經過介入第三點來縮短路徑,其實就是一個貪心。所以,有兩種 「鬆弛」 方式:

算法5:DFS鬆弛 100pt
void dfs ( int x, int y, int h )
{

	for ( int i = 0; i < 4; i ++ )
	{
		tx = x + d [ i ] [ 0 ], ty = y + d [ i ] [ 1 ];
		if ( ! vis [ tx ] [ ty ] && tx > 0 && tx <= n && ty > 0 && ty <= m && map [ tx ] [ ty ] != '#' )
			if ( dis [ tx ] [ ty ] > dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ] )
				dis [ tx ] [ ty ] = dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ], dfs ( tx, ty );
	}

}
算法6:BFS鬆弛 100pt
void bfs ( int sx, int sy )
{
      
      que.push ( node { sx, sy } );
      
      while ( ! que.empty ( ) )
      {
            
            t = que.front ( ); que.pop ( );
            
            for ( int k = 0; k < 4; k++ )
            {
                  tx = t.x + d [ k ] [ 0 ], ty = t.y + d [ k ] [ 1 ];
                  if ( tx >= 1 && tx <= n && ty >= 1 && ty <= m )
                        if ( dis [ tx ] [ ty ] > dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ] )
                              que.push ( node { tx, ty } ), dis [ tx ] [ ty ] = dis [ t.x ] [ t.y ] + a [ map [ tx ] [ ty ] ];
            }

      }

}

算法5:BFS優化

在這個BFS中,其實每一個點都只用入隊/出隊一次,數值就固定了,之後不須要更新。
所以,能夠再把FLAG數組開回來,每一個點只要入隊一次就打上標記,之後就不用再入隊了:

void bfs ( int sx, int sy )
{
      
      que.push ( node { sx, sy } );
      flag [ sx ] [ sy ] = true;
      
      while ( ! que.empty ( ) )
      {
            
            t = que.front ( ); que.pop ( );
            flag [ t.x ] [ t.y ] = false;
            
            for ( int k = 0; k < 4; k++ )
            {
                  tx = t.x + d [ k ] [ 0 ], ty = t.y + d [ k ] [ 1 ];
                  if ( tx >= 1 && tx <= n && ty >= 1 && ty <= m )
                        if ( dis [ tx ] [ ty ] > dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ] )
                        {
                              if ( ! flag [ tx ] [ ty ] ) que.push ( node { tx, ty } ), flag [ tx ] [ ty ] = true;
                              dis [ tx ] [ ty ] = dis [ t.x ] [ t.y ] + a [ map [ tx ] [ ty ] ];
                        }
            }

      }

}

嗯,恭喜你,你一不當心發明了SPFA ( Shortest Path Fast Algorithm )。

在介紹兩種算法

1. 灰常暴力的 Bellman-Ford 算法

其實對於 「鬆弛」 操做,還有另外一種更暴力的方法:不斷把矩陣裏每個點進行 「鬆弛」,直到所有完畢,沒有再更新的爲止。

void Bellman_Ford ( int sx, int sy )
{
	
        memset ( dis, 0x3f, sizeof ( dis ) );
        
	dis [ sx ] [ sy ] = 0; 
        
	for ( bool flag = true; ! flag; flag = true )
        {
		for ( int x = 1; x <= n; x ++ )
			for ( int y = 1; y <= m; y ++ )
				for ( int k = 0; k < 4; k++ )
                                {
					tx = x + d [ k ] [ 0 ], ty = y + d [ k ] [ 1 ];
					if ( tx > 0 && tx <= n && ty > 0 && ty <= m && map [ tx ] [ ty ] != '#' )
					        if ( dis [ tx ] [ ty ] > dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ] )
						        dis [ tx ] [ ty ] = dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ], flag = false;
				}

}
2. 稍稍改進的 Dijkstra 算法

你可能很快就看出來了貝爾曼-福特算法有着一個致命的缺陷:每一輪都遍歷了不少無用的點,因此能夠每一輪都只把傷害最少的一個點周圍的四個點 「鬆弛」 一下,這樣每一個點至多更新一次,快樂(誤 快了不少。另外,Dijkstra算法還能夠作一個隊列優化,基本上就變成了SPFA。

void Dijkstra ( int sx, int sy )
{
	
        memset ( dis, 0x3f, sizeof ( dis ) );
        
	dis [ sx ] [ sy ] = 0; 
        
	for ( int cnt = 1; cnt <= n* m; cnt ++ )
	{

		int mindis = 0x3f3f3f3f, minx, miny;

		for ( int x = 1; x <= n; x ++ )
			for ( int y = 1; y <= m; y ++)
				if ( ! flag [ x ] [ y ] && dis [ x ] [ y ] < mindis )
                                	mindis = dis [ x ] [ y ], midx = x, miny = y;

		if ( mindis == 0x3f3f3f3f ) break;

		flag [ minx ] [ miny ] = true;

		for ( int k = 0; k < 4; k ++ )
		{
			int tx = minx + d [ k ] [ 0 ], ty  = miny + d [ k ] [ 1 ];
                        if ( tx >= 1 && tx <= n && ty >= 1 && ty <= m )
                              if ( dis [ tx ] [ ty ] > dis [ x ] [ y ] + a [ map [ tx ] [ ty ] ] )
                                    dis [ tx ] [ ty ] = dis [ t.x ] [ t.y ] + a [ map [ tx ] [ ty ] ];
                 }
                 
	}

}

T4 汽車旅行

額,還沒寫好

相關文章
相關標籤/搜索