比赛链接:
http://www.bnuoj.com/v3/contest_show.php?cid=7561
A. 道路修建
如果使用可持久化并查集,二分答案判定连通性,复杂度是 O(mlog3n) ,不能在时限内出解。考虑到并查集实际上是一棵树,可以尝试在边上维护一些信息,假设 t 时刻加了一条边 (u,v) ,若 u 和 v 此时未连通,则在 root(u) 和 root(v) 之间连一条权值为 t 的边,表示 u 所在集合以及 v 所在集合在 t 时刻连通,这样对于一组查询 (u,v) ,如果 u 和 v 位于同一个连通块内,只需找出并查集中 u 到 v 的路径上的权值最大值,很显然这样是不能路径压缩的,但是可以按秩合并保证树高是 O(logn) ,总的复杂度是 O(mlogn) 。
#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(最小公倍数)。现在考虑如何处理操作序列,对于只包含字母的序列,直接模拟即可,对于需要将某一段重复多次的序列,只需对处理循环节之后得到的置换做一个若干次幂,倍增即可,对于括号嵌套的情况,直接递归处理即可。
#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] 表示标号为1,2,…,i的队员组队的方案数,则有 dp[0]=1 ,通过枚举1,2,3,…,i-1中与i组队的队员,可以知道 dp[i]=∑j=0m−1Cji−1dp[i−j−1] ,直接dp的复杂度是 O(nm) 的。如果将式子整理一下,可以得到 dp[i]i!=1i∑j=0m−11j!dp[i−j−1](i−j−1)! ,这是一个卷积的形式,用分治NTT可以得到一个复杂度为 T[n]=2T[n/2]+O(nlogn) ,即 T[n]=O(nlog2n) 的算法。
#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[n]*fp(invf[n],-1)%Mod);
}
return 0;
}