CF1495E Qingshan and Daniel
阿新 • • 發佈:2021-11-11
一、題目
有 \(n\) 個人排成環,第 \(i\) 個人有 \(a_i\) 張卡牌,屬於陣營 \(t_i\),首先第一個人會打出卡牌,然後和當前打出卡牌的人屬於不同陣營且距離它最近(向右的距離)的人會接著打出卡牌。
問每個人最後的卡牌數,輸入和輸出都需要按題目所述進行加密。
\(n\leq 5\cdot 10^6\)
二、解法
首先做一些基本的觀察:所屬陣營卡牌總數少的陣營都是 \(0\)
然後考慮打牌的形式是從序列上選取一個環,然後環上每個人依次打牌之後有人無牌退出遊戲。這相當於每次刪去環上的最小值,我們考慮維護這個環:
- 考慮刪除的位置是 \(id\),如果下一個位置同色則在環上替換成下一個位置。
- 如果下一個位置異色,那麼它在環上的前驅需要把它在環上的後繼的同色段合併起來。
感覺暴力維護的話需要 \(\tt set/priority\) 之類的資料結構,\(O(n\log n)\) 無法通過。
但是沒有必要按照時間順序來維護環,我們考慮一個很小的區域性問題,對於相鄰兩個屬於不同陣營的人,當它們出現在環上時,可以看成它們"決鬥"之後其中一個掛掉。
我們欽定起點屬於總數少的陣營(否則我們暴力先跳一步),然後從它開始線性掃,如果遇到相同陣營的就讓 \(cnt\leftarrow cnt+a_i\),如果遇到不同陣營的就讓它和 \(cnt\) "決鬥",顯然這樣做是把我上面說的巧妙地模擬了出來,時間複雜度 \(O(n)\)
三、總結
不只是此題,很多題目簡化實現\(/\)優化複雜度的關鍵都是減少我們維護的量,我們可以遵守一個 整體->區域性
的思維過程,先考慮整體的變化再反映到區域性上便於快速計算。
#include <cstdio> #include <iostream> #include <queue> using namespace std; const int N = 200005; const int M = 5000005; const int MOD = 1e9+7; #define int long long int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int n,m,cnt,ans,seed,base,sum[2],a[M],tmp[M],t[M]; int random() { int ret=seed; seed=(seed*base+233)%MOD; return ret; } void decode() { n=read();m=read(); for(int i=1,ls=0;i<=m;i++) { int p=read(),k=read(),b=read(),w=read(); seed=b;base=w; for(int j=ls+1;j<=p;j++) { t[j]=random()%2; a[j]=random()%k+1; } ls=p; } } signed main() { decode(); for(int i=1;i<=n;i++) sum[t[i]]+=a[i],tmp[i]=a[i]; int st=1,nw=1; if(sum[t[1]]>sum[!t[1]])//switch the start pos { sum[t[1]]--;a[1]--; while(st<=n && t[st]==t[1]) st++; } nw=st; if(st<=n) while(sum[t[st]]>0) { if(t[nw]==t[st]) cnt+=a[nw],a[nw]=0; else { int o=min(cnt,a[nw]); a[nw]-=o;cnt-=o;sum[t[st]]-=o; } nw=nw%n+1; } ans=1; for(int i=1;i<=n;i++) ans=ans*((((tmp[i]-a[i])^(i*i))+1)%MOD)%MOD; printf("%lld\n",ans); }