quailty's Contest #1 题解
2016-02-05 23:22
465 查看
比赛链接:
http://www.bnuoj.com/v3/contest_show.php?cid=7561
A. 道路修建
如果使用可持久化并查集,二分答案判定连通性,复杂度是O(mα(n)log2n)O(m\alpha(n)log^2n),不能在时限内出解。考虑到并查集实际上是一棵树,可以尝试在边上维护一些信息,假设tt时刻加了一条边(u,v)(u,v),若uu和vv此时未连通,则在root(u)root(u)和root(v)root(v)之间连一条权值为tt的边,表示uu所在集合以及vv所在集合在tt时刻连通,这样对于一组查询(u,v)(u,v),如果uu和vv位于同一个连通块内,只需找出并查集中uu到vv的路径上的权值最大值,很显然这样是不能路径压缩的,但是可以按秩合并保证树高是O(logn)O(logn),总的复杂度是O(mlogn)O(mlogn)。
B. 魔方复原
对魔方的每个方块标号之后,每个操作的置换可以手工推出,由于多次操作的复合仍然是一个置换,因此魔方总是能复原,并且将置换分解成环之后,操作序列的重复次数就是这些环长度的lcm(最小公倍数)。现在考虑如何处理操作序列,对于只包含字母的序列,直接模拟即可,对于需要将某一段重复多次的序列,只需对处理循环节之后得到的置换做一个若干次幂,倍增即可,对于括号嵌套的情况,直接递归处理即可。
C. 组队活动
记dp[i]dp[i]表示标号为1,2,…,i的队员组队的方案数,则有dp[0]=1dp[0]=1通过枚举1,2,3,…,i-1中与i组队的队员,可以知道dp[i]=∑j=0m−1Cji−1dp[i−j−1]dp[i]=\sum\limits_{j=0}^{m-1}{C_{i-1}^{j}dp[i-j-1]},直接dp的复杂度是O(nm)O(nm)的。如果将式子整理一下,可以得到dp[i]i!=1i∑j=0m−11j!dp[i−j−1](i−j−1)!\frac{dp[i]}{i!}=\frac{1}{i}\sum\limits_{j=0}^{m-1}\frac{1}{j!}\frac{dp[i-j-1]}{(i-j-1)!},这是一个卷积的形式,用分治NTT可以得到一个复杂度为T[n]=2T[n/2]+O(nlogn)T
=2T[n/2]+O(nlogn),即T[n]=O(nlog2n)T
=O(nlog^2n)的算法。
http://www.bnuoj.com/v3/contest_show.php?cid=7561
A. 道路修建
如果使用可持久化并查集,二分答案判定连通性,复杂度是O(mα(n)log2n)O(m\alpha(n)log^2n),不能在时限内出解。考虑到并查集实际上是一棵树,可以尝试在边上维护一些信息,假设tt时刻加了一条边(u,v)(u,v),若uu和vv此时未连通,则在root(u)root(u)和root(v)root(v)之间连一条权值为tt的边,表示uu所在集合以及vv所在集合在tt时刻连通,这样对于一组查询(u,v)(u,v),如果uu和vv位于同一个连通块内,只需找出并查集中uu到vv的路径上的权值最大值,很显然这样是不能路径压缩的,但是可以按秩合并保证树高是O(logn)O(logn),总的复杂度是O(mlogn)O(mlogn)。
[code]#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<iostream> #include<algorithm> using namespace std; const int MAXN=100005; int fa[MAXN],ra[MAXN],tcn[MAXN],vis[MAXN]; void Init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; ra[i]=0; tcn[i]=0; vis[i]=-1; } } int Find(int x) { return x==fa[x] ? x : Find(fa[x]); } bool Union(int x,int y,int t) { x=Find(x); y=Find(y); if(x==y)return 0; if(ra[x]>ra[y]) { fa[y]=x; tcn[y]=t; } else { fa[x]=y; tcn[x]=t; if(ra[x]==ra[y])ra[y]++; } return 1; } int Query(int x,int y) { int p=x,now=0; while(1) { vis[p]=now; if(p==fa[p])break; now=max(now,tcn[p]); p=fa[p]; } int res=0; now=0; p=y; while(1) { if(vis[p]>=0) { res=max(vis[p],now); break; } if(p==fa[p])break; now=max(now,tcn[p]); p=fa[p]; } p=x; while(1) { vis[p]=-1; if(p==fa[p])break; p=fa[p]; } return res; } int main() { int T; scanf("%d",&T); while(T--) { int n,m; scanf("%d%d",&n,&m); Init(n); int la=0,bk=n; for(int i=1;i<=m;i++) { int p,u,v; scanf("%d%d%d",&p,&u,&v); u^=la,v^=la; if(p==0) { if(Union(u,v,i))bk--; printf("%d\n",la=bk); } else { printf("%d\n",la=Query(u,v)); } } } return 0; }
B. 魔方复原
对魔方的每个方块标号之后,每个操作的置换可以手工推出,由于多次操作的复合仍然是一个置换,因此魔方总是能复原,并且将置换分解成环之后,操作序列的重复次数就是这些环长度的lcm(最小公倍数)。现在考虑如何处理操作序列,对于只包含字母的序列,直接模拟即可,对于需要将某一段重复多次的序列,只需对处理循环节之后得到的置换做一个若干次幂,倍增即可,对于括号嵌套的情况,直接递归处理即可。
[code]#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<iostream> #include<algorithm> #include<vector> using namespace std; typedef unsigned long long ull; const int L=54; const int MAXN=100005; const char c[7]="FLRUDB"; const int d[6][5][4]= { {{1,3,9,7},{2,6,8,4},{43,19,48,18},{44,22,47,15},{45,25,46,12}},//F {{10,12,18,16},{11,15,17,13},{37,1,46,36},{40,4,49,33},{43,7,52,30}},//L {{19,21,27,25},{20,24,26,22},{54,9,45,28},{51,6,42,31},{48,3,39,34}},//R {{37,39,45,43},{38,42,44,40},{28,19,1,10},{29,20,2,11},{30,21,3,12}},//U {{16,7,25,34},{17,8,26,35},{46,48,54,52},{47,51,53,49},{18,9,27,36}},//D {{28,30,36,34},{29,33,35,31},{27,39,10,52},{24,38,13,53},{21,37,16,54}}//B }; int ty[256]; void work(vector<int>&p,int type) { for(int i=0;i<5;i++) { int t=p[d[type][i][3]]; for(int j=3;j>0;j--) p[d[type][i][j]]=p[d[type][i][j-1]]; p[d[type][i][0]]=t; } } void multiply(vector<int>&p,vector<int>&q) { int t[L+1]; for(int i=1;i<=L;i++)t[i]=p[q[i]]; for(int i=1;i<=L;i++)p[i]=t[i]; } char s[MAXN]; int loc; vector<int> solve() { vector<int>p(L+1); for(int i=1;i<=L;i++)p[i]=i; while(s[loc] && s[loc]!=')') { if(s[loc]>='0' && s[loc]<='9') { int r=0; while(s[loc]>='0' && s[loc]<='9') r=r*10+s[loc++]-'0'; loc++; vector<int>q=solve(); while(r) { if(r&1)multiply(p,q); multiply(q,q); r>>=1; } } else work(p,ty[s[loc++]]); } loc++; return p; } ull gcd(ull a,ull b) { return b==0 ? a : gcd(b,a%b); } //p[i]表示当前在标号为i的位置能找到初始标号为p[i]的方块 ull get_ans(vector<int>p) { bool vis[L+1]; memset(vis,0,sizeof(vis)); ull res=1LL; for(int i=1;i<=L;i++) if(!vis[i]) { ull len=1LL; vis[i]=1; int u=i; while(p[u]!=i) { u=p[u]; vis[u]=1; len++; } res=res/gcd(res,len)*len; } return res; } int main() { for(int i=0;i<6;i++)ty[c[i]]=i; int T; scanf("%d",&T); while(T--) { scanf("%s",s); loc=0; printf("%llu\n",get_ans(solve())); } return 0; }
C. 组队活动
记dp[i]dp[i]表示标号为1,2,…,i的队员组队的方案数,则有dp[0]=1dp[0]=1通过枚举1,2,3,…,i-1中与i组队的队员,可以知道dp[i]=∑j=0m−1Cji−1dp[i−j−1]dp[i]=\sum\limits_{j=0}^{m-1}{C_{i-1}^{j}dp[i-j-1]},直接dp的复杂度是O(nm)O(nm)的。如果将式子整理一下,可以得到dp[i]i!=1i∑j=0m−11j!dp[i−j−1](i−j−1)!\frac{dp[i]}{i!}=\frac{1}{i}\sum\limits_{j=0}^{m-1}\frac{1}{j!}\frac{dp[i-j-1]}{(i-j-1)!},这是一个卷积的形式,用分治NTT可以得到一个复杂度为T[n]=2T[n/2]+O(nlogn)T
=2T[n/2]+O(nlogn),即T[n]=O(nlog2n)T
=O(nlog^2n)的算法。
[code]#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int MAXN=100005; const ll Mod=998244353; const ll g=3; void change(ll y[],int len) { for(int i=1,j=len/2;i<len-1;i++) { if(i<j)swap(y[i],y[j]); int k=len/2; while(j>=k) { j-=k; k/=2; } if(j<k)j+=k; } } ll fp(ll a,ll k) { if(k<0) { a=fp(a,Mod-2); k=-k; } ll res=1; while(k) { if(k&1)res=res*a%Mod; a=a*a%Mod; k>>=1; } return res; } void ntt(ll y[],int len,int on) { change(y,len); for(int h=2;h<=len;h<<=1) { ll wn=fp(g,-on*(Mod-1)/h); for(int j=0;j<len;j+=h) { ll w=1; for(int k=j;k<j+h/2;k++) { ll u=y[k]; ll t=w*y[k+h/2]%Mod; y[k]=(u+t)%Mod; y[k+h/2]=(u-t+Mod)%Mod; w=w*wn%Mod; } } } if(on==-1) { ll t=fp(len,-1); for(int i=0;i<len;i++) y[i]=y[i]*t%Mod; } } ll inv[MAXN],invf[MAXN]; void build() { for(int i=1;i<MAXN;i++) inv[i]=fp(i,-1); invf[0]=1; for(int i=1;i<MAXN;i++) invf[i]=inv[i]*invf[i-1]%Mod; } ll dp[MAXN],x1[MAXN<<1],x2[MAXN<<1]; void cdq(int l,int r,int k) { if(l==r)return; int m=(l+r)>>1; cdq(l,m,k); int len=1; while(len<=r-l+1)len<<=1; for(int i=0;i<len;i++) { x1[i]=(i<k ? invf[i] : 0); x2[i]=(l+i<=m ? dp[l+i] : 0); } ntt(x1,len,1); ntt(x2,len,1); for(int i=0;i<len;i++) x1[i]=x1[i]*x2[i]%Mod; ntt(x1,len,-1); for(int i=m+1;i<=r;i++) dp[i]=(dp[i]+x1[i-l-1]*inv[i])%Mod; cdq(m+1,r,k); } int main() { build(); int T; scanf("%d",&T); while(T--) { int n,k; scanf("%d%d",&n,&k); memset(dp,0,sizeof(dp)); dp[0]=1; cdq(0,n,k); printf("%lld\n",dp *fp(invf ,-1)%Mod); } return 0; }
相关文章推荐
- cf#AIM Tech Round -B. Making a String-贪心/set
- cf#AIM Tech Round -C. Graph and String-贪心/ 二分图染色
- cf#AIM Tech D. Array GCD (数学+枚举)
- AIM Tech Round (Div. 2) D. Array GCD(dp)
- codeforces AIM Tech Round
- WM_PAINT消息详解,使用InvalidateRect或InvalidateRgn函数刻意产生WM_PAINT消息(WIN7里有变化,“调整视觉效果”,将“启用桌面组合”去掉)
- WM_PAINT消息在窗口重绘的时候产生,那什么时候窗口会重绘(异步工作方式,效率更高,灵活性更强)
- 四川成都电信天邑TEWA-300AI EPON光猫各种折腾
- Cloud Foundry warden container 安全性探讨
- async await
- VIM配置:vim-airline插件安装
- AIM Tech Round (Div. 2) C. Graph and String
- AIM Tech Round (Div. 2)C - Graph and String(二分图染色)
- gmail如何设置邮箱别名
- Codeforces AIM Tech Round (Div. 1) ABD
- Codeforces AIM Tech Round (Div. 1) A Graph and String 想法
- AIM Tech Round (Div. 2) D. Array GCD dp
- poj 3007 Organize Your Train part II(字符串哈希)
- symfony2 服务容器(Service Container)
- AIM Tech Round (Div. 2) C. Graph and String 二分图染色