比赛连接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"); }