欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

牛客网暑期ACM多校训练营(第一场

程序员文章站 2022-04-02 18:57:21
...

补得好辛苦QAQ,不过终于补完了。。
A Monotonic Matrix
A题博主是打表找出规律来的,稍后会把详解贴上来。
找出来的规律为:C(m+n,n)*C(m+n+1,n)/(n+1)
找到规律就好办了,直接预处理出阶乘及阶乘的逆元及1到maxn的逆元就行了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int mod=1e9+7;
long long tab1[maxn],tab2[maxn];

long long extgcd(long long a,long long b,long long &x,long long &y)
{
    if(b==0){x=1;y=0;return a;}
    long long d=extgcd(b,a%b,x,y);
    long long t=x;x=y;y=t-a/b*y;
    return d;
}

long long mod_reverse(long long a,long long p)
{
    long long x,y;
    long long d=extgcd(a,p,x,y);
    if(d==1)return (x%p+p)%p;
    return -1;
}

void init()
{
    tab1[1]=1;
    tab2[1]=mod_reverse(1,mod);
    for(int i=2;i<maxn;i++)
        tab1[i]=tab1[i-1]*i%mod,tab2[i]=mod_reverse(tab1[i],mod);
    tab2[0]=1;

}

int C(int a,int b)
{
    return 1LL*tab1[a]*tab2[b]%mod*tab2[a-b]%mod;
}

int inv[maxn];
void invTable(int n,int p)
{
    inv[1]=1;
    for(int i=2;i<=n;i++)
    {
        inv[i]=1LL*(p-p/i)*inv[p%i]%p;
    }
}

int main()
{
    init();
    invTable(1005,mod);
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        //cout<<C(m+n,n)<<endl;
        //cout<<C(m+n+1,n)<<endl;
        //cout<<inv[n+1]<<endl;
        printf("%lld\n",1LL*C(m+n,n)*C(m+n+1,n)%mod*inv[n+1]%mod);
    }
    return 0;

}

B Symmetric Matrix
B题我们很容易想到邻接矩阵,也就是每个点都连有两条边,可以有重边,但不能有自环。
我们设dp[n]为n个点能构成的方案数。
当我们在添加一个点进来时,如果只从中取一个点和它组成环,那么这个点有n-1种取法,剩下的n-2个点合法方案已经求出。
所以方案数为(n-1)*f(n-2)
推广到一般情况,当我们取k个点,剩下的点与新点组成环,旧点有C(n-1,k),剩下的点与新点组成的环的方案数为(n-1-k)!种,但考虑对称性需要除以2,又考虑只取一个点的时候不需要考虑对称性,所以把这种情况单独拿出来。当然也可以取出0个。
那么化简后的公式为:
f(n)=(n1)f(n2)+x=0n3(n1)!f(x)2x!

这里有个讨厌的求和,我们可以将其化为递推式。
g(n)=(n1)!2x=0n3f(x)x!
那么g(n+1)=n!2x=0n3f(x)x!+n(n1)f(n2)2
,再化简即可得到g(n+1)与g(n)的关系
所以g(n)=(n1)g(i1)+(n1)(n2)f(n3)2

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int f[maxn],g[maxn];
int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        if(m==1)
        {
            puts("0");
            continue;
        }
        f[1]=0;
        f[2]=1;
        f[3]=1;
        long long sum=1;
        g[3]=1;
        for(int i=4;i<=n;i++)
        {
            g[i]=(1LL*(i-1)*g[i-1]%m+1LL*(i-1)*(i-2)/2%m*f[i-3]%m)%m;
            f[i]=(1LL*(i-1)*f[i-2]%m+g[i])%m;
        }
        //cout<<g[4]<<endl;
        //+------cout<<f[4]<<endl;
        printf("%d\n",f[n]);
    }
    return 0;
}

