常用技巧精選 反轉(開關問題)

2022-09-08 03:45:12 字數 4839 閱讀 9262

例.給定乙個01串,現有翻轉規則:翻轉某乙個位置時其後面2個位置也會跟著翻轉,也就是每次翻轉都會翻轉3個連續的位置。要將01串全部翻轉為0,求最小的翻轉次數

形似這類題的問題叫做翻轉問題,也可以叫開關問題,對於這類題通常有以下的特點,思考一下就可以想到。

1.交換區間得反轉順序隊結果沒有影響。

2.此外可以知道對於同乙個區間進行兩次以上得反轉是多餘的。

3.由此,反轉問題(也叫開關問題)就轉化成求反轉區間的集合,常常和列舉一起服用。

1. poj 3276

題意:n個牛 每個都有一定的方向 b背對 f表示頭對著你 給你乙個裝置 每次可以選擇連續的k個牛反轉方向 問你如何選擇k 使得運算元最少 k也應盡量小.

思路:

定義 f[i]:區間[i,i+k-1]進行反轉的話就為1,否則為0

分析:即先反轉1到3號牛, 然後在反轉3到5號, 最後反轉5號到7號。 步驟如下:

bbf bfbb ----------> ffb bfbb ---------> ff ffb bb -----> ffff fff

我們看出, 對乙個區間(大小為k, 當然連續)反轉兩次以上是多餘的, 也就是說需要反轉, 一次就夠, 在反, 就等於沒反了。

於是問題就變成了求解需要被反轉的區間的集合。 我們首先考慮最左端的牛, 包含這頭牛的區間只有乙個, 即[0, k - 1], 如果這頭牛面超前,

就不需要反轉, 否則, 這頭牛面朝後, 當然這個區間(即[0, k - 1])需要被反轉了。這樣問題的規模就變為n - 1, 不斷的重複下去, 即可以求

出反轉的次數了。

忽略掉每個區間重複反轉的多與操作,只要存在所有牛面朝前的辦法, 那麼操作就和順序無關了, 因為每個區間只反轉一次或者不反轉。

演算法複雜度如何呢?

對所有的k(k 從1 到n)遍歷搜尋一次, 最壞情況下, 需要進行 n - k + 1次的反轉(最後k- 1個無法反轉, 要考慮可行性, 因為每次

必須要轉k頭牛, 而不能少於k頭牛), 每次反轉需要操作k頭牛, 所以總的複雜度為o(n^3)。

但是其實我們並不需要每次非要操作k頭牛真的反轉, 我們可以通過記錄下每個區間是否反轉了這一資訊, 避免去做每一次的k頭牛反轉這一工作。 這樣我們可以把時間複雜度降為o(n^2)。

具體的, 記錄 f[i] = 區間[i, i + k - 1]是否進行了反轉, 若反轉了 f[i] = 1, 沒有反轉則記錄 f[i] = 0, 這樣我們考慮第i 頭牛時(方向dir[i], 記為0的時候, 表示朝前, 為1 表示朝後), 如果 ∑f[j], 從 i - k + 1 累加到 i- 1, 若這個sum 為奇數, 表明i頭牛已經進行了奇數次的反轉, 也就是說, 此時這頭牛的方向和其最開始的方向相反, 同理, 若這個sum為偶數, 表明這頭牛已經進行了偶數次的反轉, 也就是此時這頭牛的方向dir[i]和其最開始的方向相同。

我們有如下的觀察:

∑ f[j] = ∑f[j] + f[i] - f[i - k + 1]

注意, 第乙個的求和 從 (i + 1) - k + 1 到 i

第二個求和從 i - k + 1 到 i - 1

有個上述公式, 我們就可以每一次在常數時計算上述的sum了。

另外我們有如下的觀察:

dir[i] + sum <---------> 0 + 奇數, 為奇數, 表示還需要反轉一次(原方向就是朝前的了, 已經進行奇數次反轉, 變成了朝後)

dir[i] + sum <---------> 0 + 偶數, 為偶數, 表示不用反轉了

dir[i] + sum <---------> 1 + 奇數, 為偶數, 表示不需要反轉了, 原方向為1, 奇數次後為超前(即0)

dir[i] + sum <---------> 0 + 奇數, 為奇數, 表示還需要反轉一次, 原方向為0, 已經朝前了。

綜上, 我們總結, 當dir[i] + sum 為奇數的時候, 就需要反轉, 為偶數時, 就不需要反轉了。

總的程式如下:

#include #include #include using namespace std;

const int max_n = 5000 + 10;

int n;

int dir[max_n];

int f[max_n];

int calc(int k)

sum += f[i];

if(i - k + 1 >= 0)

}// 檢查最後面的情況(k- 1)頭牛

for(int i = n - k + 1; i < n; i++)

if(i - k + 1 >= 0)

}return res;}

int main()

int k = 1;

int m = n;

for(int k = 1; k <= n; k++)

}printf("%d %d \n", k, m);

}}

2. poj 3279

題意:乙個m*n的黑白棋棋盤擺滿棋子,每次操作可以反轉乙個位置和其上下左右共五個位置的棋子的顏色,求要使用最少翻轉次數將所有棋子反轉為黑色所需翻轉的是哪些棋子. 

