2021牛客寒假算法基础集训营1

A:串 (DP)

  题目大意是有多少种长度不超过 n n n的字符串能包含一个子序列“us”。这道题一开始我的想法是用dp的方式表示①包括"us"的子串;②只包括“u”不包括“s”的子串;③不包括“u”的子串。但是发现这三种情况的和不等于 2 6 n 26^n 26n,原因在于第二种情况遗漏了"s"在“u”前面的情况(既没有形成“us”,也包括了“u”)。因此我们把第二种情况更改成没有包括“us”但包括了"u"
  我们令 d p [ i ] [ 0 / 1 / 2 ] dp[i][0/1/2] dp[i][0/1/2]对应三种情况,则 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] ∗ 26 + d p [ i − 1 ] [ 1 ] , d p [ i ] [ 1 ] = 2 6 i − d p [ i ] [ 0 ] − d p [ i ] [ 2 ] , d p [ i ] [ 2 ] = 2 5 i dp[i][0]=dp[i-1][0]*26+dp[i-1][1],dp[i][1]=26^i-dp[i][0]-dp[i][2],dp[i][2]=25^i dp[i][0]=dp[i1][0]26+dp[i1][1],dp[i][1]=26idp[i][0]dp[i][2],dp[i][2]=25i。补充一个转移时遇到的错误: d p [ i ] [ 0 ] dp[i][0] dp[i][0]多加了一项 d p [ i − 2 ] [ 2 ] dp[i-2][2] dp[i2][2](意为直接在后面补"us")。这样的错误在于我们能从情况②中找到第 i − 1 i-1 i1位是“u”,前面跟 d p [ i − 2 ] [ 2 ] dp[i-2][2] dp[i2][2]的排列是一样的字符串,造成重复计数。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e6+100;
const int mod=1e9+7;
typedef long long ll;
ll dp[maxn][3];
int main()
{
    
    
    close;int n;cin>>n;
    dp[1][1]=1;dp[1][2]=25;
    dp[2][0]=1;dp[2][1]=50;dp[2][2]=625;
    ll sum=1,mul=26*26;
    for(int i=3;i<=n;++i)
    {
    
    
        mul=mul*26%mod;
        dp[i][0]=(dp[i-1][0]*26%mod+dp[i-1][1])%mod;
        dp[i][2]=dp[i-1][2]*25%mod;
        dp[i][1]=((mul-dp[i][0]-dp[i][2])%mod+mod)%mod;
        sum=(sum+dp[i][0])%mod;
    }
    cout<<sum<<endl;
}

B:括号 (构造)

  题目大意是构造一个非空的仅仅包括’(‘和’)‘的括号字符串,包含正好 k k k个不同合法括号对,但构造的括号字符串的长度不能超过1e5。
  我的想法是:假设我们构造一个类似于"()()()()…",那么里面的合法括号对的个数就是1+2+3+…;假设我们现在有5e4对括号,那么应该有(5e4-1)*5e4/2>1e9,因此这样的构造不会超出长度限制。但是1~x的和不一定恰好是 k k k,我们只需要找到一个和 s u m sum sum不超过 k k k的最大的 x x x,那么一定有 k − s u m ≤ x k-sum\le x ksumx,我们只要多加一个’)'就能实现。注意特判0的情况(要求构造的括号序列非空)

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e5+100;
int num[maxn];
int main()
{
    
    
    close;int n;cin>>n;
    for(int i=1;n>0;++i)
    {
    
    
        if(n>=i) n-=i,num[i]++;
        else {
    
    num[n]++;break;}
    }
    for(int i=1;num[i]!=0;++i)
    {
    
    
        if(num[i]==1) cout<<"()";
        else if(num[i]==2) cout<<"())";
    }
    if(n==0) cout<<")";
}

  官方提供的题解是找到 p = k p=\sqrt {k} p=k ,令 a , b a,b a,b满足 k = p ∗ a + b ( b < p ) k=p*a+b(b<p) k=pa+b(b<p).那么我们可以构造 p p p对左括号和 a a a对右括号,并在第 b b b个左括号的右边插入一个右括号。这样的构造也能满足题意。