D Two Graphs
由于n只有8,所以我们可以枚举所有的映射,根据所映射对应的点之间的边集来作为判断依据,这里注意不能用映射对应的点来作为判断依据,因为就算点选的是一样的,也可以选不同的边来称为同构图。

#include<bits/stdc++.h>
using namespace std;
set<vector<int>>S;
vector<int>V[10];
int mp2[10][10];
int bijection[10];
bool vis[10];
int n,m1,m2;

bool check()
{
    for(int i=1;i<=n;i++)if(V[i].size())
    {
        for(int j=0;j<V[i].size();j++)
        {
            int v=V[i][j];
            if(!mp2[bijection[i]][bijection[v]])
                return 0;
        }
    }
    return 1;
}

void solve()
{
    vector<int>tmp(m2);
    for(int i=0;i<m2;i++)tmp[i]=0;
    for(int i=1;i<=n;i++)if(V[i].size())
    {
        for(int j=0;j<V[i].size();j++)
        {
            int v=V[i][j];
            int idx=mp2[bijection[i]][bijection[v]];
            tmp[idx-1]=1;
        }
    }
    S.insert(tmp);
}


void dfs(int d)
{
    if(d==n+1)
    {
        if(check())
            solve();
        return;
    }
    for(int i=1;i<=n;i++)if(!vis[i])
    {
        vis[i]=1;
        bijection[d]=i;
        dfs(d+1);
        vis[i]=0;
    }
}
int main()
{

    while(scanf("%d %d %d",&n,&m1,&m2)==3)
    {
        memset(mp2,0,sizeof(mp2));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++)V[i].clear();
        S.clear();
        int u,v;
        for(int i=1;i<=m1;i++)
        {
            scanf("%d %d",&u,&v);
            V[u].push_back(v);
            V[v].push_back(u);
        }
        for(int i=1;i<=m2;i++)
        {
            scanf("%d %d",&u,&v);
            mp2[u][v]=mp2[v][u]=i;
        }

        dfs(1);
        printf("%d\n",S.size());
    }
    return 0;
}

E Removal
设dp[i][j] 为到i位置已经删除j个数的不同串个数,在转移时,我们可以枚举下一个数是k,那么就转移到了next[i][k] 这个位置,记忆化搜索就完事了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int mod=1e9+7;
int nxt[maxn][15];
int now[15];
int dp[maxn][15];
int n,m,k;
int a[maxn];
int dfs(int now,int d)
{
    if(d==m)return 1;
    if(now==n)
        return 0;
    if(dp[now][d]!=-1)return dp[now][d];
    int res=0;
    for(int i=1;i<=k;i++)
    {
        int nxtidx=nxt[now][i];
        if(nxtidx==-1)continue;
        int tmp=d+nxtidx-now-1;
        if(tmp>m)continue;
        res=(res+dfs(nxtidx,tmp))%mod;
    }
    if(d+n-now==m)res=(res+1)%mod;
    return dp[now][d]=res;
}

int main()
{

    while(~scanf("%d %d %d",&n,&m,&k))
    {
        memset(dp,-1,sizeof(dp));
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=k;i++)now[i]=-1;
        for(int i=n;i>=0;i--)
        {
            for(int j=1;j<=k;j++)
                nxt[i][j]=now[j];
            now[a[i]]=i;
        }
        dfs(0,0);
        printf("%d\n",dp[0][0]);
    }
}

