格雷碼是一種特殊的 \(n\) 位二進制串排列法,要求相鄰的兩個二進制串剛好有一位不一樣,環狀相鄰。node
生成方法:數組
求 \(n\) 位格雷碼的第 \(k\) 個串。函數
\(1\leq n\leq 64,0\leq k\leq 2^n\) .優化
考慮一個跟康託展開很是類似的思路。spa
首先看第一位,若是是 1 那麼說明它前面已經能夠肯定至少排了 \(2^n\) 個 0 開頭的二進制串。code
那麼這樣就能夠肯定第一位是 0 仍是 1 ,看 \(k\) 的大小就行了。遞歸
後面的也是同理,每次判斷完以後:隊列
複雜度是 \(\mathcal{O}(n)\) 的。(不過這種題也不須要考慮這個吧)字符串
最後:通過 CSP-S2020 ,我發現 \(k<2^n\leq 2^{64}\) (get
寫代碼的時候注意溢出問題,要開 unsigned long long
特別是左移的地方要注意。
//Author: RingweEH #define ull unsigned long long const int N=70; int n,a[N]; ull k; int main() { n=read(); scanf( "%llu",&k ); ull now=1ull<<(n-1); for ( int i=n-1; i>=0; i-- ) { if ( (k>>i)&1 ) a[i]=1,k=(now<<1)-k-1; else a[i]=0; } for ( int i=n-1; i>=0; i-- ) printf( "%d",a[i] ); return 0; }
給定一棵以 \(1\) 爲根的括號樹,每一個點恰有一個 (
或 )
,定義 \(s(i)\) 爲將根節點到 \(i\) 號點的簡單路徑按通過順序排列造成的字符串。
設 \(k(i)\) 表示 \(s(i)\) 中互不相同的子串是合法括號串的個數。求 \(\forall1\leq i\leq n,\sum i\times k(i)\) ,這裏的求和表示異或和。
\(n\leq 5e5\)
終於補完模擬賽來繼續寫題了
題外話:今天模擬賽也有一道括號匹配題,可是是奇妙的貪心,要寫兩個棧+一個雙端隊列(
STL永遠的神!
顯然若是對這棵樹進行 DFS ,那麼根到 \(i\) 的路徑上的點能夠用棧獲得。
那麼一遍 DFS 就能夠處理出根到 \(i\) 的路徑上 互不相同的子串是合法括號串 的個數。
設 \(endpos[i]\) 爲以節點 \(i\) 結尾的,根到 \(i\) 中互不相同的合法括號子串的個數。相似括號匹配的思路,若是當前爲左括號那麼直接進棧;若是是右括號且棧不爲空,那麼棧頂就能和當前點配對,這樣就造成了一個新的合法子串 sta.top(),i
,那麼當前節點的 \(endpos\) 就能夠由這一對括號以前的東西推知。
令 \(fa[i]\) 表示括號樹上點 \(i\) 的父親節點,那麼 \(endpos[i]=endpos[fa[sta.top()]]+1\) (由於 \(endpos[fa[sta.top()]]\) 這些串都能和當前這一對括號接起來,成爲一個新的合法子串;或者當前這個單獨成串)
最後 \(k(i)\) 就是根到 \(i\) 的路徑上全部的 \(endpos\) 之和,這個也能夠在 DFS 的時候順帶求出來。
注意遞歸完以後要記得還原,pop 掉的左括號搞回去,push 的左括號拿出來。
//Author: RingweEH const int N=5e5+10; int n,fa[N],endpos[N]; ll f[N]; stack<int> sta; vector<int> son[N]; char s[N]; void dfs( int u ) { bool pu=0; int las=0; if ( s[u]=='(' ) sta.push( u ),pu=1; else if ( !sta.empty() ) { endpos[u]=endpos[fa[sta.top()]]+1; las=sta.top(); sta.pop(); } f[u]=f[fa[u]]+endpos[u]; for ( int i=0; i<son[u].size(); i++ ) dfs( son[u][i] ); if ( pu && sta.top()==u ) sta.pop(); if ( las ) sta.push( las ); } int main() { n=read(); scanf( "%s",s+1 ); for ( int i=2; i<=n; i++ ) fa[i]=read(),son[fa[i]].push_back(i); endpos[1]=0; f[1]=0; dfs( 1 ); ll ans=0; for ( int i=1; i<=n; i++ ) ans^=(i*f[i]); printf( "%lld\n",ans ); return 0; }
給定一棵大小爲 \(n\) 的數,初始時每一個節點上都有一個 \(1\sim n\) 的數字,且每一個 \(1\sim n\) 的數字都只 剛好 在一個節點上出現。
進行 剛好 \(n-1\) 次刪邊操做,每次操做須要選一條 未被刪去的邊 ,交換兩個端點的數字,並刪邊。
刪完後,按數字 \(1\sim n\) 的順序將所在節點編號依次排列獲得排列 \(P_i\) ,求能獲得的字典序最小的 \(P_i\) .
\(n\leq 2000\)
這種 「字典序最小」的題一看就很像貪心。
題外話:今天模擬賽也有 「字典序最小」的貪心題,但是我一連胡錯了兩次,最後思路對了還調了半天(
要想貪心,確定是讓某個小編號儘量地在最後的排列中獲得小的權值。那麼考慮如何讓一個數字最終達到某個特定的位置。
假設如今有一條路徑 :\(start\to a\to b\to c\to d\to end\) ,並將這些邊從左到右依次標號爲 \(1,2,3,4,5\) 。
那麼,假設咱們如今想要把 \(start\) 節點上的數字轉移到 \(end\) 節點上。能夠發現:
因爲要前面的數儘量小,那麼枚舉填數的時候必定是從小到大的。每次利用以上的性質判斷是否可以填到這個位置,直到找到一個能填且最小的位置,並加上這個點給後面的限制。
看到圖論裏面的限制其實一個很天然的想法就是:連邊爲限制 ,可是這裏的限制是針對邊的,那麼就能夠考慮把邊轉化成點。
對原樹上的每個點建一張圖,圖上每一個點表明連的一條邊,並記錄這個點欽定的第一條邊和最後一條邊。這張圖上的一條有向邊表示出點在入點以後立刻選擇。點與點之間建的圖是獨立的。
考慮什麼狀況下會出現矛盾。
圖不能被分割成獨立的若干條鏈(這樣就會有一條邊後面要連多條邊,或是出現環,顯然不合法)
欽定的第一個點和最後一個點有入/出邊(顯然不合法)
第一個點和最後一個點在同一條鏈上,可是還有點不在這條鏈中。(已經造成了完備惟一的刪邊方案,可是有邊還沒刪)
這些條件的矛盾分別表現爲:
而後各類判斷就行了。題目很 毒瘤 細節(也有多是我寫煩了),實現時要注意。
代碼中有詳細的註釋,若是發現本身掛了能夠看看註釋,找找有沒有漏掉的條件。
我發現我最近善於把題目寫得碼農(
//Author: RingweEH const int N=2010; int n,pos[N],head[N],tot; //pos:數字 i 初始節點位置 struct edge { int to,nxt; }e[N<<1]; struct node_graph //每一個點所創建的圖 { int fir,las,num,fa[N]; //欽定的第一條邊,最後一條,邊(點)數,並查集 bool ine[N],oue[N]; //是否有入/出邊 void clear() { fir=las=num=0; for ( int i=1; i<=n; i++ ) fa[i]=i,ine[i]=oue[i]=0; } int find( int x ) { return x==fa[x] ? x : fa[x]=find(fa[x]); } }g[N]; void add( int u,int v ) { e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; g[u].num++; e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; g[v].num++; } int dfs1( int u,int fro_edge ) { int res=n+1; if ( fro_edge && (!g[u].las || g[u].las==fro_edge ) ) //尚未終點或者終點就是這條邊 { if ( !g[u].oue[fro_edge] && !(g[u].fir && g[u].num>1 && g[u].find(fro_edge)==g[u].find(g[u].fir)) ) //這條邊(點)在這個點的圖裏面尚未出邊,並且不能: //有起點,總點數大於1,且已經在一條鏈裏面了 res=u; } for ( int i=head[u]; i; i=e[i].nxt ) { int v=e[i].to,to_edge=i/2; if ( fro_edge==to_edge ) continue; //防止沿着雙向邊搜回去,tot是從1開始的,因此同一條雙向邊/2向下取整是同樣的 if ( !fro_edge ) //前面沒有鏈的狀況 { if ( !g[u].fir || g[u].fir==to_edge ) //沒有欽定起點,或者起點就是當前邊 { if ( g[u].ine[to_edge] ) continue; //若是有入邊了就不能當起點 if ( g[u].las && g[u].num>1 && g[u].find(to_edge)==g[u].find(g[u].las) ) continue; //起點和終點已經在一條鏈裏面了 res=min( res,dfs1( v,to_edge ) ); } else continue; } else //前面有鏈,日後接的狀況 { if ( fro_edge==g[u].las || to_edge==g[u].fir || g[u].find(fro_edge)==g[u].find(to_edge) ) continue; //若是上一條鏈的尾點是終點,那麼後面不能接鏈;若是這條邊是起點,那麼不能被接; //若是已經在一條鏈上了,也不能被接 if ( g[u].oue[fro_edge] || g[u].ine[to_edge] ) continue; //已經接過了 if ( g[u].fir && g[u].las && g[u].num>2 && g[u].find(fro_edge)==g[u].find(g[u].fir) && g[u].find(to_edge)==g[u].find(g[u].las) ) continue; //從起點來的鏈,接上去終點的鏈,且還有點不在鏈上 res=min( res,dfs1( v,to_edge ) ); } } return res; } int dfs2( int u,int fro_edge,int endpos ) { if ( u==endpos ) { g[u].las=fro_edge; return 1; } //到終點了,使命完成 for ( int i=head[u]; i; i=e[i].nxt ) { int v=e[i].to,to_edge=i/2; if ( fro_edge!=to_edge ) { if ( dfs2( v,to_edge,endpos ) ) //後面可行 { if ( !fro_edge ) g[u].fir=to_edge; //前面沒有了,這個就是起點 else { //更新有無出入邊的限制,並查集合並 g[u].oue[fro_edge]=g[u].ine[to_edge]=1; g[u].num--; g[u].fa[g[u].find(fro_edge)]=g[u].find(to_edge); } return 1; } } } return 0; } int main() { int T=read(); while ( T-- ) { tot=1; memset( head,0,sizeof(head) ); n=read(); for ( int i=1; i<=n; i++ ) g[i].clear(),pos[i]=read(); for ( int i=1,u,v; i<n; i++ ) u=read(),v=read(),add( u,v ); if ( n==1 ) { printf( "1\n" ); continue; } int p; for ( int i=1; i<=n; i++ ) { p=dfs1( pos[i],0 ); dfs2( pos[i],0,p ); //1用來搜方案,2用來加限制 printf( "%d ",p ); } printf( "\n" ); } return 0; }
有 \(1\sim n\) 種烹飪方法和 \(1\sim m\) 種食材,使用 \(i\) 方法,食材爲 \(j\) 的一共有 \(a_{i,j}\) 道菜。
對於一種包含 \(k\) 道菜的方案而言:
求有多少種不一樣的搭配方案,對 \(998244353\) 取模。\(1\leq n\leq 100,1\leq m\leq 2000\)
對於方陣 \(a\) ,題目要求就至關因而:
考慮容斥,那麼就是:每行至多取一個的方案 - 取了 0/1 個的方案 - 存在一列取了超過半數的方案(顯然這樣的列至多有一個)
對於每行至多取一個的總方案,來一遍 DP ,令 \(g[i][j]\) 表示到第 \(i\) 行,取了 \(j\) 個的方案數,\(sum[i]=\sum a_{i,j}\) 那麼有:
發現 取了 1 個的方案
其實能夠直接在 存在一列取了超過半數的方案
裏面統計掉,由於必定是超過半數的。
沒有取的方案直接不加上就行了。
而後就能夠暴力枚舉超過半數的材料是哪一個,進行DP。
設 \(f[i][j][k]\) 表示前 \(i\) 行,取了 \(j\) 個,其中超過半數的 \(x\) 取了 \(k\) 個( \(\Big\lfloor \dfrac{j}{2}\Big\rfloor <k\)),枚舉到 \(pos\) 這道菜取了超過半數。
轉移挺好想的,就是三種狀況:
轉移方程:
對於每一個 \(pos\) ,對答案的貢獻就是 \(\sum_{i=0}^n\sum_{j=\lfloor i/2\rfloor+1}^i f[k][i][j]\) .
這樣的複雜度是 \(\mathcal{O}(n^3m)\) 的,能獲得 84 分的好成績( 在考場上已經至關可觀了……
而後考慮優化。發現合法狀態只有 \(2\times k>j\) 的部分,也就是說你徹底不須要知道 \(j,k\) 的具體值,因此能夠把狀態搞成 \(2k-j\) ,省掉一維的枚舉時間和空間。
那麼方程就是:
(數組下標內的小括號表示根據原先的 \(j,k\) 定義,這個下標的值)
(注意,這裏的合法狀態指的是最終對答案有貢獻的部分,從轉移方程易知 \(2k\leq j\) 的部分仍是有用的,能夠經過若干次 \(j-1\) 部分的轉移貢獻到合法狀態裏面去)
複雜度是 \(\mathcal{O}(n^2m)\) .
實現的時候注意減法取模……由於這個掛成 88 了qaq
//Author: RingweEH const int N=110,M=2010; const ll Mod=998244353; int n,m; ll a[N][M],g[N],sum[N],f[N][N<<1]; void add( ll &t1,ll t2 ) { t1=(t1+t2); if ( t1>Mod ) t1-=Mod; } int main() { n=read(); m=read(); for ( int i=1; i<=n; i++ ) for ( int j=1; j<=m; j++ ) a[i][j]=read(),add( sum[i],a[i][j] ); memset( g,0,sizeof(g) ); g[0]=1; for ( int i=1; i<=n; i++ ) for ( int j=i; j>=1; j-- ) add( g[j],g[j-1]*sum[i]%Mod ); ll ans=0; for ( int i=1; i<=n; i++ ) add( ans,g[i] ); for ( int pos=1; pos<=m; pos++ ) { memset( f,0,sizeof(f) ); f[0][n]=1; for ( int i=1; i<=n; i++ ) for ( int j=1; j<=n+i; j++ ) { f[i][j]=f[i-1][j]; add( f[i][j],f[i-1][j+1]*(sum[i]+Mod-a[i][pos])%Mod ); add( f[i][j],f[i-1][j-1]*a[i][pos]%Mod ); } for ( int i=n+1; i<=n*2; i++ ) add( ans,Mod-f[n][i]); } printf( "%lld\n",ans ); return 0; }
給定一個長爲 \(n\) 的序列 \(a_i\) ,對於一組規模爲 \(u\) 的數據,代價爲 \(u^2\) .你須要找到一些分界點 \(1\leq k_1<k_2<...<n\) ,使得:
\(p\) 能夠爲 \(0\) 且此時 \(k_0=0\) .而後要求最小化::
求這個最小的值。
(數據生成方式見題面)
\(n\leq 4e7,1\leq a_i\leq 1e9,1\leq m\leq 1e5,1\leq l_i\leq r_i\leq 1e9,0\leq x,y,z,b_1,b_2\leq 2^{30}\)
難想好寫的典型案例(其實也不難……)
一個顯然的想法是DP分組。因爲這道題跟組數沒有關係,因此能夠修改一下常規的式子。
設 \(f[i][j]\) 爲對前 \(i\) 個進行分組,最後一組爲 \([j+1,i]\) 的最小代價,\(sum[i]\) 爲序列前綴和。
有方程:
複雜度爲 \(\mathcal{O}(n^3)\) .問題出在上一個斷點要一個一個枚舉 \(k\) 獲得。考慮如何加速這個過程。
注意到 「平方之和」必定比 「和的平方」要小。因此把最後一段拆成幾段(在知足遞增的狀況下)答案必定不會變劣。
也就是說最優解的方案必定是合法的裏面 最後一段最短 的一種。
那麼這時候的 \(k\) 就是肯定的,數組就省掉了一維變成 \(f[i]\) .記錄一個 \(las[i]\) 表示 \(f[i]\) 的方案中上一段的末尾。
方程就是:
複雜度 \(\mathcal{O}(n^2)\) 。這樣已經實現了36分到64分的巨大飛躍(
然而對於 \(n\leq 4e7\) ,加上常數的話複雜度得是線性的……繼續優化。😔
注意上面的 \(j\) 的條件式。
是否是清新可人的樣子 你會發現若是一個 \(j\) 對於 \(i\) 知足上式,因爲前綴和遞增,顯然對 \(i+1\) 也知足上式,所以可行決策點的範圍必定是左端點爲 \(1\) 的一個區間,且隨着 \(i\) 的增大,這個區間的右端點遞增(顯然)。
咱們用一個函數 \(g(j)=2\times sum[j]-sum[las[j]]\) 來表示右式的值。根據題意,顯然 \(j\) 的位置越靠右越優。
那麼,若是有 \(j<j'\) 且 \(g(j)>g(j')\) ,\(j'\) 必定比 \(j\) 優,\(j\) 就是沒用的了。
到這裏,優化方式已經呼之欲出——單調隊列!樸素想法就是在這個 \(j\) 單增 \(g(j)\) 單增的隊列裏面進行二分。可是這樣還有一個 \(\log\) .
再考慮左式 \(sum[i]\) 的單調遞增性質, 發現若是有一個點 \(j\) 對當前點 \(i\) 已經合法,能夠進行轉移了,那麼 \(j\) 以前的點雖然能用,可是顯然沒有 \(j\) 好用,就能夠丟掉了。因此每次從隊頭彈出直到留下最後一個合法點便可。
每一個點只會入隊一次出隊一次,均攤一下,轉移複雜度就是 \(\mathcal{O}(1)\) 的,總複雜度 \(\mathcal{O}(n)\) . 數據範圍誠不欺我
LOJ AC連接 給你們講個笑話,這道題我同一份代碼(去掉文件頭了)在 ACWing 上重複提交四次能獲得1次RE的好成績(
卡空間就有點過度,不過考慮到 OJ 確實開不起這麼大的空間也能夠理解,就是出題人太噁心。(包括這個 __int128
的離譜操做)
//Author: RingweEH const int N=4e7+10,M=1e5+10; int n,las[N],p[M],l[M],r[M],typ,que[N]; ll a[N]; ll g( int x ) { return a[x]*2-a[las[x]]; } int main() { //freopen( "partition.in","r",stdin ); freopen( "partition.out","w",stdout ); n=read(); typ=read(); if ( typ==0 ) { for ( int i=1; i<=n; i++ ) scanf( "%lld",&a[i] ); } else { ll x,y,z; scanf( "%lld%lld%lld",&x,&y,&z ); int now=0,b[2],m; scanf( "%d%d%d",&b[0],&b[1],&m ); for ( int i=1; i<=m; i++ ) scanf( "%d%d%d",&p[i],&l[i],&r[i] ); for ( int i=1; i<=n; i++ ) { while ( p[now]<i ) now++; if ( i<=2 ) a[i]=b[i-1]%(r[now]-l[now]+1)+l[now]; else { b[0]^=b[1]^=(b[0]=(y*b[0]+x*b[1]+z)%(1<<30))^=b[1]; a[i]=b[1]%(r[now]-l[now]+1)+l[now]; } } } for ( int i=1; i<=n; i++ ) a[i]+=a[i-1]; int l=0,r=0; for ( int i=1; i<=n; i++ ) { while ( l<r && g(que[l+1])<=a[i] ) l++; las[i]=que[l]; while ( l<r && g(que[r])>=g(i) ) r--; que[++r]=i; } I128 ans=0; while ( n ) ans+=(I128)(a[n]-a[las[n]])*(a[n]-a[las[n]]),n=las[n]; int cnt=0; do { que[++cnt]=ans%10; ans/=10; }while ( ans ); do { printf( "%d",que[cnt] ); cnt--; }while ( cnt ); //fclose( stdin ); fclose( stdout ); // return 0; }
給定一棵 \(n\) 點的樹,求單獨刪去每條邊以後,分裂出的兩個子樹的重心編號和之和。(重心定義和簡單性質自行閱讀題面)
\(n\leq 299995\) .
考場騙分小能手狂喜(
發現前面 40 分的部分分徹底能夠 \(\mathcal{O}(n^2)\) 暴力碾過去,枚舉刪邊,而後 \(\mathcal{O}(n)\) DFS求一遍重心便可。
對於後面 15 分,有性質 \(A\) 也就是鏈。對於鏈,重心顯然是找個中點就行了。
咳……這個要面向數據。
注意到題目裏面對於這個部分分,欽定了 \(n=262143\) ,算一算就會發現是個滿二叉樹……其實滿二叉樹的根節點就是重心……
那麼能夠獲得以下推論:
而後直接 \(\mathcal{O}(n)\) 枚舉 \(\mathcal{O}(1)\) 計算就行了。
考慮重心的出現位置。有結論:
對於一個節點 \(u\) ,若是 \(n-siz[u]\leq \lfloor n/2\rfloor\) ,且 \(u\) 自己並不是重心,那麼重心必定在 \(u\) 的重兒子裏面。
這個挺顯然的的吧。
而後就有一些顯然的推論:(此處的 \(u\) 依然知足 \(n-siz[u]\leq\lfloor n/2\rfloor\) )
所以,重心必定在 root 向下的重鏈上,並且重鏈上自上往下,節點的 \(siz\) 遞減。再結合數據範圍獲得合理猜想:複雜度 \(\mathcal{O}(n\log n)\) .
那麼就能夠考慮在重鏈上倍增。令 \(f[i][x]\) 表示以 rt 爲根,節點 \(x\) 沿着重鏈往下走 \(2^i\) 步達到的節點。這樣,求重心的時候就相似 LCA 同樣,逆序枚舉 \(i\) 往下跳就行了。
而後相似換根DP,二次掃描維護 \(f\) 數組和重兒子便可。
時間複雜度是 \(\mathcal{O}(n\log n)\) .
//Author: RingweEH const int N=3e5+10,K=25; struct edge { int to,nxt; }e[N<<1]; int head[N],tot=0,n,siz[N],f[N][K],son[N],fa[N]; ll ans; void ST_init( int x ) { for ( int i=1; i<K; i++ ) f[x][i]=f[f[x][i-1]][i-1]; } void calc( int x ) { int u=x; for ( int i=K-2; i>=0; i-- ) if ( f[u][i] && siz[f[u][i]]*2>=siz[x] ) u=f[u][i]; if ( siz[u]*2==siz[x] ) ans+=fa[u]; ans+=u; } void dfs( int u,int fat ) { fa[u]=fat; siz[u]=1; siz[0]=0; son[u]=0; for ( int i=head[u]; i; i=e[i].nxt ) { int v=e[i].to; if ( v==fat ) continue; dfs( v,u ); siz[u]+=siz[v]; if ( siz[v]>siz[son[u]] ) son[u]=v; //重兒子 } f[u][0]=son[u]; ST_init( u ); } void get_ans( int u,int fat ) { int mx1=0,mx2=0; siz[0]=0; for ( int i=head[u]; i; i=e[i].nxt ) { int v=e[i].to; if ( siz[v]>=siz[mx1] ) mx2=mx1,mx1=v; else if ( siz[v]>=siz[mx2] ) mx2=v; //最大和次大的兒子 } for ( int i=head[u]; i; i=e[i].nxt ) { int v=e[i].to; if ( v==fat ) continue; calc( v ); f[u][0]=(v==mx1) ? mx2 : mx1; ST_init( u ); siz[u]-=siz[v]; siz[v]+=siz[u]; calc( u ); fa[u]=v; get_ans( v,u ); siz[v]-=siz[u]; siz[u]+=siz[v]; //算完(u,v)以後撤銷影響 } f[u][0]=son[u]; ST_init( u ); fa[u]=fat; } void add( int u,int v ) { e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; } int main() { //freopen( "centroid.in","r",stdin ); freopen( "centroid.out","w",stdout ); int T=read(); while ( T-- ) { memset( siz,0,sizeof(siz) ); memset( son,0,sizeof(son) ); memset( f,0,sizeof(f) ); memset( fa,0,sizeof(fa) ); ans=0; memset( head,0,sizeof(head) ); tot=0; n=read(); for ( int i=1,u,v; i<n; i++ ) u=read(),v=read(),add( u,v ); dfs( 1,0 ); get_ans( 1,0 ); printf( "%lld\n",ans ); } //fclose( stdin ); fclose( stdout ); return 0; }