思路:和 1 題一樣,同乙個格仔(位置)翻轉兩次的話就會恢復原狀,所以多次翻轉是多餘的。此外,翻轉的格仔的集合相同的話,其次序是無關緊要的。因此總共有 2nm 種方法,顯然效率太低。

聯想到上一道題,讓最左端的牛反轉的方法只有一種,不妨先看一下最左上角的格仔。在這裡,除了翻轉 (1, 1)之外,翻轉(1, 2)和(2, 1)也可以把這個格仔翻轉,所以像之前那樣直接確定的辦法行不通。

於是不妨先指定好最上面一行的翻轉方法。此時能夠翻轉(1, 1) 的只剩下(2, 1)了,所以可以直接判斷(2, 1)是否需要翻轉。類似地(2, 1)~(2, n)都能這樣判斷,如此反覆下去就可以確定所有的格仔的翻轉方法。最後(m, 1)~(m, n)如果並非全為白色,就意味著不存在可行的操作方法。

像這樣,先確定第一行的翻轉方式,然後可以很容易判斷這樣是否存在解以及解的最小步數是多少,這樣將第一行的所有的翻轉方式都嘗試一次之後就能求出整個問題的最小步數。這個演算法中最上面一行的翻轉方式共有 2n 種,複雜度為o(mn 2n) 

為什麼要用搜尋?因為n和m很小,最大是15

為什麼要用二進位制搜尋?因為是列舉狀態,而且是每行每列的狀態

怎麼樣列舉最好?列舉第一行,然後依次遍歷其他行,列舉第一行的複雜度是2的n次方,然後遍歷全矩陣式n*m,整個複雜度相乘沒有問題

怎麼搜尋呢?用乙個整數表示1行的狀態,比如255=11111111(2),那麼迴圈就是從0到255表示256種情況,255的第0位至第7位均是1,所以呢,表示全部需要翻,然後從第二行到第n-1行依次推理得需要或者不需要翻,再用最後一行來檢驗方案是不是可行

#include#includeusing namespace std;

const int dx[5]=;

const int dy[5]=;

int m,n,m[20][20],tmp[20][20],ans[20][20],cnt;

// 查詢顏色

int get(int x,int y)

int t=cal();

if(t4. poj 3185

題意:將一列碗翻成口朝上,翻轉一次可能同時反轉3個或2個(首尾),求最小翻轉次數。

思路:這題分析一下,可以知道選擇從第乙個開始翻,還是從第二個開始翻,會導致兩種不同的狀態和結果,但都是唯一的。所以就列舉第乙個還是第二個開始翻,然後從左往右依次判斷,接下來每個點需不需要翻轉取決它左邊是不是朝下。

// #include#include #include #include #include // for memset

#include // priority_queue

#include #include // multiset set>大到小

#include // vector().swap(v);清空釋放記憶體

#include #include // auto &name : stlname name.

#include #include #include //__builtin_popcount(ans);//獲取某個數二進位制位1的個數

using namespace std;

#define rep(i,a,n) for(int i=a;i<=n;i++)

#define per(i,a,n) for(int i=n;i>=a;i--)

#define read_a_int(x) scanf("%d",&x)

#define read(x,y) scanf("%d%d",&x,&y)

typedef long long ll;

const int inf = 0x3f3f3f3f;

const int mod1e9 = 1000000007;

const int mod998 = 998244353;

const int mod = mod1e9;

const int max_n = 5000 + 10;

int bows[25],flip[25];

int main(void)

;int get(int x,int y)

return c%2;

}int calc()}}

for(int j=0;j<6;j++)

int res=0;

for(int i=0;i<5;i++)

}return res;

}int main()

int ans=31;

for(int i=0;i<1<<6;i++)

int num=calc();

if(num}

printf("puzzle #%d\n",cnt++);

for(int i=0;i<5;i++)}}

}

反轉(開關問題)

poj3276 n頭牛排成一列,牛頭向前或向後。擁有一台自動轉向機器,設定數值k,每次使用可以令k頭連續的牛轉向。求為了讓所有的牛都面向前方需要的最少操作次數m和對應最小的k。首先交換區間順序對結果是沒有影響的,此外對同乙個區間進行兩次以上反轉是多餘的。因此,問題轉化成了求需要被反轉的區間的集合。於...

反轉 開關問題 挑戰程式設計競賽

例題2例題3 農場主約翰把他的 n 1 n 5,000 頭奶牛排成一排,很多都是面向前方的,就像好奶牛一樣。然而,有些人是面向過去的,而他需要所有人都面向未來,以使他的生活變得完美。幸運的是,fj最近買了一台自動翻牛機。因為他購買的是折扣型,所以必須一次性轉 k 1 k n 頭,且只能轉排在一起的奶...

poj3276 反轉 開關問題

題目大意 給你乙個長度為n的字串,包含字母f和b 你可以把區間k 乙個常數 內的所有f變成b,b變成f。為了把這個字串都變成f,求變化的最小次數和其對應的k的值 分析 挑戰程式設計競賽 反轉法的例題,此做法非書上做法 列舉k,對於每個k,只要序列最左端的b變成f,然後依次變化,得到答案 列舉起點然後...