F Sum of Maximum
这题想清楚后就会发现很显然,做法也十分套路。
每个数都有自己的最大值,首先我们将a排序,对于a[i-1]!=a[i],我们考虑一下a[i-1]+1到a[i]这个区间的每个数对答案的贡献。
设这个数为x,对于最大值小于x所有的数,它们可以随便取,那么它们提供的方案数为j=1i1aj,记为now。
对于最大值不小于x的那些数,他们只能在1到x中取,并且要保证至少有一个数为x。那么我们倒过来想就行了总方案数减一个都没有的方案数。也就是xni+1(x1)ni+1,记为f(x).
所以x对答案的贡献为now*f(x)*x.
那么这段区间对答案的贡献为
x=ai1+1ainowf(x)x.
化简为nowx=ai1+1aix(xni+1(x1)ni+1)
我们设f(x)=x(xni+1(x1)ni+1)
套路来了。我们观察这个式子,实际上它就是一个幂为n-i+2的多项式,我们想得到这个多项式的某个区间的和,而他的和的函数的最大幂肯定不超过n-i+2,所以我们可以枚举n-i+2个点,计算出n-i+2f(x) 个点出来,再利用拉格朗日插值把它的和的函数计算出来。这样就做出来了。
有空研究一下这个模板的奥秘,现在先贴出来吧。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;

