CodeTON Round 2/退役划水(18)

這一篇沒什麼意思,就是自己做了場CF的比賽玩玩

日常做點小水題 防止腦子生鏽

不得不說還是CF題有意思

題面放上面,題解放下面,感興趣的可以想一想

 

A. Two 0-1 Sequences

題意:給你一個起始01串和一個目標01串。你每次可以進行操作:取出目前串的前兩個字符,然後把它們的$max$或者$min$放回去。問能否通過操作把起始串變成目標串。

複雜度要求$O(n)$

評價:純純嚇唬人

 

B. Luke is a Foodie

題意:有$n$道菜,每道菜有個屬性$a_i$。你有個容忍度$v$和食物親和度$s$。你能喫一道菜當且僅當$|a_i-s| \leq v$。你可以隨時改變你的親和度。

現在你要按順序從$1$號菜喫到$n$號菜。但你懶,問你最少需要改變幾次親和度。

複雜度要求$O(n log)$。再多個$log$行不行我也不知道。

評價:有點無聊

 

C.Virus

題意:有$n$個房子圍城一個圈,依次編號$1$到$n$,其中有$m$個房子裏的人得了灰指甲,從第一天開始一個傳染倆(相鄰的兩家)。

但是你每天可以選擇一戶尚未被感染的人,給他們用亮甲,這樣他們以後就再也不會得灰指甲了。(已經感染的就擺爛了)

問如果你亮甲用的好的話最少有幾戶得了灰指甲的。

複雜度要求$O(m log)$(n很大)

評價:你看我都這麼翻譯題面了這題能無聊嗎

 

D.Magical Array

題意:有一個數列$a$長爲$m$,有$n$個人,其中有恰好一個人是笨比。

除了笨比以外,所有人可以按照這個方法操作數列:任選$1<i,j<m$,使$a[i]--,a[j]--,a[i-1]++,a[j+1]++$。每人至少操作一次

笨比也聽了,但是笨比畢竟是笨比,他每次是使$a[i]--,a[j]--,a[i-1]++,a[j+2]++$。他也至少操作了一次

現在給你$n$個被操作後的數列,問哪個是笨比數列,同時還問這個數列被笨比操作了多少次。

複雜度要求$O(nm)$

評價:尚且算有趣的腦洞題,但是腦洞並不大(感覺之前似乎見過)

 

E.Count Seconds

題意:有一個$DAG$,且只有恰好一個點其出度爲$0$。每個點都有一個非負權值$a_i$(可以是$0$)

在每一秒開始時,你都會記錄下所有權值非$0$的點,這些點構成的集合稱爲$S$。

此後,對於$S$中的每個點$v$,你會先使$a_v$減少$1$,然後使$v$的所有出邊指向的點$u$其$a_u$增加$1$

由於是$DAG$所以這些數最後一定都會從出度爲$0$的那個點流出(消失)。問多少秒之後所有點的權值都變成$0$了。

複雜度要求$O(nm)$。初始權值可以很大。

評價:更無聊一些的腦洞題。要注意考慮到那唯一的出點可能有了一段時間的權值之後又沒有了之後又有了。

 

F.Colouring Game

題意:$A,B$兩個人在一個長度爲$n$的序列上博弈。最開始序列中每個位置要麼是$A$要麼是$B$。$A$先手

每次輪到$A$行動時,$A$需要選擇相鄰的兩個位置,且這兩個位置中至少要有一個位置是$A$,然後把這兩個位置都變成空的

輪到$B$時也類似,不過$B$選的位置至少要包含一個$B$。要注意,選擇的位置可以包含空的,只要另一個格子有自己要的就行。

(如$A$選擇的位置可以是$AA,AB,BA,A_,_A$這幾種,下劃線表示空)

輪流來,誰不能行動了誰就輸了。問最優策略下誰會贏

複雜度要求$O(n)$

評價:很不錯的思維題。很有意思。很博弈。也很搞心態。

 

G.沒看

H1.懶得寫(似乎是個多項式$exp$的模板題)

H2.不會

 


 

題解區(我沒看官方題解)

A.

你每次操作只能動最靠左的兩個數,每次還會使長度縮短1。

那右邊的字符肯定是沒法改變的了,你只能操作恰好$n-m$次(分別是起始和目標串長度)來決定操作結果的第一個字符。

剩下的$m-1$個字符是固定的,如果目標串和起始串的最後$m-1$個字符不一樣那就直接$No$

然後前面這個$max,min$說得玄乎,但你可以自行決定$max,min$的話那其實就相當於保留兩個中你想要的一個