C:红和蓝(DFS)

  题目大意是给定一棵树,每个结点只能被染成红色或蓝色,同时要求对于结点 x x x,与他相邻的结点中有且仅有一个与其染成相同颜色的结点,问能否找到一个合法的染色方式。
  很明显我们能发现叶子结点是非常特殊的结点,因为叶子结点的颜色一定和他的父结点的颜色相同。我们的做法是:两遍DFS,第一遍将结点两两分组,叶子结点或者没分组的结点都和他的父亲结点分为一组(根据递归性质,子结点已经分完组,如果没有给父亲结点染上颜色,父亲结点只能跟其父亲结点分为一组);第二遍DFS在确定了能够有合法染色方案的前提上,再次进行染色。这里指出一个自己编程的错误:不能对叶子结点直接染一种颜色然后往回判断!例如数据“4 1 2 2 3 1 4”.

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
const int maxn=1e5+100;
using namespace std;
vector<int> v[maxn];
int group[maxn],color[maxn],cnt=0;bool ok=true;
void DFS1(int root,int fa)
{
    
    
    int son=0;
    for(int i=0;i<v[root].size();++i)
    {
    
    
        if(v[root][i]==fa) continue;
        else son++,DFS1(v[root][i],root);  
    }
    if(son==0 || group[root]==0)
    {
    
    
        if(group[fa]!=0) ok=false;
        else group[root]=group[fa]=++cnt;
    }
}
void DFS2(int root,int fa)
{
    
    
    for(int i=0;i<v[root].size();++i)
    {
    
    
        if(v[root][i]==fa) continue;
        else if(group[v[root][i]]==group[root]) color[v[root][i]]=color[root];
        else color[v[root][i]]=(1^color[root]);
        DFS2(v[root][i],root);
    }
}
int main()
{
    
    
    close;int n;cin>>n;
    for(int i=1;i<n;++i)
    {
    
    
        int x,y;cin>>x>>y;
        v[x].push_back(y);v[y].push_back(x);
    }
    DFS1(1,0);
    if(!ok || group[0]) {
    
    cout<<-1<<endl;return 0;}
    color[1]=1;DFS2(1,0);
    for(int i=1;i<=n;++i) cout<<(color[i]?'R':'B');
}

D:点一成零(连通块 并查集)

  题目大意是给定一个 n ∗ n n*n nn的01矩阵,然后有 k k k次操作,每次操作后都问你如何将全部的1变成0。这里变化的方案是,你选择了数字1连通块中的一个1变成0,就可以将整个连通块中的1都变成0。
  很明显这个题的答案应该是 n ! ∗ ∏ i = 1 n s i z e ( i ) n!*\prod_{i=1}^n size(i) n!i=1nsize(i),其中 n n n表示连通块的个数, s i z e ( i ) size(i) size(i)表示第 i i i个连通块的大小。很明显我们可以用并查集去维护连通块的个数及大小,这里放一下自己遇到的坑点:①数组有没有越界;②周围要合并的会不会本身就是一个连通块;③使用num数组表示当前连通块的大小是一定要先find_set()找到根节点。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=500*500+100;
const int mod=1e9+7;
ll fact[maxn];int a[510][510],s[maxn],num[maxn];
ll inv(ll base,ll x)
{
    
    
    ll ans=1;
    while(x) {
    
    if(x&1)ans=ans*base%mod;base=base*base%mod;x>>=1;}
    return ans;
}
void prepare(){
    
    fact[1]=1;for(int i=2;i<maxn;++i) fact[i]=fact[i-1]*i%mod;}
void init_set(){
    
    for(int i=1;i<maxn;++i)  s[i]=i,num[i]=1;}
int find_set(int x){
    
    if(x!=s[x]) s[x]=find_set(s[x]);return s[x];}
void union_set(int x,int y){
    
    x=find_set(x);y=find_set(y);if(x!=y) s[x]=s[y],num[y]+=num[x];}
ll solve(int org,int now) {
    
    ll rec=num[find_set(org)];union_set(org,now);return rec;}
