十二省联考2019题解

day1t1

考场上唯一会做的题,还因为习惯性写for(int i=1;i<=n;i++)而挂掉40分,本次考试最大的败笔。

说说我的做法:对每一个a[i]建立一棵字典树,然后维护一个,记录对于每个a[i]第x大的异或值,然后每次弹出时记录下一个。不需要可持久化,因为可以给字典树记录一个维护子树大小的sz数组,查询方式类似于treap。

考场上只改了一个字符的code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int N=5e5+7;
struct node{int u,id;ll v;};
int n,k,tot=1,ch[N*33][2],sz[N*33];
ll ans,a[N];
bool operator<(node a,node b){return a.v<b.v;}
priority_queue<node>q;
void build(ll x)
{
    int u=1;
    for(int i=31;i>=0;i--)
    {
        int idx;
        if(x&(1ll<<i))idx=1;else idx=0;
        if(!ch[u][idx])ch[u][idx]=++tot;
        u=ch[u][idx];
        sz[u]++;
    }
}
ll query(ll x,int rk)
{
    int u=1;
    ll ret=0;
    for(int i=31;i>=0;i--)
    {
        int idx;
        if(x&(1ll<<i))idx=1;else idx=0;
        if(sz[ch[u][idx^1]]>=rk)ret+=1ll<<i,u=ch[u][idx^1];
        else rk-=sz[ch[u][idx^1]],u=ch[u][idx];
    }
    return ret;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]),a[i]^=a[i-1];
    for(int i=0;i<=n;i++)build(a[i]);
    for(int i=0;i<=n;i++)q.push((node){i,1,query(a[i],1)});
    for(int tim=1;tim<2*k;tim++)
    {
        node u=q.top();q.pop();
        if(tim&1)ans+=u.v;
        if(u.id==n)continue;
        u.id++,u.v=query(a[u.u],u.id);
        q.push(u);
    }
    printf("%lld",ans);
    return 0;
}
View Code

day1t2

考场上只会40pts的O(nm)哈希暴力,80pts写挂弃疗,还是太水了。

做法:其实不用SA,可以用一种代码较短的写法,但是思维难度比较高,要用到SAM+树上倍增+字典树优化建图+拓扑排序