那肯定只要出現過就可以一直保留到最後了。沒出現過那一定就不行

檢查一下目標串的第一位在初始串的前$n-m$位中是否出現過就好了

就這麼簡單個東西用了$6$分鐘,我好菜

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,t;
 4 char s[1000],d[1000];
 5 int main(){
 6     cin>>t;
 7     while(t--){
 8         cin>>n>>m>>s>>d;
 9         for(int i=1;i<m;++i) if(s[n-i]!=d[m-i]) goto fail;
10         for(int i=0;i<=n-m;++i) if(s[i]==d[0]) goto succ;
11         puts("NO"); continue;
12         succ: puts("YES"); continue;
13         fail: puts("NO");
14     }
15 }
View Code

 

B.

你要用最少的次數,那肯定就要每一次儘量多的喫。

想喫一個連續區間的食物,你需要這個區間裏的所有食物的$max-min$不超過$2v$,這樣你就可以把你的$s$調整成$max$和$min$的平均值了

每次最多能喫多少?單調性很明顯。二分即可。每次檢查一個區間能否一次喫完,寫一個$ST$表來搞區間查極值即可

賽後發現可以寫線段樹上二分。複雜度一樣,也一樣的無聊

但是人菜手生,就倆$ST$的玩意寫了$12$分鐘,我好菜

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,v,t,mx[20][633333],mn[20][633333], hb[633333];
 4 int Max(int l, int r){return max(mx[hb[r-l+1]][l],mx[hb[r-l+1]][r-(1<<hb[r-l+1])+1]);}
 5 int Min(int l, int r){return min(mn[hb[r-l+1]][l],mn[hb[r-l+1]][r-(1<<hb[r-l+1])+1]);}
 6 int main(){
 7     cin>>t;
 8     for(int i=0; i<19; ++i) for(int j=1<<i; j<1<<i+1; ++j) hb[j]=i;
 9     while(t--){
10         cin>>n>>v;
11         for(int i=1;i<=n;++i) scanf("%d",&mx[0][i]),mn[0][i]=mx[0][i];
12         for(int i=1;i<19;++i) for(int j=1;j<=n;++j) mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<i-1)]), mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
13         int p=1, c=0;
14         while(p<=n){
15             //cerr<<p<<' '<<c<<endl;
16             int l=p, r=n, m, a=p;
17             while(m=l+r>>1,l<=r) if(Max(p,m)-Min(p,m)<=v+v) a=m, l=m+1; else r=m-1;
18             c++; p=a+1; 
19         }
20         cout<<c-1<<endl;
21     }
22 }
View Code

 

C.

其實也並沒有有趣到哪裏去

你每次選擇一個房子保護起來,你所選擇的一定是已經感染和尚未感染的分界線上的房子

不然你留出若干空位第二天也一定會被感染了

這題從已感染的房子的角度出發並不方便,所以應該從 尚未被感染的連續區間考慮(區間個數也是$m$個)

那麼顯而易見的,如果你什麼也不做,那麼每過去一天,所有區間的兩端都會被感染,區間長度$-2$(直到爲$0$)

然後如果你去對一個區間保護了一次(也就是把病毒從一頭堵上了)那麼久只會從另一頭傳染,區間長度的縮短速率由$2$變成$1$

再操作一次那麼中間這一段就被堵上了,速率降爲$0$。剩餘的整段區間都被保護下來了。

決策也就比較明顯了,把所有區間按照從長到短的順序依次排序,然後畫$2$天時間去保護第一段,後$2$天保護第二段…能保護下$len_i-4i-1$戶(從$0$開始給段編號)

最後邊界情況是區間長度小於等於$2$的時候,你再去保護也就能護下來恰好$1$個了。和上面的情況用一樣的公式會出問題

(這裏還$WA$了一發,不過是樣例所以沒扣分)

思路上也不算難,但我腦子鏽了,用了$14$分鐘,我好菜

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,t,a[233333],b[233333];
 4 int main(){
 5     cin>>t;
 6     while(t--){
 7         cin>>n>>m;
 8         for(int i=1;i<=m;++i) scanf("%d",&a[i]);
 9         sort(a+1,a+1+m);
10         for(int i=1;i<m;++i) b[i]=a[i+1]-a[i]-1;
11         b[m]=n+a[1]-a[m]-1;
12         sort(b+1,b+1+m);
13         reverse(b+1,b+1+m);
14         int ans=0;
15         for(int i=1;i<=m;++i) ans+=b[i]-i-i-i-i+4==1?1:max(0,b[i]-i-i-i-i+3);
16         cout<<n-ans<<endl;
17     }
18 }
View Code

 