int main()
{
    
    
    int n;scanf("%d",&n);
    for(int i=0;i<n;++i)
    {
    
    
        getchar();
        for(int j=0;j<n;++j)
        {
    
    char c=getchar();a[i][j]=c-'0';}
    }
    prepare();
    init_set();
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            if(a[i][j]==1){
    
    
                if(i-1>=0 && a[i-1][j]==1) union_set(i*n+j+1,(i-1)*n+j+1);
                if(j-1>=0 && a[i][j-1]==1) union_set(i*n+j+1,i*n+j);
            }
    ll tot=0,ans=1;
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            if(a[i][j]==1 && find_set(i*n+j+1)==i*n+j+1) tot++,ans=ans*num[find_set(i*n+j+1)]%mod;
    int k;scanf("%d",&k);
    for(int i=1;i<=k;++i)
    {
    
    
        int x,y,rec=1;scanf("%d%d",&x,&y);
        if(a[x][y]==1) {
    
    printf("%lld\n",ans*fact[tot]%mod);continue;}
        ll tmp=1;num[x*n+y+1]=1;a[x][y]=1;
        if(x-1>=0 && a[x-1][y]==1 && find_set((x-1)*n+y+1)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve((x-1)*n+y+1,x*n+y+1)%mod;
        if(x+1<n && a[x+1][y]==1 && find_set((x+1)*n+y+1)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve((x+1)*n+y+1,x*n+y+1)%mod;
        if(y-1>=0 && a[x][y-1]==1 && find_set(x*n+y)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve(x*n+y,x*n+y+1)%mod;
        if(y+1<n && a[x][y+1]==1 && find_set(x*n+y+2)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve(x*n+y+2,x*n+y+1)%mod;
        tot+=rec;ans=ans*inv(tmp,mod-2)%mod*num[find_set(x*n+y+1)]%mod;
        printf("%lld\n",ans*fact[tot]%mod);
    }
}

E:三棱锥之刻(计算几何)

  题目大意就是求解圆心在正三棱锥的中心,半径为 r r r的球与正棱锥的交面积。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-3;
const double PI=acos(-1);
int main()
{
    
    
    double a,r;scanf("%lf%lf",&a,&r);
    if(sqrt(6)*a/12-r>=-eps) printf("0.00000");
    else if(sqrt(2)*a/4-r>=-eps) printf("%.5f",4*PI*(r*r-a*a/24));
    else if(sqrt(6)*a/4-r>=eps){
    
    
        double r1=sqrt(r*r-a*a/24);
        double r2=sqrt(r1*r1-a*a/12);
        printf("%.5f",4*(PI*r1*r1-acos(sqrt(3)*a/(6*r1))*3*r1*r1+r2*a*sqrt(3)/2));
    }
    else printf("%.5f",sqrt(3)*a*a);
}

F:对答案一时爽(思维)

  得分之和最小就是两个人的答案全是错误的;得分之和最大就是一个人的答案是全部正确的,那么同时另一个人和他作答相同的题目也会得分。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    
    
    int n,sum=0;cin>>n;
    string A,B;
    getchar();getline(cin,A);getline(cin,B);
    for(int i=0;i<n*2;i+=2) if(A[i]==B[i]) sum++;
    cout<<n+sum<<' '<<0;
}

G:好玩的数字游戏(大模拟)

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int a[10][10],tmp[10][10];
typedef long long ll;
ll x,p,q;
long long f(long long x,int p){
    
     
    long long r=(x%p)*(x%p)/10%p; 
    return (r^(1LL<<17)^(1LL<<57))%p; 
} 
int solve(char op,int loc)
{
    
    
    if(op=='D'){
    
    
        int score=0;
        vector<int> before,after;before.clear();after.clear();
        for(int i=4;i>0;--i) if(a[loc][i]!=0) before.push_back(a[loc][i]);
        int size=before.size();
        if(size==0) return 0;
        else{
    
    
            int l=0,r=1;
            while(r<size) 
            {
    
    
                if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
                else after.push_back(before[l]),l++,r++;
            }
            if(l<size) after.push_back(before[l]);
            int cur=after.size(),p=0;
            for(int i=4;i>0;--i) a[loc][i]=(p<cur)?after[p++]:0;
            return score;
        }
    }
    else if(op=='A'){
    
    
        int score=0;
        vector<int> before,after;before.clear();after.clear();
        for(int i=1;i<=4;++i) if(a[loc][i]!=0) before.push_back(a[loc][i]);
        int size=before.size();
        if(size==0) return 0;
        else{
    
    
            int l=0,r=1;
            while(r<size) 
            {
    
    
                if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
                else after.push_back(before[l]),l++,r++;
            }
            if(l<size) after.push_back(before[l]);
            int cur=after.size(),p=0;
            for(int i=1;i<=4;++i) a[loc][i]=(p<cur)?after[p++]:0;
            return score;
        }
    }
    else if(op=='W'){
    
    
        int score=0;
        vector<int> before,after;before.clear();after.clear();
        for(int i=1;i<=4;++i) if(a[i][loc]!=0) before.push_back(a[i][loc]);
        int size=before.size();
        if(size==0) return 0;
        else{
    
    
            int l=0,r=1;
            while(r<size) 
            {
    
    
                if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
                else after.push_back(before[l]),l++,r++;
            }
            if(l<size) after.push_back(before[l]);
            int cur=after.size(),p=0;
            for(int i=1;i<=4;++i) a[i][loc]=(p<cur)?after[p++]:0;
            return score;
        }
    }
    else{
    
    
        int score=0;
        vector<int> before,after;before.clear();after.clear();
        for(int i=4;i>0;--i) if(a[i][loc]!=0) before.push_back(a[i][loc]);
        int size=before.size();
        if(size==0) return 0;
        else{
    
    
            int l=0,r=1;
            while(r<size) 
            {
    
    
                if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
                else after.push_back(before[l]),l++,r++;
            }
            if(l<size) after.push_back(before[l]);
            int cur=after.size(),p=0;
            for(int i=4;i>0;--i) a[i][loc]=(p<cur)?after[p++]:0;
            return score;
        }
    }
}
bool change(char op,int loc)
{
    
    
    if(op=='D' || op=='A'){
    
    
        for(int i=1;i<=4;++i) if(a[loc][i]!=tmp[loc][i]) return true;
        return false;
    }
    else{
    
    
        for(int i=1;i<=4;++i) if(a[i][loc]!=tmp[i][loc]) return true;
        return false;
    }
}
void get_newboard()
{
    
    
    bool ok=true;
    while(ok){
    
    
        int tmp=x%16,cur_x=tmp/4+1,cur_y=tmp%4+1;
        if(a[cur_x][cur_y]==0) ok=false,a[cur_x][cur_y]=2;
        x=f(x,p);
    }
}
bool Game_over()
{
    
    
    for(int i=1;i<=4;++i) 
        for(int j=1;j<=3;++j)
            if(a[i][j]==0 || a[i][j+1]==0 || a[i][j]==a[i][j+1] || a[j][i]==0 || a[j+1][i]==0 || a[j][i]==a[j+1][i]) return true;
    return false;
}
void get_sameboard(){
    
    for(int i=1;i<=4;++i) for(int j=1;j<=4;++j) tmp[i][j]=a[i][j];}
int main()
{
    
    
    close;cin>>x>>p>>q;
    for(int i=1;i<=4;++i)
        for(int j=1;j<=4;++j)
            cin>>a[i][j],tmp[i][j]=a[i][j];
    string op;cin>>op;
    ll ans=0,lastpos=-1;
    for(int i=0;i<q;++i)
    {
    
    
        ans+=solve(op[i*2],op[i*2+1]-'0');
        if(change(op[i*2],op[i*2+1]-'0')) get_newboard(),get_sameboard();
        if(!Game_over()) {
    
    lastpos=i+1;break;}
    }
    if(lastpos==-1) cout<<ans<<endl<<"never die!";
    else cout<<ans<<endl<<lastpos;
}

H:幂塔个位数的计算

  题目大意就是让你求幂塔的个位数,但是需要注意的是 1 ≤ n ≤ 1 0 100000 1\le n\le 10^{100000} 1n10100000
  需要掌握的一个非常重要的定理:欧拉降幂: a b ≡ {   a b % Φ ( p ) g c d ( a , p ) = 1 a b g c d ( a , p ) ≠ 1 , b < Φ ( p ) ( m o d p ) a b % Φ ( p ) + Φ ( p ) g c d ( a , p ) ≠ 1 , b ≥ Φ ( p ) a^b\equiv \begin{cases} \ a^{b\%\Phi (p)} & gcd(a,p)=1\\ a^{b} & gcd(a,p)\ne 1,b<\Phi(p) & (mod p)\\ a^{b\% \Phi(p)+\Phi(p)} & gcd(a,p)\ne 1,b\ge \Phi(p)\\ \end{cases} ab ab%Φ(p)abab%Φ(p)+Φ(p)gcd(a,p)=1gcd(a,p)=1,b<Φ(p)gcd(a,p)=1,bΦ(p)(modp)我们在不知道a,p是否互质的时候,可以直接使用第三个公式。同时降幂的过程中对于 m o d = 10 mod=10 mod=10来说只需要降4次就能到达1,所以效率还是很高的。

def phi(n):
    if(n==10):return 4
    elif(n==4):return 2
    else:return 1
   
def eular(a,n,mod):
    if(n==1): return a%mod
    elif(mod==1): return 0
    else:
        e=eular(a,n-1,phi(mod))
        return pow(a,e+phi(mod),mod)

a=int(input())
n=int(input())
if(n==1):print(a%10)
else:print(eular(a,n,10))

I:限制不互素对的排列(构造)

  题目大意就是重新排列1~n这些数字,使得一共有 k k k对相邻的数字满足他们的gcd大于1。
  这个题因为限制了 0 ≤ k ≤ n / 2 0\le k \le n/2 0kn/2,我们能够使用常见的结论:相邻两个偶数的gcd一定大于1,相邻两个奇数的gcd一定等于1 n n n个数有 ⌊ n / 2 ⌋ \lfloor n/2 \rfloor n/2个偶数,最多有 ⌊ n / 2 ⌋ − 1 \lfloor n/2 \rfloor -1 n/21对数满足题意,如果 k = = n / 2 k==n/2 k==n/2,这时候我们就根据最后一个偶数能不能被3整除,补上一个单独的3或者补上一对3 9来凑齐。注意某些情况的特判。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e5+100;
int vis[maxn];
int main()
{
    
    
    close;int n,k,num=0,last;cin>>n>>k;
    if(n<6 && k==(n/2)) {
    
    cout<<-1<<endl;}
    else if(n==6 && k==3) {
    
    cout<<"2 4 6 3 1 5"<<endl;}
    else if(n==7 && k==3) {
    
    cout<<"2 4 6 3 1 5 7"<<endl;}
    else if(n==8 && k==4) {
    
    cout<<"2 4 8 6 3 1 5 7"<<endl;}
    else{
    
    
        cout<<2;vis[2]=1;
        for(int i=4;i<=n&&num<k;i+=2)
            cout<<' '<<i,num++,vis[i]=1,last=i;
        if(num<k){
    
    
            if(last%3==0) cout<<' '<<3,vis[3]=1;
            else cout<<' '<<3<<' '<<9,vis[3]=vis[9]=1;
        } 
        for(int i=1;i<=n;++i) if(!vis[i]) cout<<' '<<i,vis[i]=1;
    }
}

J:一群小青蛙呱蹦呱蹦呱(数论)

  题目大意是无穷多只青蛙按照 1 , p ( i ) , p ( i ) 2 , p ( i ) 3 . . . 1,p(i),p(i)^2,p(i)^3... 1,p(i),p(i)2,p(i)3...跳跃,其中 p ( i ) p(i) p(i)表示第 i i i个质数。问最终1~n中所有青蛙跳不到的数字的lcm是多少(由于答案很大,要对1e9+7取模)。
  这个题很容易发现青蛙跳不到的数字就是素因素个数超过1个的合数,再根据lcm的定义, l c m = ∏ i = 1 t o t p ( i ) m a x ( c n t 1 , c n t 2 , . . . ) lcm=\prod_{i=1}^{tot}p(i)^{max(cnt_1,cnt_2,...)} lcm=i=1totp(i)max(cnt1,cnt2,...),其中 c n t cnt cnt代表上述合数中该素数因子的出现次数, t o t tot tot表示1~n中所有的质数个数。进一步发现, p ( i ) = = 2 p(i)==2 p(i)==2时, c n t m a x = m a x { k ∣ 2 k ∗ 3 ≤ n } cnt_{max}=max\{k|2^k*3\le n\} cntmax=max{ k2k3n};否则, c n t m a x = m a x { k ∣ p ( i ) k ∗ 2 ≤ n } cnt_{max}=max\{k|p(i)^k*2\le n\} cntmax=max{ kp(i)k2n}。我们去计算各个素数最大的 c n t m a x cnt_{max} cntmax,用循环的方式去寻找,根据素数个数定理,大约是 n / l n ( n ) n/ln(n) n/ln(n)个素数,同时根据调和级数,时间复杂度近似线性。同时找质数用欧拉筛,时间复杂度也是 O ( n ) O(n) O(n)。最后注意特判即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn=8e7+100;
const int mod=1e9+7;
bool u[maxn];int su[maxn],num=0;
typedef long long ll;
void prepare(int n)
{
    
    
    memset(u,true,sizeof(u));
    for(int i=2;i<=n;++i)
    {
    
    
        if(u[i]) su[++num]=i;
        for(int j=1;j<=num;++j)
        {
    
    
            if(i*su[j]>n) break;
            u[i*su[j]]=false;
            if(i%su[j]==0) break;
        }
    }
}
int main()
{
    
    
    int n;cin>>n;
    if(n<=5) {
    
    cout<<"empty"<<endl;return 0;} 
    prepare(n/2);ll ans=1;
    for(int i=1;i<=num;++i)
    {
    
    
        ll bound=(su[i]==2?n/3:n/2),cur=1;
        while(cur<=bound) cur*=su[i];
        cur/=su[i];
        ans=ans*cur%mod;
    }
    cout<<ans;
}

猜你喜欢

转载自blog.csdn.net/CUMT_William_Liu/article/details/113621314