首先建立反串的SAM,然后记录反串每个位置在SAM上的位置,再倍增。对SAM的每个点开vector,然后按照子串长度和是否为A类串为1和2关键字排序即可,再用字典树优化建边,就可以跑拓扑排序了(考场上我还写tarjan判环)。

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+7;
int n,m,na,nb,sz,tot,a[N],b[N],is[N<<1],in[N<<1],hd[N<<1],v[N<<2],nxt[N<<2];
int last,cnt,id[N],lst[N],fa[N],f[N][20],ch[N][26],len[N<<1];
ll dis[N<<1];
char s[N];
vector<int>G[N<<1];
queue<int>q;
bool cmp(int x,int y){return len[x]>len[y]||len[x]==len[y]&&is[x]>is[y];}
void adde(int x,int y){v[++tot]=y,nxt[tot]=hd[x],hd[x]=tot,in[y]++;}
void build(int c)
{
    int p=last,np=++cnt;
    last=np,len[np]=len[p]+1;
    while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
    if(!p)fa[np]=1;
    else{
        int q=ch[p][c];
        if(len[p]+1==len[q])fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof ch[q]);
            fa[nq]=fa[q],fa[q]=fa[np]=nq;
            while(p&&ch[p][c]==q)ch[p][c]=nq,p=fa[p];
        }
    }
}
void judge(int b)
{
    int l,r;scanf("%d%d",&l,&r);
    r=r-l+1,l=id[l];
    for(int i=19;~i;i--)if(f[l][i]&&len[f[l][i]]>=r)l=f[l][i];
    is[++sz]=b,len[sz]=r;
    G[l].push_back(sz);
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s+1),n=strlen(s+1);
        last=cnt=1;
        for(int i=n;i;i--)build(s[i]-'a'),id[i]=last;
        for(int i=1;i<=cnt;i++)f[i][0]=fa[i];
        for(int j=1;j<=19;j++)for(int i=1;i<=cnt;i++)f[i][j]=f[f[i][j-1]][j-1];
        scanf("%d",&na),sz=cnt;
        for(int i=1;i<=na;i++)judge(1),a[i]=sz;
        scanf("%d",&nb);
        for(int i=1;i<=nb;i++)judge(0),b[i]=sz;
        for(int i=1;i<=cnt;i++)sort(G[i].begin(),G[i].end(),cmp);
        for(int i=1;i<=cnt;i++)
        {
            int last=i;
            for(int j=G[i].size()-1;j>=0;j--)
            {
                adde(last,G[i][j]);
                if(!is[G[i][j]])last=G[i][j];
            }
            lst[i]=last;
        }
        for(int i=2;i<=cnt;i++)adde(lst[fa[i]],i);
        for(int i=1;i<=sz;i++)if(!is[i])len[i]=0;
        scanf("%d",&m);
        int flag=0;ll ans=0;
        for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),adde(a[x],b[y]);
        for(int i=1;i<=sz;i++)if(!in[i])q.push(i);
        while(!q.empty())
        {
            int x=q.front();q.pop(),ans=max(ans,dis[x]+len[x]);
            for(int i=hd[x];i;i=nxt[i])
            {
                dis[v[i]]=max(dis[v[i]],dis[x]+len[x]);
                if(!--in[v[i]])q.push(v[i]);
            }
        }
        for(int i=1;i<=sz;i++)if(in[i]){flag=1;break;}
        if(flag)puts("-1");else printf("%lld
",ans);
        while(!q.empty())q.pop();
        for(int i=1;i<=cnt;i++)fa[i]=0,memset(ch[i],0,sizeof ch[i]);
        for(int i=1;i<=sz;i++)G[i].clear(),is[i]=len[i]=hd[i]=dis[i]=in[i]=0;
        last=cnt=sz=tot=0;
    }
}
View Code

day1t3

坑。

考场上杠了很长时间T3,获得39分的“好”成绩

做法:这题目太垃圾,做个鬼,而且也做不了,不用gedit也打不开最后一个点,真实防AK的题。

说说39分的吧:subtask1~3:直接写,注意指数模998244352即可;subtask4:找到最大的结果,然后找2个结果相同的,就能求出循环节,但这里要找2组,这样就可以愉快的求gcd了,很容易找到模数;subtask6:快速幂不乘long long,做法就是直接写ans=ans*19%mod,开int就行了;subtask8:线性筛质数(实际上我傻了没想起来用1e6以内的质数筛出第9个点,可能是考场上太紧张了);subtask11:线性筛mu;subtask14:求原根。原根的定义:对于一个整数p,若p^0,p^1,p^2,……,p^(mod-2)在%mod的意义下互不相同。然后由于p^(Mod-1)≡1(mod Mod),所以p^i的循环节必然是mod-1的因数,枚举1e5个数判断循环节,是否中途回到1即可,复杂度是O(1e5*31*步长个数),但是mod-1有94个不为1或mod-1的因数,显然会TLE(实测11s),那如何优化呢?发现一些步长是另一些的因数,显然这些是可以筛掉的,只保留真正有用的就行,发现1s左右就跑完了。

