洛谷P3943 星空 題解

2022-04-12 02:02:33 字數 2953 閱讀 7576

一道很好的鍛鍊思維難度的題,如果您能在考場上直接想出來的話,提高組450分以上就沒問題了吧。(別像作者一樣看了好幾篇題解才勉強會)

先提取出題目大意:給定乙個長度n<=40000的01串,其中1的個數<=8,有m種操作,每次操作都是把乙個該操作對應長度的區間取反,或者說異或上1,求使整個串變為只有0的串的最小操作次數。 

首先對於一次操作,肯定不能暴力地乙個個去取反吧。優化區間操作,要麼用資料結構,要麼用字首和或差分。其實這裡可以用差分來優化:建立新的下標最大為n+1、下標正常從1開始的d陣列,d[i]=a[i]^a[i-1],每次區間修改時,設修改的區間的左端點為l,右端點為r,只要讓a[l]和a[r+1]分別異或上1就行了。

解釋一下:這裡的差分中的「差」已經不能簡單理解為減法做差了,而是邏輯上的「差距」。差分維護的是相鄰元素間的邏輯關係,從而使能從初始狀態(a[0])通過差分陣列表達的邏輯關係推出某個位置上a的值(從形式上看就是求字首)。對於異或來說,正好滿足這樣的性質:我們讀入串時從a[1]開始讀入,那a[0]沒管它的話就會是0,那麼發現從它開始向後與d陣列做字首異或時,設當前做到第i個位置了(即當前值=a[0]^d[1]^d[2]^……^d[i]),則當前值就是a[i]的值,同樣對於差分優化的區間操作來說,對  左端點  和   右端點+1  處取反後,在求一遍字首異或,發現對於那個要修改的區間,真的就取反了。(可以這麼考慮:對於區間中的位置來說,修改後再求完字首後,每位都比修改前的這位多異或了1,故取反;對於區間後面的位置,修改後再求完字首後,每位都比修改前的這位多異或了2個1,就不會改變,總體上看,這個區間就被取反了。這要依賴於異或這個運算可交換且有單位元(麼元)、且1有對於異或的逆元(其實所有數都有)(逆元可不只限於取模喲))。因為要能實現右端點等於n的區間修改,所以d陣列的最大下標為n+1,同時也是為下文1的個數為偶數的結論做鋪墊。

那麼問題就變為:給定乙個長度n<=40001的01串,其中1的個數<=16,有m種操作,每次操作都是把下標差該操作對應長度的兩個數取反,求使整個串變為只有0的串的最小操作次數。

解釋一下:考慮將原串的1全都拿出來後乙個個加入到乙個全是0的串u裡,形成乙個與原串完全一樣的串,看看u串對應的差分陣列的變化,發現每次加入乙個1,差分陣列要麼新增2個1(加入u串的1在u串中左右沒有相鄰的1),要麼有乙個1往後或前移乙個位置(加入u串的1在u串中的左邊或右邊中只有一邊有相鄰的1),要麼減少2個1(加入u串的1在u串中的左邊和右邊都相鄰的1),因為要從u串中加最多8個1,顯然對應的差分陣列最多就16個1,同時差分陣列中1的個數一定為偶數。

顯然對於每次操作取反的兩個數中一定有乙個1,不然這次操作不但沒用,還多出了2個1,浪費次數還增多任務。考慮每次取反的兩個數:

1、有1個1:那麼結果是原來1的位置現在變成了0,原來0的位置現在變成了1。形象化地,1從原來的位置移動到了另乙個位置

2、有2個1:那麼這兩個1都會變為0,形象化地乙個1移動到了有1的位置,兩個1碰到一起就消失了。

於是問題又被形象化地轉化為:給定乙個長度n<=40001的01串,其中1的個數<=16,有m種移動長度,每次移動都是把1個1移動這個移動相應的移動長度,若2個1碰到一起(在同乙個位置)就會消失,求使整個串的所有1消失的最小移動次數。

如果我們知道了這些1兩兩碰到一起消失的最小移動次數,跑個狀壓dp就可以嘍。而這些1兩兩碰到一起消失的最小移動次數,正可以對每個1跑一遍bfs求得(把串的每一位看成乙個點,向這個點能一次移動到的所有點都連一條長度為1的邊),時間複雜度o(kmn),完全可以接受。至於狀壓dp,推薦寫o(k*(2的2*k次方))的dp,如果寫o((k的平方)*(2的2*k次方))的dp,雖然能過這個題,但是很容易到考場上被卡。關於o(k*(2的2*k次方)),我們可以從當前狀態向以後狀態轉移,當前狀態第乙個沒有被消去的1遲早要被消去,不如先消去,轉移到包括這個1的狀態,容易發現每乙個消去所有1的狀態,都可以通過這樣的策略實現出來,所以這樣是正確的。

最後看看**吧:

1 #include2 #include3 #include4 #include5

6using

namespace

std;78

const

int n=40005,m=64,k=8;9

10int n,k,m,x,a[n],d[n],caolen[m+2],d1[k<<2],cntd1,lst[n],nxt[n*m<<1],to[n*m<<1

],cnt;

11int

wei[n],vis[n];

1213

long

long dp[1

<2],dis[k<<3][k<<3

];14

15char

ch;16

17 inline int

read()

1825

26 inline void addedge(int u,int

v)27

3233

struct

nodehead;

3637 queueq,ling;

3839 inline void bfs(int w,int ord)//

變數含義:這個1在原串中的位置,這個1在d1(將d中的1又單獨存了一下)中的位置

40);

46while(!q.empty())

47);60}

61}62}

6364 inline void

init()

657980}

81for(int i=1;i<=n+1;++i)

82for(int j=1;j<=m;++j)

8389 memset(dis,0x3f,sizeof

dis);

90for(int i=1;i<=cntd1;i++)

91bfs(d1[i],i);92}

9394

intmain()

95110

}111 printf("

%lld

",dp[lim]);

112return0;

113 }

洛谷P3943 星空

洛谷p3943 星空 命運偷走如果只留下結果,時間偷走初衷只留下了苦衷。你來過,然後你走後,只留下星空。逃不掉的那一天還是來了,小 f 看著夜空發呆。天上空蕩蕩的,沒有一顆星星 大概是因為天上吹不散的烏雲吧。心裡吹不散的烏雲,就讓它在那裡吧,反正也沒有機會去改變什麼了。小 c 拿來了一長串星型小燈泡...

洛谷 P3943 星空

有乙個長度為n的01序列,有k個1 每次可以將給定長度的子串取反,即0 1,1 0 求最少幾次可以將整個序列都變為0 差分 狀壓dp 首先將原序列異或差分 每消去一對1就是將1移到一起 先用bfs預處理出每兩個1移到一起的最小步數 然後用狀壓dp來求總的最小步數 include include co...

洛谷 P3943 星空

命運偷走如果只留下結果,時間偷走初衷只留下了苦衷。你來過,然後你走後,只留下星空。逃不掉的那一天還是來了,小 f 看著夜空發呆。天上空蕩蕩的,沒有一顆星星 大概是因為天上吹不散的烏雲吧。心裡吹不散的烏雲,就讓它在那裡吧,反正也沒有機會去改變什麼了。小 c 拿來了一長串星型小燈泡,假裝是星星,遞給小 ...