quailty's Contest #1 题解

比赛链接:
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=0m1Cji1dp[ij1] ,直接dp的复杂度是 O(nm) 的。如果将式子整理一下,可以得到 dp[i]i!=1ij=0m11j!dp[ij1](ij1)! ,这是一个卷积的形式,用分治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;
}

猜你喜欢

转载自blog.csdn.net/quailty/article/details/50638004