考场39pts code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e6+7;
int mod=998244353,cnt,pri[N],vis[N],mu[N];
char typ[101],str[1001];
int qpow(int a,int b)
{
    int ret=1;
    while(b)
    {
        if(b&1)ret=1ll*ret*a%mod;
        a=1ll*a*a%mod,b>>=1;
    }
    return ret;
}
void so_easy()
{
    int Q,n,len;scanf("%d",&Q);
    while(Q--)
    {
        scanf("%s",str);
        len=strlen(str),n=0;
        for(int i=0;i<len;i++)n=(10ll*n+str[i]-'0')%(mod-1);
        printf("%d
",qpow(19,n));
    }
}
void prepare()
{
    mu[1]=1;
    for(int i=2;i<=1e6;i++)
    {
        if(!vis[i])pri[++cnt]=i,mu[i]=-1;
        for(int j=1;j<=cnt&&i*pri[j]<=1e6;j++)
        {
            vis[i*pri[j]]=1;
            if(i%pri[j]==0){mu[i*pri[j]]=0;break;}
            mu[i*pri[j]]=-mu[i];
        }
    }
}
int main()
{
    scanf("%s",typ);
    int lentyp=strlen(typ);
    if(typ[0]=='1'&&typ[1]=='_'){so_easy();return 0;}
    if(lentyp==2&&typ[1]=='?')
    {
        mod=1145141;
        int Q,n,len;scanf("%d",&Q);
        while(Q--)
        {
            scanf("%s",str);
            len=strlen(str),n=0;
            for(int i=0;i<len;i++)n=(10ll*n+str[i]-'0')%572570;
            printf("%d
",qpow(19,n));
        }
        return 0;
    }
    if(typ[0]=='1'&&typ[1]=='w')
    {
        int Q,n=1,len;scanf("%d",&Q);
        while(Q--)
        {
            scanf("%*s");
            printf("%d
",n);
            n=n*19%mod;
        }
        return 0;
    }
    prepare();
    if(typ[0]=='2'&&typ[1]=='p')
    {
        int Q,a,b;
        scanf("%d",&Q);
        while(Q--)
        {
            scanf("%d%d",&a,&b);
            if(a==5&&b==10)puts("p.p...");
            else if(a==2)
            {
                for(int i=2;i<=b;i++)if(!vis[i])printf("p");else printf(".");
                puts("");
            }
        }
        return 0;
    }
    if(typ[0]=='2'&&typ[1]=='u')
    {
        int Q,a,b;
        scanf("%d",&Q);
        while(Q--)
        {
            scanf("%d%d",&a,&b);
            for(int i=a;i<=b;i++)
            if(mu[i]>0)printf("+");else if(mu[i]<0)printf("-");else printf("0");
            puts("");
        }
        return 0;
    }
    if(typ[0]=='2'&&typ[1]=='g')
    {
        cnt=0;
        for(int i=2;i*i<mod;i++)if((mod-1)%i==0)pri[++cnt]=i,pri[++cnt]=(mod-1)/i;
        sort(pri+1,pri+cnt+1);
        for(int i=cnt-1;i>=1;i--)
        for(int j=i+1;j<=cnt;j++)
        if(pri[j]&&pri[j]%pri[i]==0){pri[i]=0;break;}
        sort(pri+1,pri+cnt+1);
        reverse(pri+1,pri+cnt+1);
        while(!pri[cnt])cnt--;
        int Q,a,b;scanf("%d",&Q);
        while(Q--)
        {
            scanf("%d%d%*d",&a,&b);
            for(int i=a;i<=b;i++)
            {
                char as='g';
                for(int j=1;j<=cnt;j++)if(qpow(i,pri[j])==1){as='.';break;}
                printf("%c",as);
            }
            puts("");
        }
        return 0;
    }
    return 0;
}
View Code

day2t1

考场上SB只会40,出来发现50是个人都会

做法:50分做法:f[i][j][k]表示到第i个人,蓝阵营j人,鸭派k人的方案数,按城市排序暴力DP即可,滚动一维最好。

k=0做法:实际上就是把阵营和派系2个分开DP,然后方案数相乘就行了。

然鹅我两个考场上都没想到(或许也是心态问题)。

100分做法:发现k<=30,可以考虑在k^2*m的做法,本质是前两种做法的结合。发现一些学校不能在选某派的同时加入某阵营,但是其他学校选阵营和选派系似乎还是可以分开算。所以把未强制的学校按照k=0做法去计算,没有学校被强制的城市也如此做。对于有被强制的城市,注意在选择一个阵营以后要把这个城市所有学校全部加入该阵营。

时间复杂度:O(Tm(c+n+k^2))

code

#include<bits/stdc++.h>
#define mem(a) memset(a,0,sizeof a)
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int mod=998244353,N=2555;
int n,m,c,C0,C1,D0,D1,tot,all,ans,f[N][400],g[N][400],b[N],s[N],sz[N],del[N],fx[N],fy[N];
vector<pii>G[N];
void add(int&x,int y){x=x+y>=mod?x+y-mod:x+y;}
int ask(int*f,int l,int r){return r<0?0:(l<=0?f[r]:(f[r]-f[l-1]+mod)%mod);}
void clear()
{
    for(int i=1;i<=n;i++)G[i].clear();
    mem(f),mem(g),mem(b),mem(s),mem(sz),mem(del),mem(fx),mem(fy);
    ans=all=tot=0;    
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d%d%d",&n,&c,&C0,&C1,&D0,&D1);
        for(int i=1;i<=n;i++)scanf("%d%d",&b[i],&s[i]),sz[b[i]]+=s[i],all+=s[i];
        scanf("%d",&m);
        for(int i=1,x;i<=m;i++)scanf("%d",&x),scanf("%d",&del[x]),++del[x];
        if(C0+C1<all||D0+D1<all){puts("0"),clear();continue;}
        C0=all-C0,D0=all-D0;
        fx[0]=fy[0]=1;
        for(int i=1;i<=n;i++)
        if(!del[i])for(int j=D1;j>=s[i];j--)add(fy[j],fy[j-s[i]]);
        else G[b[i]].push_back(pii(s[i],del[i]));
        f[0][0]=1;
        for(int i=1;i<=c;i++)
        {
            if(G[i].empty()&&sz[i])for(int j=C1;j>=sz[i];j--)add(fx[j],fx[j-sz[i]]);
            if(G[i].empty())continue;
            memset(g,0,sizeof g);
            for(int j=sz[i];j<=C1;j++)for(int k=0;k<=tot;k++)g[j][k]=f[j-sz[i]][k];
            int sz1=tot,sz2=tot;
            for(int j=0;j<G[i].size();j++)
            {
                int tp=G[i][j].second-1;
                if(tp/2)
                {
                    sz1+=G[i][j].first;
                    for(int l=0;l<=C1;l++)
                    for(int k=sz1;k>=G[i][j].first;k--)
                    add(f[l][k],f[l][k-G[i][j].first]);
                    if(tp%2==0)
                    {
                        sz2+=G[i][j].first;
                        for(int l=0;l<=C1;l++)
                        for(int k=sz2;k>=G[i][j].first;k--)
                        g[l][k]=g[l][k-G[i][j].first];
                        for(int l=0;l<=C1;l++)for(int k=G[i][j].first-1;k>=0;k--)g[l][k]=0;
                    }
                }
                else{
                    sz2+=G[i][j].first;
                    for(int l=0;l<=C1;l++)
                    for(int k=sz2;k>=G[i][j].first;k--)
                    add(g[l][k],g[l][k-G[i][j].first]);
                    if(tp%2==0)
                    {
                        sz1+=G[i][j].first;
                        for(int l=0;l<=C1;l++)
                        for(int k=sz1;k>=G[i][j].first;k--)
                        f[l][k]=f[l][k-G[i][j].first];
                        for(int l=0;l<=C1;l++)
                        for(int k=G[i][j].first-1;k>=0;k--)
                        f[l][k]=0;
                    }
                }
            }
            tot=max(sz1,sz2);
            for(int i=0;i<=C1;i++)for(int j=0;j<=tot;j++)add(f[i][j],g[i][j]);
        }
        for(int i=1;i<=D1;i++)add(fy[i],fy[i-1]);
        for(int i=1;i<=C1;i++)add(fx[i],fx[i-1]);
        for(int i=0;i<=C1;i++)
        for(int j=0;j<=tot;j++)
        ans=(ans+1ll*f[i][j]*ask(fx,C0-i,C1-i)%mod*ask(fy,D0-j,D1-j))%mod;
        printf("%d
",ans);
        clear();
    }
}
View Code

day2t2

考场上再一次因为心态爆炸,想问题复杂错失一条链的15pts

做法:其实是一个神奇的贪心。先考虑一条链,就是将两侧从大到小排序,取最大的加起来即可。这个做法实际上可以拓展成一棵树,就是对每棵树维护一个优先队列,然后树形DP的时候,将两个队列启发式合并,然后dfs完成后把根节点的优先队列中所有值加起来就行了

code

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
priority_queue<int>q[N];
int n,cnt,w[N],deg[N],id[N],a[N];
vector<int>G[N];
long long ans;
void dfs(int u)
{
    id[u]=++cnt;
    for(int i=0;i<G[u].size();i++)
    {
        dfs(G[u][i]);
        if(q[id[u]].size()<q[id[G[u][i]]].size())swap(id[u],id[G[u][i]]);
        int k=q[id[G[u][i]]].size();
        for(int j=1;j<=k;j++)
        {
            a[j]=max(q[id[u]].top(),q[id[G[u][i]]].top());
            q[id[u]].pop(),q[id[G[u][i]]].pop();
        }
        for(int j=1;j<=k;j++)q[id[u]].push(a[j]);
    }
    q[id[u]].push(w[u]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    for(int i=2,x;i<=n;i++)scanf("%d",&x),G[x].push_back(i);
    dfs(1);
    while(!q[id[1]].empty())ans+=q[id[1]].top(),q[id[1]].pop();
    printf("%lld",ans);
}
View Code

day2t3

坑。

说一下考场想到的36pts和后来想到的52pts:

36pts做法:

1、指数级别的复杂度暴力16分我也不想说了。

2、对于一条链的部分分,可以这样考虑:考虑k条线段所共有的保护区域中,左端点为i的方案数,然后可以扫描一遍,统计一下保护区域包含i的线段数s1和保护区域左端点为i的线段数s2,然后对于i点答案就是qpow(s1,k)-qpow(s1-s2,k),s1和s2可以O(1)数学方法求解,边界条件有点繁琐还容易错,然后n个点累加起来即可。考场上拍了过了,实际上也过了。

3、也许是一条链给我的启发,可以把一条链左端点为i扩展为一棵树以1为根i为深度最浅的点。然后由于时间不够,只想到了L=n的做法:由于连通块没有要求,所以不需考虑距离等繁琐问题,然后想到对于每个点维护s1(过i点的连通块数),s2(以1为根i为深度最浅的点的连通块数)。s2很显然可以一发树形DP求解,DP方程:f[u]=1ll*f[u]*(f[son]+1)%mod。然后s1可以换根DP求解,撤回操作只需一个逆元即可,复杂度O(nlogn)

52pts:代码先咕了,只口胡写不出来,思路好像是对的。就是O(nL)的树形DP,正着DP一遍,再反过来换根DP,记录f[i][j]表示以i为根长度为j的连通块,然后只能用一维,id函数转换一下。结合一条链和n=L即可获得52pts。

52pts code(LOJ又莫名RE,在luogu得的52)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=11e6,mod=998244353;
int n,m,L,k,ans,sz[N],a[N],f[N],g[N],pre[N],suf[N];
vector<int>G[N];
int qpow(int a,int b)
{
    int ret=1;
    while(b)
    {
        if(b&1)ret=1ll*ret*a%mod;
        a=1ll*a*a%mod,b>>=1;
    }
    return ret;
}
void dp(int u,int fa)
{
    f[u]=1;
    for(int i=0;i<G[u].size();i++)
    if(G[u][i]!=fa)dp(G[u][i],u),f[u]=1ll*f[u]*(f[G[u][i]]+1)%mod;
}
void dp2(int u,int fa)
{
    for(int i=0;i<G[u].size();i++)
    if(G[u][i]!=fa)
    g[G[u][i]]=(1ll*g[u]*qpow(f[G[u][i]]+1,mod-2)%mod+1)*f[G[u][i]]%mod,dp2(G[u][i],u);
}
bool chain()
{
    for(int i=1;i<=n;i++)if(G[i].size()>2)return 0;
    return 1;
}
int id(int x,int y){return (x-1)*(L+2)+y;}
void dfs(int u,int fa)
{
    for(int i=0;i<G[u].size();i++)if(G[u][i]!=fa)dfs(G[u][i],u);
    for(int i=0;i<=L;i++)f[id(u,i)]=1;
    for(int i=0;i<G[u].size();i++)
    if(G[u][i]!=fa)for(int j=1;j<=L;j++)
    f[id(u,j)]=1ll*f[id(u,j)]*(f[id(G[u][i],j-1)]+1)%mod;
}
void dfs2(int u,int fa)
{
    int tot=0;
    for(int i=0;i<G[u].size();i++)if(G[u][i]!=fa)a[++tot]=G[u][i];
    pre[id(1,0)]=1;for(int j=1;j<=L;j++)pre[id(1,j)]=f[id(a[1],j-1)]+1;
    for(int i=2;i<=tot;i++)
    {
        pre[id(i,0)]=1;
        for(int j=1;j<=L;j++)pre[id(i,j)]=1ll*pre[id(i-1,j)]*(f[id(a[i],j-1)]+1)%mod;
    }
    suf[id(tot,0)]=1;for(int j=1;j<=L;j++)suf[id(tot,j)]=f[id(a[tot],j-1)]+1;
    for(int i=tot-1;i>0;i--)
    {
        suf[id(i,0)]=1;
        for(int j=1;j<=L;j++)suf[id(i,j)]=1ll*suf[id(i+1,j)]*(f[id(a[i],j-1)]+1)%mod;
    }
    for(int i=1;i<=tot;i++)
    {
        g[id(a[i],0)]=1;
        for(int j=1;j<=L;j++)
        {
            int x=i>1?pre[id(i-1,j-1)]:1,y=i<tot?suf[id(i+1,j-1)]:1;
            g[id(a[i],j)]=1ll*x*y%mod*g[id(u,j-1)]%mod+1;
        }
    }
    for(int i=0;i<G[u].size();i++)if(G[u][i]!=fa)dfs2(G[u][i],u);
}
int main()
{
    scanf("%d%d%d",&n,&L,&k);
    for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
    if(chain())
    {
        for(int i=1;i<=n;i++)
        {
            int l=max(1,i-L),r=min(n,i+L),s1,s2;
            s1=1ll*(r-i+1)*(i-l+1)%mod;
            s2=r-i+1;
            if(r==i+L)s2+=i-l;
            s2=(s1-s2+mod)%mod;
            ans=(1ll*ans+qpow(s1,k)-qpow(s2,k)+mod)%mod;
        }
        printf("%d",ans);return 0;
    }
    if(L==n)
    {
        dp(1,0),g[1]=f[1],dp2(1,0);
        for(int i=1,s1,s2;i<=n;i++)
        s1=g[i],s2=(s1-f[i]+mod)%mod,ans=(1ll*ans+qpow(s1,k)-qpow(s2,k)+mod)%mod;
        printf("%d",ans);
        return 0;
    }
    dfs(1,0);
    for(int j=0;j<=L;j++)g[id(1,j)]=1;
    dfs2(1,0);
    for(int i=1;i<=n;i++)ans=(ans+qpow(1ll*f[id(i,L)]*g[id(i,L)]%mod,k))%mod;
    if(L)for(int i=2;i<=n;i++)ans=(ans-qpow(1ll*f[id(i,L-1)]*(g[id(i,L)]+mod-1)%mod,k)+mod)%mod;
    printf("%d",ans);
}
View Code

 

原文地址:https://www.cnblogs.com/hfctf0210/p/10685432.html