比賽連接c++
官方題解算法
before:T1觀察+結論題,T2樹形Dp,能夠換根或up&down,T3正解妙,轉化爲圖上問題。題目質量不錯,但數據太水了~。數組
一共n個石子堆,每一個石子堆有ai個石子,兩人輪流對石子塗色(先手塗紅,後手塗藍),且須要保證當前回合塗的石子顏色不能和它相鄰的兩個同色,誰塗不下去誰輸。一共T個詢問,對於每一個詢問輸出先手必勝仍是後手必勝。測試
\(1<=n<=10^3,1<=ai<=10^9,1<=T<=10^2\)優化
標籤:模擬,猜結論spa
當n=1時,模擬一下能夠發現,除非石子數=1,不然後手必勝。當增長多堆石子時,假設當前局面是P必敗,則P確定要去新的一堆裏塗,則對於新的一堆石子P成爲了先手,假設新的一堆石子數>1,則P任然必敗,反之能夠轉移,P的另外一方成爲了下一堆的先手。也就是說只有數量爲1的石子堆可以改變勝負狀態。調試
因此,只需統計石子數爲1的堆的數量\(cnt1\),根據\(cnt1\)的奇偶性來判斷。若爲奇,則先手必勝;反之先手必敗。code
#include<bits/stdc++.h> using namespace std; inline int read(){} int main(){ int T=read(); while(T--){ int n=read(); bool o=0; for(register int i=1;i<=n;i++){ int x=read(); if(x==1)o^=1; } if(o==0)puts("hamster"); else puts("rabbit"); } }
見原題面。blog
標籤:樹形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){ dp[x][0]=1; for(int i=0;i<=k;++i)dp2[x][i]=1; for(int i=0;i<G[x].size();++i){ if(G[x][i]!=fa){ dp_dfs(G[x][i],x); for(int j=1;j<=k;++j){ dp[x][j]+=dp[G[x][i]][j-1]; dp2[x][j]=dp2[x][j]*dp2[G[x][i]][j-1]%mod; } } } long long sum=0; for(int i=0;i<=k;++i){ sum+=dp[x][i]; dp2[x][i]=dp2[x][i]*sum%mod; } }
這一遍dfs後,此時的根節點1已經能夠直接統計出答案了,但對於其餘非根節點來講,它們只考慮了本身子樹內的貢獻,還需考慮子樹外的貢獻。
接下來再dfs一遍,換根。
void change_root(int root1,int root2) { //原來的根:root1 如今要換爲:root2,且root1以前是root2的父節點 cut_dp(root1,root2);//逆操做 link_dp(root2,root1);//正操做,從新統計一遍 }
逆操做、正操做都與以前的預處理dfs相似。
無根樹Dp還能夠up and down去作。網上關於這一塊內容很少,並且不少blog都說和換根法差很少。
好比對於這題的詢問一,要求樹中離x距離小於等於k的節點數目,能夠分解爲這樣兩個問題,①x子樹中距離小於等於k的節點數目+②x子樹外距離小於等於k的節點數目,對這兩個問題分別dfs一遍,問題①用兒子更新本身,問題②用本身更新兒子,最後將兩部分的貢獻合起來。好像確實和換根差很少,但實現起來感受up&down的思路更清晰些。
下面是up&down的代碼
#include<bits/stdc++.h> #define Mod 1000000007 #define int long long using namespace std; const int N=1e5+10; vector<int>g[N]; int num[N][11],dp[N][11]; int outnum[N][11],outdp[N][11]; int n,k,ans1[N],ans2[N]; namespace UpandDown{ int ksm(int x,int d){ int res=1; while(d){ if(d&1)res=res*x%Mod; x=x*x%Mod;d>>=1; } return res; } void dfs(int x,int fa){ for(int i=0;i<g[x].size();i++)if(g[x][i]!=fa)dfs(g[x][i],x); num[x][0]=1,dp[x][0]=1; for(int i=1;i<=k;++i){ num[x][i]=1,dp[x][i]=1; for(int j=0;j<g[x].size();++j){ int y=g[x][j];if(y==fa)continue; num[x][i]+=num[y][i-1]; dp[x][i]=dp[x][i]*dp[y][i-1]%Mod; } dp[x][i]=dp[x][i]*num[x][i]%Mod; } } void redfs(int x,int fa){ if(x==1){ ans1[x]=num[x][k]; ans2[x]=dp[x][k]; } else{ ans1[x]=num[x][k]+outnum[x][k]; ans2[x]=dp[x][k]*ksm(num[x][k],Mod-2)%Mod*(num[x][k]+outnum[x][k])%Mod*outdp[x][k]%Mod; } for(int i=0;i<g[x].size();i++){ int y=g[x][i];if(y==fa)continue; outnum[y][1]=1,outdp[y][1]=1; for(int j=2;j<=k;j++){ outnum[y][j]=outnum[x][j-1]+num[x][j-1]-num[y][j-2]; outdp[y][j]=outdp[x][j-1]*dp[x][j-1]%Mod*ksm(dp[y][j-2],Mod-2)%Mod* ksm(num[x][j-1],Mod-2)%Mod*(num[x][j-1]-num[y][j-2]+outnum[x][j-1])%Mod; } redfs(y,x); } } void solve(){ for(int i=1;i<=k;i++)for(int j=1;j<=k;j++)outdp[i][j]=1; dfs(1,0),redfs(1,0); for(int i=1;i<=n;i++)printf("%lld ",ans1[i]); printf("\n"); for(int i=1;i<=n;i++)printf("%lld ",ans2[i]); } } signed main(){ scanf("%lld%lld",&n,&k); for(int i=1;i<n;i++){ int u,v;scanf("%lld%lld",&u,&v); g[u].push_back(v); g[v].push_back(u); } UpandDown::solve(); return 0; }
小w有\(k\)張魔術撲克,第i張魔術撲克有兩面數值\(ai,bi\)(可能相等),牌面的數值在\([1,n]\)。如今有q個詢問,每一個詢問有兩個整數\(l,r\),詢問是否能用若干張魔術撲克湊成\(l,l+1,..,r-1,r\)這個順子,每張魔術撲克只能使用一面的值。
對於30%測試點,\(1<=n<=11\),\(1<=k<=10\),\(1<=q<=100\)。
對於60%測試點,\(1<=n,k<=50\),\(1<=q<=500\)。
對於100%測試點,\(1<=n,k,q<=10^5\),\(1<=l<=r<=n\)。
標籤:思惟題,圖論,並查集,離線詢問,樹狀數組
二進制枚舉。\(O(2^n*n)\)。
這種一一匹配的關係容易聯想到二分圖。令左邊爲\([1,n]\)的牌面,右邊爲魔術撲克的編號。創建兩兩聯通關係,對於每一個牌面l(\(l∈[1,n]\)),維護一個\(Ma[l]\),表示若能打出l,則能打出的連續最右位置爲Ma[l],那麼對於每一個詢問\((l,r)\),只用判斷\(Ma[l]>=r?可行:不可行\)。
如何維護?上面已經提到二分圖了,若是直接枚舉l,Ma[l],而後再用匈牙利算法判一下,複雜度比較大。因爲這個Ma顯然具備單調性,考慮一邊尺取,一邊跑匈牙利算法。
代碼以下,時間複雜度爲\(O(N^3)\),可是因爲垃圾數據賽時是能夠A了這題的。
#include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n,m,bf[N],gf[N]; int mark[N],Ma[N]; vector<int>g[N]; bool grab(int x){ if(mark[x])return 0; mark[x]=1; for(int i=0;i<g[x].size();i++){ int y=g[x][i]; if(!bf[y]||grab(bf[y])){ bf[y]=x; gf[x]=y; return 1; } } return 0; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y;scanf("%d%d",&x,&y); g[x].push_back(i); g[y].push_back(i); } for(register int l=1,r=0;l<=n;l++){ bf[gf[l-1]]=0; r=max(r,l-1); memset(mark,0,sizeof(mark)); while(r<n&&grab(r+1)){ r++; memset(mark,0,sizeof(mark)); } Ma[l]=r; } int q,l,r;scanf("%d",&q); for(register int i=1;i<=q;i++){ scanf("%d%d",&l,&r); if(Ma[l]>=r)puts("Yes"); else puts("No"); } }
數據範圍加到\(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)\)。
代碼以下:
#include<bits/stdc++.h> using 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){return fa[x]==x?x:fa[x]=find(fa[x]);} struct seg{ int l,r,id; }que[N]; vector<seg>g; inline bool cmp(seg p,seg q){return p.r<q.r;} int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)fa[i]=mi[i]=ma[i]=i; for(int i=1;i<=m;i++){ int x,y;scanf("%d%d",&x,&y); int A=find(x),B=find(y); if(A==B)huan[A]=1; else{ fa[A]=B; huan[B]|=huan[A]; ma[B]=max(ma[B],ma[A]); mi[B]=min(mi[B],mi[A]); } } for(int i=1;i<=n;i++){ if(find(i)==i&&!huan[i])g.push_back((seg){mi[i],ma[i],0}); } int q; scanf("%d",&q); for(int i=1,a,b;i<=q;i++){ scanf("%d%d",&a,&b); que[i]=(seg){a,b,i}; } sort(g.begin(),g.end(),cmp); sort(que+1,que+q+1,cmp); for(int i=1,cur=0,maxl=0;i<=q;i++){ while(cur<g.size()&&g[cur].r<=que[i].r){ maxl=max(maxl,g[cur].l);cur++; } if(maxl>=que[i].l)cover[que[i].id]=1; } for(int i=1;i<=q;i++)puts(cover[i]?"No":"Yes"); }