【2018 9th C++】 蓝桥杯决赛 (1.) 换零钞 (2.) 激光样式 (3.) 格雷码 (4.) 调手表 (5.) 搭积木 (6.) 矩阵求和

题意

钞票一共有(100、5、2、1)四种,现在有(2)(100)元的钞票,去换零钱,要换出的零钱中(2)元的是(1)元的(10)倍,剩下的都是(5)元面额的

数据范围

题解

换的钱张数最少,即(1)元的最少,枚举(1)元的数目即可,然后得到(2)元的数目,最后判断一下剩余的金钱数能不能用(5)元的凑出来

Code

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    int one=0;
    int res=0;
    for(int i=1;i<=200;i++)
    {
        if((200-i*2*10-i)%5==0)
        {
            one=i;
            res=200-(i*2*10+i);
            break;
        }    
    }
    int ans;
    ans=one+10*one;
    ans=ans+res/5;
    printf("%d
",ans);
}

(2.) 激光样式

题意

(30)个灯,相邻的两个不能够同时亮,问一共能够有多少种不同的打开方式,全部关闭也算是一种

数据范围

(1leq nleq 30)

题解

暴力搜索所有可能的状态,全为(0)也算是一种

Code

#include<iostream>
using namespace std;
bool st[35];
int ans=0;
void dfs(int u,int pre){
    if(u==31){
        ans++;
        return;
    }
    if(pre==0){
        dfs(u+1,1);
    }
    dfs(u+1,0);
}
int main(){
    dfs(1,0);
    cout<<ans<<endl;
}

(3.) 格雷码

题意

代码填空,实现对一个数(x)求出其二进制下最右边的一个(1),并将其左边的二进制数字进行改变

数据范围

题解

(lowbit)操作能求出最右边的1,低位补(0)后的数字,将其左移一位置,即在后面加一个(0),求得的数字即当前最右边的(1)左边数字对应的数字为(1)的值,将原值与其异或后就可以求得改变后的数字

Code

x ^ (x&(-x)<<1)

(4.) 调手表

题意

给定(n)进制的手表,和一个(k),在任意时刻,有两个操作可以进行,(+1)或者(+k),手表只能显示(0sim n-1)的数字,
在最优策略下,求出从任意时间到达其他任意时间的操作数的最大值

数据范围

(0< k < n < 100000)

题解

因为是(mod)循环,可以看做是从(1)(n-1)的所有最优方案,最小步数模型,(bfs)即可,所有步数的最大值即可。

Code

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<=n;i++)
const int N=1e5+10;
int n,k;
int ans;
int dist[N];
void bfs(){
    memset(dist,-1,sizeof dist);
    queue< int > q;
    q.push(0);
    dist[0]=0;
    while(q.size()){
        int t=q.front();
        q.pop();

        int tx=(t+1)%n,ty=(t+k)%n;

        if(dist[tx]==-1){
            dist[tx]=dist[t]+1;
            ans=max(ans,dist[tx]);
            q.push(tx);
        }
        if(dist[ty]==-1){
            dist[ty]=dist[t]+1;
            ans=max(ans,dist[ty]);
            q.push(ty);
        }
    }
}
int main(){
    scanf("%d%d",&n,&k);
    bfs();
    cout<<ans<<endl;
}

(5.) 搭积木

题意

输入一个(n,m),之后输入(n)行,每行(m)个字符表示当前是否可以放置,如果为(.)则可以放置,如果为(X)则不能放置,积木的放置必须满足以下三个规则,

  • 当前可以防止积木当且仅当当前位置上一行放置了积木

  • 每一行中积木的放置必须是连续的

默认第(0)行已经全部放置,求有多少种可能的放置方案,答案(mod; 10^{9}+7)

数据范围

(1leq n,mleq 10^{5})

