牛客CSP S提高組賽前集訓營1

2022-06-03 06:45:11 字數 3424 閱讀 3653

比賽鏈結

官方題解

before:t1觀察+結論題,t2樹形dp,可以換根或up&down,t3正解妙,轉化為圖上問題。題目質量不錯,但資料太水了~。

一共n個石子堆,每個石子堆有ai個石子,兩人輪流對石子塗色(先手塗紅,後手塗藍),且需要保證當前回合塗的石子顏色不能和它相鄰的兩個同色,誰塗不下去誰輸。一共t個詢問,對於每個詢問輸出先手必勝還是後手必勝。

\(1<=n<=10^3,1<=ai<=10^9,1<=t<=10^2\)

標籤:模擬,猜結論

當n=1時,模擬一下可以發現,除非石子數=1,否則後手必勝。當增加多堆石子時,假設當前局面是p必敗,則p肯定要去新的一堆裡塗,則對於新的一堆石子p成為了先手,假設新的一堆石子數》1,則p任然必敗,反之可以轉移,p的另一方成為了下一堆的先手。也就是說只有數量為1的石子堆能夠改變勝負狀態。

所以,只需統計石子數為1的堆的數量\(cnt1\),根據\(cnt1\)的奇偶性來判斷。若為奇,則先手必勝;反之先手必敗。

#includeusing namespace std;

inline int read(){}

int main()

if(o==0)puts("hamster");

else puts("rabbit");

}}

見原題面。

標籤:樹形dp,換根法,up and down

暴力做法是\(o(n^2)\)的,將每個點都提做根跑一遍樹形dp。優化很明顯是去換根

由於標程給的換根法寫的非常板子(雖然有點長),所以這裡按標程的思路走一遍,整理一下換根法的大致思路。

step1:預處理dfs

隨便找乙個節點作為根,進行dfs/樹形dp,處理出所有節點此時的答案(只考慮自己子樹下的答案)。

step2:換根

平時做題打換根都是直接去統計,加加減減,雖然碼量小但不同的題目細節不同,除錯起來不太方便。標程中將換根分為兩部分。第一部分\(cut\),先分別消除彼此的影響,逆操作;第二部分\(link\),對更改後的陣列正操作一遍。

在本題中有兩個詢問。弄兩個陣列分別針對題目的兩個詢問,第乙個比較好理解,第二個比較抽象,根據**y一下。

\(dp[x][i]\):換根前,只考慮x的子樹時,與x距離為i的子孫數量。

\(dp2[x][i]\):換根前,只考慮x的子樹時,當影響範圍為i時,x影響到的子孫的擁擠程度之乘積。

將節點1作為根,預處理dfs一遍。下面的操作都是將\(sonx\)的貢獻加給\(x\)的正操作

void dp_dfs(int x,int fa)
逆操作、正操作都與之前的預處理dfs類似。

標程的換根法**

無根樹dp還可以up and down去做。網上關於這一塊內容不多,而且很多blog都說和換根法差不多。

比如對於這題的詢問一,要求樹中離x距離小於等於k的節點數目,可以分解為這樣兩個問題,①x子樹中距離小於等於k的節點數目+②x子樹外距離小於等於k的節點數目,對這兩個問題分別dfs一遍,問題①用兒子更新自己,問題②用自己更新兒子,最後將兩部分的貢獻合起來。好像確實和換根差不多,但實現起來感覺up&down的思路更清晰些。

下面是up&down的**

#include#define mod 1000000007

#define int long long

using namespace std;

const int n=1e5+10;

vectorg[n];

int num[n][11],dp[n][11];

int outnum[n][11],outdp[n][11];

int n,k,ans1[n],ans2[n];

namespace upanddown

return res;

} void dfs(int x,int fa)

}

資料範圍加到\(10^5\)。把問題拎到圖上去,建模。

對於一張魔術撲克\((a,b)\),將編號為\((a,b)\)的節點先連一條無向邊,這樣會連出共\(k\)條邊,可能會有重邊,可能會形成多個聯通塊,每個聯通塊可能包含環也可能只是一棵樹。現在對於詢問\((l,r)\),你需要將其中的若干條無向邊變成有向邊,也就是指定這條邊指向兩個端點中的乙個,使得\((l,...,r)\)中的點的入度均不為0。

現在這個問題就是原問題套件衣服,不過現在到了圖上看起來比較可做?。

引理1:對於無向連通圖s。試將所有無向邊改為有向邊,當且僅當s中存在至少乙個環(包括自環)時,能夠使得s中的所有節點入度不為0。

證明:如果不存在環,那就是一棵普通的樹,對於每條邊,最優排布時應該是讓每條邊都指向深度較大的那個(指向兒子),但最後對於根節點,它的入度為0,不合法。如果這時候樹上出現了環,由人類智慧型可知,會有多餘的邊剩給根節點,此時一定合法。

也就是說,當我們詢問的\((l,r)\)時,只要存在乙個不含環的聯通塊,滿足這個聯通塊內的節點編號均被l,r覆蓋,則不存在可行解能打出\([l,r]\)的順子。

這就變成了乙個簡單的線段覆蓋問題。接下來做法就很多了樹狀陣列,set之類的,可以參考官方題解。下面給出一種較簡潔的做法。

理一遍流程,在輸出魔術撲克\((a,b)\)時,利用並查集維護聯通塊的資訊,如果此前\(a,b\)已經在同一聯通塊,則合併這條邊後,這個聯通塊就有了環,標記一下。連完邊後,找出所有不含環的聯通塊,記該聯通塊內編號最大的節點為\(ma\),編號最小的節點為\(mi\),則視\([mi,..,ma]\)為一條線段,對於詢問直接看\((l,r)\)會不會把某條線段覆蓋了,如果會覆蓋某條線段則輸出no,反之yes。看會不會把某條線段覆蓋可以離線詢問,然後對詢問和原有線段都排個序掃一遍就好了。

時間複雜度為\(o(nlogn)\)。

**如下:

#includeusing namespace std;

const int n=1e5+10;

int n,m;

int fa[n],mi[n],ma[n],huan[n],cover[n];

int find(int x)

struct segque[n];

vectorg;

inline bool cmp(seg p,seg q)

for(int i=1;i<=q;i++)puts(cover[i]?"no":"yes");

}

牛客CSP S提高組賽前集訓營2

然後隨便用乙個資料結構維護一下就行了,我寫的線段樹。我們先找出每個環,然後我們先刪連線環的邊,每刪一條就可以多產生乙個聯通塊,在考慮刪環邊,發現從最大的環刪起一定最優 因為你刪的第一條邊得不到任何新的聯通塊 就很容易想到tar jantarjan tarjan 發現你只能寫出80 8080 分,因為...

牛客CSP S提高組賽前集訓營5(待更)

題目描述 神樹大人造了乙個長為n的01序列,並邀請無所事事的神j來和他博弈。每一輪裡,若這個序列的第1項是0,那麼神樹大人可以選擇讓它不變或者變成1 若這個序列的第1項是1,那麼神j可以選擇讓它不變或者變成0。接著對這個序列進行旋轉操作 即將第1項放到第n項的後面,其他項依次替補。如果這個序列變為全...

牛客CSP S提高組賽前集訓營5 解題報告

linker 總分 100 100 40 240 結論題。無論如何神j都會贏。最優決策 神樹變化了我就不變,神樹不變我就變化。includeusing namespace std typedef long long ll const int mod 998244353 inline ll pow l...