namespace polysum
{
    #define rep(i,a,n) for (int i=a;i<n;i++)
    #define per(i,a,n) for (int i=n-1;i>=a;i--)
    const int D=2010;
    ll a[D],f[D],g[D],p[D],p1[D],p2[D],b[D],h[D][2],C[D];
    ll powmod(ll a,ll b){ll res=1;a%=mod;assert(b>=0);for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
    ll calcn(int d,ll *a,ll n) { // a[0].. a[d]  a[n]
        if (n<=d) return a[n];
        p1[0]=p2[0]=1;
        rep(i,0,d+1) {
            ll t=(n-i+mod)%mod;
            p1[i+1]=p1[i]*t%mod;
        }
        rep(i,0,d+1) {
            ll t=(n-d+i+mod)%mod;
            p2[i+1]=p2[i]*t%mod;
        }
        ll ans=0;
        rep(i,0,d+1) {
            ll t=g[i]*g[d-i]%mod*p1[i]%mod*p2[d-i]%mod*a[i]%mod;
            if ((d-i)&1) ans=(ans-t+mod)%mod;
            else ans=(ans+t)%mod;
        }
        return ans;
    }
    void init(int M) {
        f[0]=f[1]=g[0]=g[1]=1;
        rep(i,2,M+5) f[i]=f[i-1]*i%mod;
        g[M+4]=powmod(f[M+4],mod-2);
        per(i,1,M+4) g[i]=g[i+1]*(i+1)%mod;
    }
    ll polysum(ll m,ll *a,ll n) { // a[0].. a[m] \sum_{i=0}^{n-1} a[i]
        ll b[D];
        for(int i=0;i<=m;i++) b[i]=a[i];
        b[m+1]=calcn(m,b,m+1);
        rep(i,1,m+2) b[i]=(b[i-1]+b[i])%mod;
        return calcn(m+1,b,n-1);
    }
    ll qpolysum(ll R,ll n,ll *a,ll m) { // a[0].. a[m] \sum_{i=0}^{n-1} a[i]*R^i
        if (R==1) return polysum(n,a,m);
        a[m+1]=calcn(m,a,m+1);
        ll r=powmod(R,mod-2),p3=0,p4=0,c,ans;
        h[0][0]=0;h[0][1]=1;
        rep(i,1,m+2) {
            h[i][0]=(h[i-1][0]+a[i-1])*r%mod;
            h[i][1]=h[i-1][1]*r%mod;
        }
        rep(i,0,m+2) {
            ll t=g[i]*g[m+1-i]%mod;
            if (i&1) p3=((p3-h[i][0]*t)%mod+mod)%mod,p4=((p4-h[i][1]*t)%mod+mod)%mod;
            else p3=(p3+h[i][0]*t)%mod,p4=(p4+h[i][1]*t)%mod;
        }
        c=powmod(p4,mod-2)*(mod-p3)%mod;
        rep(i,0,m+2) h[i][0]=(h[i][0]+h[i][1]*c)%mod;
        rep(i,0,m+2) C[i]=h[i][0];
        ans=(calcn(m,C,n)*powmod(R,n)-c)%mod;
        if (ans<0) ans+=mod;
        return ans;
    }

}


ll pow2(ll a,ll b)
{
    ll res=1;
    while(b)
    {
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int main()
{
    int t,n,i,j;
    ll ans,a[1111],now,b[1111];
    polysum::init(1010);
    while(~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
        ans=0;
        sort(a+1,a+1+n);
        a[0]=0;
        now=1;
        for(int i=1;i<=n;i++)
        {
            if(a[i]==a[i-1])
            {
                (now*=a[i])%=mod;
                continue;
            }
            b[0]=0;
            for(j=1;j<=n-i+1;j++)
                b[j]=1LL*j*(((pow2(j,n-i+1)-pow2(j-1,n-i+1)%mod)+mod)%mod)%mod;
            ll tmp=((polysum::polysum(n-i+1,b,a[i]+1)-polysum::polysum(n-i+1,b,a[i-1]+1))%mod+mod)%mod;
            (ans+=tmp*now%mod)%=mod;
            (now*=a[i])%=mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

H Longest Path
这题重新让我学习到了斜率dp。
首先我们考虑往下走可以得到的最大值(包括自己连向父亲的边),直接dfs即可得到,记为down(i)
up(i)表示该点往上再往下可以得到的最大值,从上往下更新。
up(u)=max(up(fa)+(e(u)e(fa))2)
它也可以往上走到父亲再往下走到其他的兄弟结点。
那么up(u)=max(down(u)+(e(u)e(u)2))
重点就是如何快速得到与父亲相同的点中的最大值,看这个式子很容易想到斜率优化,首先我们将父亲是同一个的点放进一个数组里面,按照它们的边排序,再斜率优化从左到右搞一次,从右到左搞一次就行了。
为啥博主搞错了呢,从左到右由于是最大值,那么是维护一个上凸包,并且斜率是递增的(也就是那个常数),所以减的方向实际是tail,不是front。
而从右到左是最小值,那么维护一个下凸包,而斜率是递减的,所以减的方向还是tail。
所以不要想当然的套模板啊。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
vector<pair<int,int> >G[maxn];
typedef pair<int,int> pii;

long long pow2(long long a)
{
    return a*a;
}
int c[maxn];
long long down[maxn];
void dfs1(int u,int pre)
{
    for(pii x:G[u])if(x.first!=pre)
    {
        int v=x.first,w=x.second;
        c[v]=w;
        dfs1(v,u);
        if(u!=1)down[u]=max(down[u],down[v]+pow2(c[v]-c[u]));
    }
}

long long up[maxn];
int p[maxn];

long long getup(int j,int k)
{
    j=p[j],k=p[k];
    return down[j]+pow2(1LL*c[j])-down[k]-pow2(c[k]);
}

long long getdown(int j,int k)
{
    j=p[j],k=p[k];
    return 2LL*(c[j]-c[k]);
}

long long Cal(int idx,int i)
{
    if(idx==0)return 0;
    idx=p[idx];
    i=p[i];
    return 1LL*down[idx]+pow2(c[i]-c[idx]);
}

bool cmp1(int a,int b)
{
    return c[a]<c[b];
}
int Q[maxn];

void solve(int n)
{
    sort(p+1,p+1+n,cmp1);
    int front=1,tail=0;
    Q[0]=0;
    for(int i=1;i<=n;i++)
    {
        while(front<tail&&getup(Q[tail],Q[tail-1])<getdown(Q[tail],Q[tail-1])*c[p[i]])tail--;
        int idx=Q[tail];
        up[p[i]]=max(up[p[i]],Cal(idx,i));
        while(front<tail&&getup(i,Q[tail])*getdown(Q[tail],Q[tail-1])>getup(Q[tail],Q[tail-1])*getdown(i,Q[tail]))tail--;
        Q[++tail]=i;
    }
    front=1,tail=0;
    Q[0]=0;
    for(int i=n;i>=1;i--)
    {
        while(front<tail&&getup(Q[tail],Q[tail-1])<getdown(Q[tail],Q[tail-1])*c[p[i]])tail--;
        int idx=Q[tail];
        up[p[i]]=max(up[p[i]],Cal(idx,i));
        while(front<tail&&getup(i,Q[tail])*getdown(Q[tail],Q[tail-1])<getup(Q[tail],Q[tail-1])*getdown(i,Q[tail]))tail--;
        Q[++tail]=i;
    }
}
long long ans[maxn];
void dfs2(int u,int pre)
{
    int k=0;
    if(u!=1&&pre!=1)
        up[u]=max(up[u],up[pre]+pow2(c[u]-c[pre]));
    for(pii x:G[u])if(x.first!=pre)
    {
        int v=x.first,w=x.second;
        p[++k]=v;
    }
    solve(k);
    ans[u]=up[u];
    for(pii x:G[u])if(x.first!=pre)
    {
        int v=x.first,w=x.second;
        ans[u]=max(ans[u],down[v]);
        dfs2(v,u);
    }

}

int main()
{
    int N;
    while(~scanf("%d",&N))
    {
        for(int i=1;i<=N;i++)G[i].clear();
        memset(up,0,sizeof(up));
        memset(down,0,sizeof(down));
        memset(ans,0,sizeof(ans));
        int u,v,c;
        for(int i=1;i<N;i++)
        {
            scanf("%d %d %d",&u,&v,&c);
            G[u].push_back(make_pair(v,c));
            G[v].push_back(make_pair(u,c));
        }
        dfs1(1,0);
        dfs2(1,0);
        for(int i=1;i<=N;i++)
            printf("%lld\n",ans[i]);
    }
    return 0;
}

I Substring
求本质不同子串个数。
什么叫本质不同的子串,ab,ac,bc…都是本质相同的子串。我们试想一个串求不同串时,ab,ac都会被算一次,而实际上他们本质是相同的,只是位置上的字符不同罢了。所以我们不妨将abc所有的映射都预处理出来(6个),将原字符串映射成6份,那么ab,ac,bc,ba,ca,cb都会被算进去,再除以6就相当于只算了一次了。
但aa这种只出现一种字符的字符串在这种处理下,只会变成aa,bb,cc三种,所以我们要找到映射后的不同子串个数ans1,和最长的只出现一种字符串的长度ans2,那么答案即为(ans1-3*ans2)/6+ans2.

#include<bits/stdc++.h>
using namespace std;
#define rank rk
const int maxn=50005*6+100;
int ch[maxn];
int cntA[maxn],cntB[maxn],A[maxn],B[maxn],tsa[maxn],SA[maxn],rank[maxn],height[maxn];
int N;
void get_SA()
{
    for(int i=0;i<=200;i++)cntA[i]=0;
    for(int i=1;i<=N;i++)cntA[ch[i]]++;
    for(int i=1;i<=200;i++)cntA[i]+=cntA[i-1];
    for(int i=N;i>=1;i--)SA[cntA[ch[i]]--]=i;
    rank[SA[1]]=1;
    for(int i=2;i<=N;i++)
    {
        rank[SA[i]]=rank[SA[i-1]];
        if(ch[SA[i]]!=ch[SA[i-1]])
            rank[SA[i]]++;
    }

    //cout<<endl;
    for(int step=1;rank[SA[N]]<N;step<<=1)
    {
        for(int i=1;i<=N;i++)cntA[i]=cntB[i]=0;
        for(int i=1;i<=N;i++)
        {
            cntA[A[i]=rank[i]]++;
            cntB[B[i]=(i+step<=N)?rank[i+step]:0]++;
        }
        for(int i=1;i<=N;i++)cntA[i]+=cntA[i-1],cntB[i]+=cntB[i-1];
        for(int i=N;i>=1;i--)tsa[cntB[B[i]]--]=i;
        for(int i=N;i>=1;i--)SA[cntA[A[tsa[i]]]--]=tsa[i];
        rank[SA[1]]=1;
        for(int i=2;i<=N;i++)
        {
            rank[SA[i]]=rank[SA[i-1]];
            if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])
                rank[SA[i]]++;
        }
    }
}

void get_Height()
{
    int i,j,k=0;
    for(int i=1; i<=N; i++)
    {
        if(k)k--;
        j=SA[rank[i]-1];
        while(ch[i+k]==ch[j+k])k++;
        height[rank[i]]=k;
    }
}
int bij[7][4];
void init()
{
    int cas=0;
    for(int j=0; j<3; j++)
        for(int p=0; p<3; p++)if(p!=j)
                for(int q=0; q<3; q++)if(p!=q&&q!=j)
                    {
                        cas++;
                        bij[cas][0]=j;
                        bij[cas][1]=p;
                        bij[cas][2]=q;
                    }
}
char s[50005];
void getch()
{
    int len=strlen(s+1);
    for(int i=1;i<=6;i++)
    {
        for(int j=1;j<=len;j++)
            ch[++N]=bij[i][s[j]-'a'];
        ch[++N]=3;

    }
    //cout<<endl;
}
int n;
long long get1()
{
    int pre=0;
    long long ans=0;
    //cout<<height[1]<<endl;
    for(int i=1;i<=N;i++)
    {
        ans=ans+n-(SA[i]-1)%(n+1)-min({height[i],pre,n-(SA[i]-1)%(n+1)});
        //cout<<SA[i]<<endl;
        pre=n-(SA[i]-1)%(n+1);
    }

    return ans;
}

long long get2()
{
    long long res=0;
    long long cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(s[i]!=s[i-1])
        {
            res=max(res,cnt);
            cnt=1;
        }
        else
            cnt++;
    }
    res=max(res,cnt);
    return res;
}

int main()
{

    init();
    while(~scanf("%d",&n))
    {
        ch[0]=3;
        N=0;
        scanf("%s",s+1);
        getch();
        get_SA();
        get_Height();
        long long ans1=get1();
        long long ans2=get2();
        //cout<<ans1<<endl;
        //cout<<ans2<<endl;
        printf("%lld\n",(ans1-ans2*3)/6+ans2);
    }
}

J Different Integers
签到题没啥好说的,莫队就完事了。

#include<bits/stdc++.h>
using namespace std;
int len;
const int maxn=1e5+5;
struct Query
{
    int L,R,block;
    int idx;
    Query(int l,int r,int i):L(l),R(r),idx(i)
    {
        block=L/len;
    }
    Query(){}
    bool operator<(const Query &b)const
    {
        if(block!=b.block)return block<b.block;
        return R<b.R;
    }
};

Query q[maxn];
int ans[maxn];
int times[maxn];
int havenum;
int a[maxn];

void insert(int num)
{
    times[num]++;
    if(times[num]==1)havenum++;
}

void erase(int num)
{
    times[num]--;
    if(times[num]==0)havenum--;
}

int main()
{
    int N,Q;
    while(~scanf("%d %d",&N,&Q))
    {
        havenum=0;
        memset(times,0,sizeof(times));
        len=(int)sqrt(N);
        for(int i=1;i<=N;i++)scanf("%d",&a[i]);
        int l,r;
        for(int i=1;i<=Q;i++)
        {
            scanf("%d %d",&l,&r);
            q[i]=Query(l,r,i);
        }
        sort(q+1,q+1+Q);
        int L=1,R=1;
        for(int i=1;i<=N;i++)
            insert(a[i]);
        insert(a[1]);
        for(int i=1;i<=Q;i++)
        {
            Query tmp=q[i];
            while(R<tmp.R)erase(a[R++]);
            while(L>tmp.L)erase(a[L--]);
            while(R>tmp.R)insert(a[--R]);
            while(L<tmp.L)insert(a[++L]);
            ans[tmp.idx]=havenum;
        }
        for(int i=1;i<=Q;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

补难题好爽,也好烦。继续加油吧。

相关标签: 牛客多校