D.

那肯定是要找兩種操作的區別了。

我們希望找到一種特徵值,使得所有正常人的序列的特徵值是一樣的,笨比的特徵值與衆不同。

同時最好能通過笨比的特徵值和普通人特徵值的差距能反映出笨比的操作次數。

因爲普通人操作次數彼此之間不一定相同,但是我們希望特徵值相同,所以操作一次最好能不改變特徵值

來看具體的操作,實際上就是把某一個數值$1$所在的下標$-1$,把某個數值$1$所在的下標$+1$,保證不越界

這樣說的話就很自然了,我們定義特徵值就是所有數值的下標和,那麼操作的時候顯然是不變的,也就是$\sum\limits_{i=1}^{n} i\times a_i$

然後再看笨比,實際上就是把某一個數值$1$所在的下標$-1$,把某個數值$1$所在的下標$+2$,同樣不會越界

那麼也就是每次操作特徵值會$+1$.這樣就完事了,找到特徵值最大的那個,用他減掉普通人的特徵值就是笨比的操作次數了

還挺有意思的,有點套路,但我還是用了$10$分鐘,我好菜

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,t; long long a[233333],x;
 4 int main(){
 5     cin>>t;
 6     while(t--){
 7         cin>>n>>m;
 8         for(int i=1;i<=n;++i) a[i]=0;
 9         for(int i=1;i<=n;++i) for(int j=0;j<m;++j) scanf("%lld",&x), a[i]+=j*x;
10         if(a[1]!=a[2]){if(a[3]==a[1]) cout<<"2 "<<a[2]-a[1]<<endl; else cout<<"1 "<<a[1]-a[2]<<endl;}
11         else for(int i=3;i<=n;++i) if(a[i]!=a[1]) cout<<i<<' '<<a[i]-a[1]<<endl;
12     }
13 }
View Code

 

E.

直接模擬肯定是不行的,權值一大直接起飛了

然後你可能就會考慮直接算出到達唯一沒有出邊的點的總流量是多少,再和最早到達這個點的流量到達所需要的時間加起來這類的

這種思路默認了一旦開始有流量,以後就都有了。但事實並不是這樣

考慮一種情況

 

 

 假如最開始只有$1$號點有$1$的權值。

它會在$1$秒後和$5$秒後分別給$2$號點產生$1$的流量。中間的幾秒流量斷掉了

然後就卡住了,我好菜

但沒完全卡住。把上面的思路結合起來。由於$DAG$中的最長路長度也只有$n-1$

所以我們只要模擬前$n-1$輪,此後所有流量到唯一無出度點的路徑就都已經流通了

此後流量一斷就說明全圖已經沒有流量了。在流了$n-1$輪的圖上直接算出此後到目標點的總量,加上$n-1$即爲答案

由於我是笨比,我以爲拓撲之後入度就已經被清空了我就沒再手動清空,但實際上如果模擬的輪數裏就直接結束了就會直接跳過拓撲,怒$WA$一發交罰時

暴力簡單,錯解也簡單。暴力+錯解我就做了長達$18$分鐘,我好菜

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,t,a[1234],q[1234],ind[1234];
 4 vector<int>v[1222];
 5 #define mod 998244353
 6 int main(){
 7     cin>>t;
 8     while(t--){
 9         cin>>n>>m;
10         for(int i=1;i<=n;++i) scanf("%d",a+i);
11         for(int i=1,x,y;i<=m;++i) cin>>x>>y, v[x].push_back(y), ind[y]++;
12         int ans=0;
13         while(ans<n){
14             int t=0;
15             for(int i=1;i<=n;++i) if(a[i]) q[++t]=i;
16             if(t==0) break;
17             for(int i=1;i<=t;++i){
18                 a[q[i]]--;
19                 for(auto x:v[q[i]]) a[x]++;
20             }
21             ans++;
22         }
23         int t=0;
24         if(ans<n) {printf("%d\n",ans); goto xxx;}
25         for(int i=1;i<=n;++i) if(!ind[i]) q[++t]=i;
26         for(int h=1;h<=t;++h){
27             //cerr<<"!!!"<<h<<endl;
28             int x=q[h];
29             if(h==n) printf("%d\n", (a[x]+n)%mod);
30             for(auto y:v[x]){
31                 (a[y]+=a[x])%=mod;
32                 ind[y]--;
33                 if(!ind[y]) q[++t]=y;
34             }
35         }
36         xxx:
37         for(int i=1;i<=n;++i) v[i].clear(), ind[i]=0;
38     }
39 }
View Code

 

 