题解

  • 状态表示: (dp[i][l][r])在第(i)层的(lsim r)区间都放上积木的方案数

    • (lsim r)中能放上积木当且仅当(i-1)层中(lsim r)都已经放上了积木,否则就为(0)

    • 当前行相同的放置,之前的行只要有不同的放置就算是不同的方案

  • 状态转移:(dp[i][l][r] = sum_{j=1}^{l} sum_{k=r}^{m} dp[i-1][j][k])

    • 当前的状态计算中不包含都不放的方案,所以答案的初始值需要设置为(1)

Code

#include<iostream>
using namespace std;
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define ll long long
const int mod=1000000007,N=110;

char g[N][N];
ll s[N][N],dp[N][N][N];
int c[N][N];
int n,m;
int ans;

void add_suffix(int i){
    rep(j,1,m) rep(k,1,m){
        s[j][k]=s[j-1][k]+s[j][k-1]-s[j-1][k-1]+dp[i][j][k];
    }
}
ll get_suffix(int x1,int y1,int x2,int y2){
    return (s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1])%mod;
}
int main(){
    cin>>n>>m;
    per(i,n,1) scanf("%s",g[i]+1);
    rep(i,1,n)
        rep(j,1,m)
            c[i][j]=c[i][j-1]+(g[i][j]=='X');

    dp[0][1][m]=1;
    add_suffix(0);
    int res=1;
    rep(i,1,n){
        rep(j,1,m){
            rep(k,j,m){
                if(c[i][k]-c[i][j-1]==0){
                    ll &x=dp[i][j][k];
                    x=(x+get_suffix(1,k,j,m))%mod;
                    res=(res+x)%mod;
                }
            }
        }
        add_suffix(i);
    }

    // rep(i,1,n){
    //     rep(j,1,m){
    //         cout<<dp[i][j]<<' ';
    //     }
    //     cout<<endl;
    // }
    cout<<(res+mod)%mod<<endl;
}

(6.) 矩阵求和

题意

给定一个(n),矩阵的每个位置((i,j))元素值等于(gcd(i,j)),求(n imes n)的矩阵的元素值之和

数据范围

(1leq nleq 10^{7})

题解

最大公约数(d)的范围为(1leq n)

  • 对于(i,j in [d,n],gcd(i,j)=d)的个数

  • (i,jin [1,lfloor frac{n}{d} floor],gcd(i,j)=1)的数对的个数是一一对应的

  • 满足(i<j),且(gcd(i,j)=1)的数对个数即(sum_{x=2}^{j} phi(x) + 1)

  • (i>j)时候个数上同(i<j)一样,二者都包含了((1,1))容斥掉即可

对于(iin [1,i-1])中与其互质的数的个数即欧拉函数(phi (i))

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
const int N=1e7+10,mod=1e9+7;


ll phi[N];
ll s[N];
bool st[N];
int primes[N],cnt;
void get_eulers(int n){
    phi[1]=1;
    for (int i = 2; i <=n ; ++i) {
        if(!st[i]) {
            primes[cnt++]=i;
            phi[i]=i-1;
        }
        for (int j = 0; primes[j] <=n/i ; ++j) {
            st[primes[j]*i]=true;
            if(i%primes[j]==0) {
                phi[primes[j]*i]=primes[j]*phi[i];
                break;
            }
            phi[primes[j]*i]=phi[i]*(primes[j]-1);
        }
    }

    s[1]=phi[1];
    rep(i,2,n) s[i]=(s[i-1]+phi[i]*2)%mod;
}
// void get_eulers(int n){
//     rep(i,2,n) phi[i]=0;
//     phi[1]=1;
//     for(int i=2;i<=n;i++){
//         if(!phi[i]){
//             for(int j=i;j<=n;j+=i){
//                 if(!phi[j]) phi[j]=j;
//                 phi[j]=phi[j]/i*(i-1);
//             }
//         }
//     }
//     s[1]=phi[1];
//     rep(i,2,n) s[i]=(s[i-1]+phi[i]*2)%mod;
// }
int main(){
    int n;
    cin>>n;
    get_eulers(n);
    ll ans=0;
    rep(d,1,n) ans=(ans+s[n/d]*d%mod*d)%mod;
    cout<<ans<<endl;
}