考试整理
今天真不错进行了一波模拟考试
T1 小 R 与排列
其实\(next\_ permutation\)还是很好用的,但是他不让(我就呵呵了
我们可以考虑一下一个性质:
要是想求下一个排列需要从最后一位往前找,找到后\(x\)位的数列他不是那\(x\)个数的最后一个排列(难♂以描述)
其实就是手动的模拟\(STL\)中\(next\_permutation\)的实现就是了
在当前序列中,从尾端向前寻找两个相邻元素,前一个记为\(*i\),后一个记为\(*t\),并且满足$*i $< \(*t\)(这样\(*t\)后面的数都是递减的)。然后再从尾端寻找另一个元素\(*j\),如果满足\(*i < *j\),即将第\(i\)个元素与第\(j\)个元素对调(这样\(*t\)后面的数仍然保持递减),并将第\(t\)个元素之后(包括\(t\))的所有元素颠倒排序,即求出下一个序列了。
\(next\_permutation\)的函数原型如下:
template<class BidirectionalIterator> bool next_permutation( BidirectionalIterator _First, BidirectionalIterator _Last ); template<class BidirectionalIterator, class BinaryPredicate> bool next_permutation( BidirectionalIterator _First, BidirectionalIterator _Last, BinaryPredicate _Comp );
所以如果到最后一个排列\(next\_permutation\)会返回\(false\),但是使用下面方法后,序列会变成字典序列的第一个,如\(cba\)变成\(abc\)。
template<class BidirectionalIterator> bool next_permutation( BidirectionalIterator first, BidirectionalIterator last ) { if(first == last) return false; //空序列 BidirectionalIterator i = first; ++i; if(i == last) return false; //一个元素,没有下一个序列了 i = last; --i; for(;;) { BidirectionalIterator t = i; --i; if(*i < *t) { BidirectionalIterator j = last; while(!(*i < *--j)); iter_swap(i, j); reverse(t, last); return true; } if(i == first) { reverse(first, last); //全逆向,即为最小字典序列,如cba变为abc return false; } } }
当然这还主要是应用了\(next\_permutation\)的原理
我们毕竟还是要自己去写对吧(直接模拟也是很简单的
#include<iostream> using namespace std; int num[100005]; int main() { freopen("permutation.in","r",stdin); freopen("permutation.out","w",stdout); int i,j,k,left,right,n; cin>>n; for(i=0;i<n;i++) scanf("%d",&num[i]); for(i=n-2;num[i]>num[i+1];i--); j=i+1; for(k=i+2;k<n;k++) if((num[i]<num[k])&&(num[j]>num[k])) j=k; swap(num[i],num[j]); for(left=i+1,right=n-1;left<right;left++,right--) swap(num[left],num[right]); for(int i=1;i<=n;i++) if(num[i]==0) num[i]=1; for(i=0;i<n;i++) printf("%d ",num[i]); return 0; }
主要还是注意题目中说的字典序中最后一个排列的下一个排列认为是字典序中第一个排列否则会丢掉\(10\ Pts\)
T2 小 R 与爬树
\(30Pts\)搜索
其实我不知道这玩意好不好打(反正我不会
\(60Pts\)LCA
利用\(LCA\)来模拟遍历的过程,边模拟边统计每条边经过的次数
用一下别人的代码(悄悄粘贴过来
#include<bits/stdc++.h> using namespace std; const int N=1e5+5; int head[N],ecnt; struct edge { int to,nxt; }edg[N]; void add(int u,int v) { edg[++ecnt].to=v; edg[ecnt].nxt=head[u]; head[u]=ecnt; } int in[N],cntnod; int n,a[N],dep[N],fa[N],faedg[N]; int vis[N]; int ans[N<<2],cnt; void dfs(int u,int f) { for(int i=head[u];i;i=edg[i].nxt) { int v=edg[i].to; in[v]++; if(v==f) continue; fa[v]=u; faedg[v]=i; dep[v]=dep[u]+1; dfs(v,u); } } int ans1[N],cnt1,ans2[N],cnt2; void work(int now,int last) { cnt1=cnt2=0; if(dep[now]>dep[last]) { while(dep[now]>dep[last]) { vis[faedg[now]]++; if(vis[faedg[now]]>2) { cout<<-1; exit(0); } ans1[++cnt1]=now; now=fa[now]; } } else if(dep[now]<dep[last]) { while(dep[now]<dep[last]) { vis[faedg[last]]++; if(vis[faedg[last]]>2) { cout<<-1; exit(0); } ans2[++cnt2]=last; last=fa[last]; } } while(now!=last) { vis[faedg[now]]++; if(vis[faedg[now]]>2) { cout<<-1; exit(0); } ans1[++cnt1]=now; now=fa[now]; vis[faedg[last]]++; if(vis[faedg[last]]>2) { cout<<-1; exit(0); } ans2[++cnt2]=last; last=fa[last]; } ans1[++cnt1]=now; for(int i=1;i<=cnt1;i++) ans[++cnt]=ans1[i]; for(int i=cnt2;i>=1;i--) ans[++cnt]=ans2[i]; } int main() { freopen("climb.in","r",stdin); freopen("climb.out","w",stdout); scanf("%d",&n); for(int i=1,u,v;i<n;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dep[1]=1; a[0]=1; for(int i=1;i<=n;i++) fa[i]=i; dfs(1,1); for(int i=2;i<=n;i++) if(in[i]==1) cntnod+=1; for(int i=1;i<=cntnod;i++) scanf("%d",&a[i]); a[0]=1; a[cntnod+1]=1; for(int i=1;i<=cntnod+1;i++) work(a[i-1],a[i]); for(int i=1;i<=cnt;i++) if(ans[i]!=ans[i-1]) printf("%d ",ans[i]); return 0; }
\(100Pts\)自动AC机
- 给叶子结点根据顺序从小到大赋值(\(qyf\):大概可以表示成区间【二元组】),父亲的值是儿子中的最小值。\(dfs\)时从小到大访问,若叶子结点的顺序符合则输出,否则为0。
\(qyf\)神仙的近似代码
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const int N=100005; int n,lst[N],to[N<<1],nxt[N<<1],cnt; int ye[N],lif[N],l[N],r[N],ycnt; bool isye[N]; char ch; inline int read() { int x=0; ch=getchar(); while(!isdigit(ch)) ch=getchar(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); return x; } inline void addedge(int u,int v) { nxt[++cnt]=lst[u]; lst[u]=cnt; to[cnt]=v; } void dfs(int u,int fa)//ye { bool son=0; int t; for(int e=lst[u];e;e=nxt[e]) if((t=to[e])!=fa) { son=1; dfs(t,u); ye[u]+=ye[t]; } if(!son) ycnt++,ye[u]=1,isye[u]=1; } int dfsbo(int u,int fa) { int t; for(int e=lst[u];e;e=nxt[e]) if((t=to[e])!=fa) { if(dfsbo(t,u)) return 1; l[u]=min(l[u],l[t]); r[u]=max(r[u],r[t]); } if(r[u]-l[u]+1!=ye[u]) return 1; else return 0; } struct node{ int ll,ord; }hed; inline bool operator < (const node &a,const node &b) { return a.ll>b.ll; } void dfsans(int u,int fa) { if(isye[u]) { printf("%d ",u); return; } printf("%d ",u); priority_queue<node> hep; for(int e=lst[u];e;e=nxt[e]) { if(to[e]!=fa) { hep.push((node){l[to[e]],to[e]}); } } while(!hep.empty()) { hed=hep.top(); hep.pop(); dfsans(hed.ord,u); printf("%d ",u); } } int main() { freopen("climb.in","r",stdin); freopen("climb.out","w",stdout); n=read(); int u,v; memset(l,0x3f,sizeof l); for(int i=1;i<n;++i) { u=read(),v=read(); addedge(u,v); addedge(v,u); } dfs(1,0); for(int i=1;i<=ycnt;++i) { lif[i]=read(); l[lif[i]]=r[lif[i]]=i; } if(dfsbo(1,0)) printf("-1"); else dfsans(1,0); return 0; }
2.饭鸽的神仙\(LCA\),咱也不知道怎么就卡过去了,反正直接看代码就能理解
饭鸽神仙的代码
#include<cstdio> #include<algorithm> using namespace std; int n,m; int head[100005],nxt[200005],to[200005]; int dep[100005]; int cnt; int fa[100005][21]; int s[2000005],top; int ans[2000005],tot; int q[100005]; int vis[100005]; int du[100005]; void dfs(int x,int f) { dep[x]=dep[f]+1; for(int i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(y==f) continue; fa[y][0]=x; dfs(y,x); } } int Lca(int x,int y) { if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--) { if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; }if(x==y) return x; for(int i=20;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } return fa[x][0]; } void add_edge(int x,int y) { cnt++; nxt[cnt]=head[x]; head[x]=cnt; to[cnt]=y; } int main() { freopen("climb.in","r",stdin); freopen("climb.out","w",stdout); scanf("%d",&n); int x,y; for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); du[x]++;du[y]++; add_edge(x,y); add_edge(y,x); } int m=0; for(int i=2;i<=n;i++) { if(du[i]==1) m++; } dfs(1,0); for(int i=1;i<=m;i++) { scanf("%d",&q[i]); } vis[q[1]]++; x=fa[q[1]][0]; while(x!=1) { vis[x]++; s[++top]=x; x=fa[x][0]; } s[++top]=1; while(top) { ans[++tot]=s[top]; top--; } for(int i=1;i<m;i++) { int x=q[i],y=q[i+1]; int lca=Lca(x,y); while(x!=lca) { vis[x]++; if(vis[x]>2) { printf("-1\n"); return 0; } ans[++tot]=x; x=fa[x][0]; } ans[++tot]=lca; vis[y]++; if(vis[y]>2) { printf("-1\n"); return 0; } y=fa[y][0]; while(y!=lca) { vis[y]++; if(vis[y]>2) { printf("-1\n"); return 0; } s[++top]=y; y=fa[y][0]; } while(top) { ans[++tot]=s[top]; top--; } } y=q[m]; while(y!=1) { vis[y]++; if(vis[y]>2) { printf("-1\n"); return 0; } ans[++tot]=y; y=fa[y][0]; } ans[++tot]=1; for(int i=2;i<=n;i++) { if(vis[i]!=2) { printf("-1\n"); return 0; } } for(int i=1;i<=tot;i++) { printf("%d ",ans[i]); } printf("\n"); return 0; }
T3 小 R 与苹果派
这个题正解是\(DP\)但是显然我不会(雾
所以从部分分看起\(⑧\)
\(10Pts\)搜索
这个就直接根据题目意思去搜就行(但是我爆了【
还是回归到代码上来吧
#include<iostream> #include<cstdio> #include<cstdlib> #include<ctime> #include<algorithm> using namespace std; int read() { char ch=getchar(); int a=0,x=1; while(ch<'0'||ch>'9') { if(ch=='-') x=-x; ch=getchar(); } while(ch>='0'&&ch<='9') { a=(a<<1)+(a<<3)+(ch-'0'); ch=getchar(); } return a*x; } const int mod=1e9+9; const int N=3000; int n,m; int vis[N],a[N],b[N]; long long ans,sum=1; void dfs(int k,long long sum) { if(k>n) { if(sum==m) ans=(ans+1)%mod; return ; } for(int i=1;i<=n;i++) { if(!vis[i]) { vis[i]=1; if(a[i]>b[k]) dfs(k+1,sum+1); else dfs(k+1,sum-1); vis[i]=0; } } } int main() { freopen("pie.in","r",stdin); freopen("pie.out","w",stdout); n=read();m=read(); for(int i=1;i<=n;i++) sum=sum*i%mod; for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++) b[i]=read(); dfs(1,0); printf("%lld",ans); return 0; }
\(30Pts\)玄学
主要是全场无\(30Pts\)
难受
就当它是剪枝很好的搜索吧(雾
\(100Pts\)DP
这不废话吗
直接题解搬来叭
首先对两个数组排序
然后预处理出数组\(p[i]\)表示一个极大值使得\(b[x]<a[i]\)
然后我们设\(f[i][k]\)表示对于前\(i\)个派,至少有\(k\)组\(a[i]>b[i]\)
显然\(f[i][k]=f[i-1][k]+f[i-1][k-1]\times(p[i]-(k-1))\)
这个数组显然不是答案
这里的\(f[i][k]\)保证了只考虑到\(A\)的前\(i\)个,\(B\)的所有位置,并且满足只给\(A>B\)的\(k\)个\(A\)分配了\(B\),其余\(A\)和\(B\)没有配对
第一项相当于考虑\(A[i]\)不分配\(B\),第二项相当于\(A[i]\)分配\(B\)
我们不妨设\(g[i]\)表示对于前n个派,恰好有\(i\)组\(a[x]>b[x]\)
运用容斥原理:
\(g[i]=f[n][i]\times(n-i)!-g[j]\times C^{i+1}_{j}\),其中\(i+1\leqslant j\leqslant n\)
这里的第一项相当于只分配了\(B\)的\(i\)个\(A\)的方案数\(\times\)没分配\(B\)的\((n-i)\)个A分配\(B\)的方案数(阶乘项)是所有\(\geqslant i\)对的数量,但注意这里可能会出现同一种分配多次出现的情况(\(3\)个位置,\(1、2\)分配了\(1、2\),\(3\)对应\(3\)\(;1、3\)分配了\(1、3\),\(2\)对应\(2\),统计了多次),所以容斥减掉的项有组合数来剪掉重复出现的方案。
设最后答案是\(g[s]\),然后我们来求一下\(s\)
显然\(s-(n-s)=k\) 解得\(s=\frac{(n+k)}{2};\)
那么最后\(g[s]\)就是答案。
注意:若\(N+k\)为奇数,则答案为零。
Code
#include<iostream> #include<cstdio> #include<algorithm> #define N 2010 #define P 1000000009 using namespace std; int i,j,a[N],b[N],s[N],n,k,t,p; long long f[N][N],g[N],c[N][N],fa[N]; int main(){ freopen("pie.in","r",stdin); freopen("pie.out","w",stdout); scanf("%d%d",&n,&k);t=(n+k)/2; if ((n+k)%2){cout<<0;return 0;} for (i=1;i<=n;scanf("%d",&a[i]),i++); for (i=1;i<=n;scanf("%d",&b[i]),i++); sort(a+1,a+n+1);sort(b+1,b+n+1); for (i=1,p=1;i<=n;i++){while(p<=n&&b[p]<a[i])p++;s[i]=p-1;} for(i=0,c[i][0]=1;i<=n;i++,c[i][0]=1)for(j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%P; for(fa[0]=1,i=1;i<=n;i++)fa[i]=fa[i-1]*i%P; for(f[0][0]=f[1][0]=1,i=1;i<=n;i++,f[i][0]=1) for(j=1;j<=i;j++)f[i][j]=(f[i-1][j]+f[i-1][j-1]*max(s[i]-(j-1),0))%P; for(i=n;i>=t;i--){ g[i]=f[n][i]*fa[n-i]%P; for(j=i+1;j<=n;j++)(g[i]+=P-g[j]*c[j][i]%P)%=P; } cout<<g[t]<<endl; }