F.

只會知識的人做着費勁,只會亂搞的人做着費勁,就我這種一瓶子不滿半瓶子晃的運氣好搞出來了(?)

我遊戲打多了。不妨認爲序列上$A$的數量就是玩家$A$的血量。$B$同理

按照題意,每次行動自己必定扣$1$點血,如果選的是$AB/BA$這樣的,那麼就會給對方也扣$1$點血

講道理選$AA$會給$A$自己扣$2$點血,但是顯而易見的,他不會這麼選

由於可以選空了的位置或者是$B$,那麼$A$一定更樂意自己只扣$1$點血

(除非全屏都是$A$而沒有$B$,這種情況扣$2$血就扣$2$血吧,反正贏定了)

輪到誰的時候如果他沒血了他就輸了。

那麼現在決策只有兩種:同時扣雙方$1$點血,或者只有自己扣$1$點血

那麼顯然前者更優。在所有的$AB/BA$都被選完後,雙方就只能每回合扣自己$1$點血,直到一方沒血

那麼很明顯了,最開始的階段每回合雙方都扣$2$血,後來雙方每回合都扣$1$血

那麼誰血多誰贏唄

好了,這題你已經完成了$10\%$了(

血相同怎麼辦?

會發現,不同位置的$AB/BA$選了之後的效果是不一樣的

如對於串$ABAB$,如果$A$喫掉中間的變成$A__B$那麼他就贏了,如果喫掉靠邊的變成$__AB$那麼他就輸了

只有$ABAB...$這樣的交替串會有這樣不同的決策

我們認爲有$n$個字母的交替串的長度爲$n-1$(因爲實際上可操作的方案數是$n-1$,所以這樣更方便)

那麼長度爲$x$的交替串的所有決策就是,使長度變爲$x-2$(喫邊緣),或者拆分成長度爲$y$和$x-3-y$的兩個串(喫中間)

$dp$味太重了。一看就是$SG$函數題。把所有的串異或起來就是最終的$SG$值

如果整個串就是一個交替串,那麼直接取這個交替串的$SG$值就好了

如果不止是一個交替串,那麼就把它劃分成多個交替串。如$AABBAB$拆分成$A,AB,BAB$也就是長度分別爲$0,1,2$的交替串

把它們的$SG$值異或起來,非$0$就必勝。完結撒花

然而還沒等你撒出去你就發現這算$SG$的複雜度是$O(n^2)$的

那就打個表看看有沒有規律吧:

$0,1,1,2,0,3,1,1,0$(這是我手動打表的範圍)

嗯?看不出來吧?

$ 0,1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 0, 5, 2, 2, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 2, 7, 4, 0, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 2, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4, 8, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4, 8, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4...$

(這是機器打表你有耐心看的範圍)

如果你是正常人我估計你還是什麼也看不出來。

事實上把表翻到後頭會好看一些:(下面這是從第$68$項開始)

$8, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4,8, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4,8, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4...$

現在能看出來了嗎?

然而你們是在打$OI$,但我是在打網絡線上賽。

 

然後結果屬實是震驚到我了

 

週期長達$34$的,前兩個週期亂掉的一個週期序列

這能看出個毛線啊

反正最後我就$OEIS$擡走了

但這個題我居然用了$52$分鐘,我太菜了

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,t,a[1234],q[1234],ind[1234];
 4 char s[533333];
 5 int f[]={8, 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4};
 6 int get(int x){
 7     switch(x){
 8         case  0:return 0;
 9         case 14:return 0;
10         case 16:return 2;
11         case 17:return 2;
12         case 31:return 2;
13         case 34:return 0;
14         case 51:return 2;
15         default:return f[x%34];
16     }
17 }
18 int main(){
19     cin>>t;
20     while(t--){
21         cin>>n>>s;
22         int ans=0;
23         for(int i=0;i<n;++i) ans+=s[i]=='R'?1:-1;
24         s[n]=s[n-1];
25         if(ans>0)puts("Alice");
26         else if(ans<0)puts("Bob");
27         else{
28             for(int i=1,c=0;i<=n;++i) if(s[i]!=s[i-1]) c++; else ans^=get(c),c=0;
29             puts(ans?"Alice":"Bob");
30         }
31     }
32 }
View Code

 


 

 

 

好耶,變成深橙名了

看來紅名真的是有手就行,即便是菜如我

算了還是別亂說了畢竟我還沒紅(((

DC老矣,尚能飯否 不能飯了 太下飯了

打什麼算法競賽,不如回家睡大覺

啊我就在家啊,那我睡覺了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章