AtCoder Beginner Contest 044 题解

欢迎来到我的算法小屋
 
 

A

题目分析

 住的天数$N$小于限制天数$K$的时候,使用每晚价格 * 天数的常规算法。居住天数$N$大于$K$的时候,小于等于$K$的部分按照常规价格算,超过$K$的部分,按照另外一种价格算。

参考代码(C++)

 

#include<bits/stdc++.h>

using namespace std;

#define inf 0x3f3f3f3f
#define N 520
#define mem(a,b) memset(a,b,sizeof(a))
#define F(i,j,r) for(int i=j;i<=r;++i)
#define lowbit(x) (x&(-x))
typedef long long ll;


//cin 、 cout 优化
void run(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
}

//快读
inline int read(){ 
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    } 
    return x*f;
}


int main(){
    run();
    int n,k,x,y;
    cin >> n >> k >> x >> y;

    int ans = 0;
    if(n < k){
        ans = n * x;
    }else{
        ans = x * k + (n-k)*y;
    }
    cout << ans;

    return 0;
}

 


 

B

题目分析

我自己的想法是用数组模拟的哈希表去统计每个字母出现的次数,最后扫描一遍,标记是否出现了个数为奇数的。

参考代码(C++)

#include<bits/stdc++.h>

using namespace std;

#define inf 0x3f3f3f3f
#define N 110
#define mem(a,b) memset(a,b,sizeof(a))
#define F(i,j,r) for(int i=j;i<=r;++i)
#define lowbit(x) (x&(-x))
typedef long long ll;
int t,n;
int h[N];

//cin 、 cout 优化
void run(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
}

//快读
inline int read(){ 
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    } 
    return x*f;
}


int main(){
    run();
    char str[N];
    cin >> str;
    int len = strlen(str);

    for(int i = 0; i < len;i++){
        h[str[i]-'a'] ++;
    }

    bool isodd = false; //标记是否出现奇数个数的字母

    for(int i = 0; i < 26;i++){
        if(h[i] % 2 == 1){
            isodd = true;
        }else continue;
    }
    if(isodd){
        puts("No");
    }else{
        puts("Yes");
    }

    return 0;
}

 


 

C

题目描述

题目翻译的原则是删繁从简,那么这个题的大意,是这种的:给咱们$N$张卡片,每个卡片有一个权值$X_i$,然后咱们选任意多张卡片(不能为0),使得这些卡片权值的平均值严格等于$A$,问你有多少种选法。

题目分析

其实想打暴力的,每张卡片有选择和不选择两种情况,那么差不多就是$2^N$的时间复杂度,$N$ = 50,直接喜提$TLE$。开VP的时候,我自己确实是只有这个题没有想起来,除了了暴力,都忘记可以用什么知识点了😳。

赛后搜dalao们写的题解,看到标题上写 动态规划 的时候,我突然悟了🤤。因为动态规划是可以理解是对暴力的优化,优化的就是那些重复的,无用的状态、步骤。

 

本题要咱们找平均数是$A$的选法,根据闫式DP分析的思路,我们集合维护的属性是数量(Count),至于集合的定义,可以开一个三维数组(可以优化到二维的),$f[i][j][k]$表示在前$i$张中卡片中选择,选择的数量是$j$,然后总和是k的方案的数量。

回忆我上面所说,当前这张卡片有选择和不选择两种情况,那么对应到状态转移方程中,想要从$i-1$的状态转移迈向$i$的状态,关键要讨论的,就是这个第$i$个物品,选择还是不选择呢,综合两种情况,就可以得到咱们的状态转移方程

$$f[i][j][k] = f[i-1][j][k](选当前的这个卡片)+ f[i-1][j-1][k-x[i]](不选当前这个物品)$$

参考代码(C++)

#include<bits/stdc++.h>

using namespace std;

#define inf 0x3f3f3f3f
#define N 55
#define mem(a,b) memset(a,b,sizeof(a))
#define F(i,j,r) for(int i=j;i<=r;++i)
#define lowbit(x) (x&(-x))
typedef long long ll;
ll x[N];
ll f[N][N][N*N];

//cin 、 cout 优化
void run(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
}

//快读
inline int read(){ 
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    } 
    return x*f;
}


int main(){
    run();
    int n,a;
    cin >> n >> a;

    //待会从1开始枚举从来避免i-1出现负数的情况。在开始动态规划之前初始化
    for(int i = 1; i <= n;i++) cin >> x[i];

    f[0][0][0] = 1;//题目说的确实至少选一个,但是 0 0 0 确实一种结果。至于其他的j = 0的情况,其结果都是0
    
    for(int i = 1; i <= n;i++){
        for(int j = 0; j <= i;j++){ //这里写0是需要f[0][0][0] = 1这种情况作为启动。
            for(int k = 2500; k >=0;k--){
                f[i][j][k] += f[i-1][j][k];//这里取 = 或者 取 += 都是可以Ac 的。+= 符合逻辑,直接= 是间接符合逻辑
                if(k >= x[i] && j >= 1) f[i][j][k] += f[i-1][j-1][k-x[i]];
            }
        }
    }


    // for(int i = 1;i <= n;i++){
    //     for(int j = 0; j <= 2500;j++)
    //         printf("f[%d][0][%d] = %d\n",i,j,f[i][0][j]);
    // }

    //对于统计数量类型的动态规划,最后都是需要扫一遍,统计最终结果的
    ll ans  = 0;
    for(int j = 1; j <= n;j++){
        ans += f[n][j][a*j];
    }

    cout << ans <<'\n';
    return 0;
}

 

 

当然,这个代码可以有两种优化(动态规划能优化的几乎只有空间),其一是滚动数组优化空间,其二是降到二维数组。

滚动数组

空间上优化一半吧

 

 

#include<bits/stdc++.h>

using namespace std;

#define inf 0x3f3f3f3f
#define N 55
#define mem(a,b) memset(a,b,sizeof(a))
#define F(i,j,r) for(int i=j;i<=r;++i)
#define lowbit(x) (x&(-x))
typedef long long ll;
ll f[2][N][N*N];
int x[N];

//cin 、 cout 优化
void run(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
}

//快读
inline int read(){ 
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    } 
    return x*f;
}


int main(){
    run();

    int n,a;
    cin >> n >> a;


    for(int i = 1;i <= n;i++) cin >> x[i];

    f[0][0][0] = 1;
    f[1][0][0] = 1;

    //^异或运算又被称为不进位加法 1 ^ 0 = 1, 1 ^ 1 = 0,以此实现0和1之间的滚动
    for(int i = 1,roll = 1; i <= n;i++,roll ^= 1){
        for(int j = 0; j <= n;j++){
            for(int k = 2500;k >= 0; k--){
                f[roll][j][k] = f[roll ^ 1][j][k];
                if(k >= x[i] && j >= 1) f[roll][j][k] += f[roll^1][j-1][k-x[i]];
            }
        }
    }

    ll ans = 0;
    //n & 1 可以区分奇数1和偶数0,从而区分空间
    for(int j = 1,roll = n&1; j <= n;j++)
        ans += f[roll][j][j*a];

    cout << ans;




    return 0;
}
View Code

 

二维数组

至于打二维数组是看到这位博主的文章,我暂时不能很好get,后面回来再尝试理解了。

upc 6461: Tak and Cards

要点总结

这个题的状态转移方程这个就是普通的线性DP,没有什么代表性了,可以试着玩玩滚动数组,以及琢磨一下优化到二维。


 

D

题目描述

D题属于是,思维题,思维题我也没有什么折,模拟出来,只是过了一半的测试数据。

题目意思大概是这种的:

给咱们一个整数$n$和整数$s$,这个整数$s$呢,是整数$n$和整数$b$通过某种运算$f$得到的结果。关于这个$f$运算法则,是这种定于的:假如$b$大于$n$,那么这个结果$s$ = $n$。假如$n$大于等于$b$,最后的结果是整数$n$不断被整数$b$取模之后结果的和。现在给咱们$n$ 和 $s$ ,让咱们找符合条件的,最小的$b$,假如找不到,就输出-1.

题目分析

这个题了,都可能知道,得分类讨论了,

情况一:$s$ == $n$的时候,最小的整数$b$是$n + 1$;

情况二:$s$ > $n$的时候,是无解,得输出-1,

那么,这个题比较考思维的情况三来了,当$s$ < $n$的时候,该咋办呢

我看dalao们是酱紫理解的。

 

他们的思考方式,让我回忆起了Y总说的话。数据范围是出题人给我们的提示

 

对于这个题而言,数据范围是$10^{11}$,即使朴素的一重循环,$O(N)$的时间复杂度做法来解决这个题了,都是没法全部Ac的。$O(N)$过不了,但是$O(\sqrt{N})$的做法,是一定可以过的,好了,情况三的核心命脉拿捏到了。

我们可以将本题的题面在情况三中抽象成数学等式:

\begin{equation}
\begin{cases}
s=x_0 + x_1 + x_2 + ... + x_m& \\
n= x_0 * b^0 + x_1*b^1+x_2*b^2 + ... + x_m*b^m&
\end{cases}
\end{equation}

 

//碎碎念:实践证明,latex写大括号公式的时,&符号前面不能有空格

根据上面的想法,咱们就着重瞄着这个能够出现根号的$\sqrt{}$的$b^2$展开毒打。

当最高次幂为2的时候,有:$b^2 <= n$,当最高次幂是3,是3的时候,仍然是满足 $b$ <= $\sqrt{n}$的,

那么,当最高次幂为2次幂及其以上的时候,可以直接枚举b,找到合适的解;

但是当 $b>\sqrt{n}$的时候,此时的数学公式是:

\begin{equation}
\begin{cases}
s=x_0 + x_1& \\
n= x_0 * b^0 + x_1*b^1&
\end{cases}
\end{equation}

经过化简,能够得到的式子是$(b-1)*x_1 = n - s$。那咱们在$(\sqrt{n-s})$的范围内枚举能使$(n-s)$的结果整除的$x_1$就可以找到相应的$b$了。

 

参考代码(C++)

#include<bits/stdc++.h>

using namespace std;

#define inf 0x3f3f3f3f
#define N 520
#define mem(a,b) memset(a,b,sizeof(a))
#define F(i,j,r) for(int i=j;i<=r;++i)
#define lowbit(x) (x&(-x))
typedef long long ll;
ll n,s;

//cin 、 cout 优化
void run(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
}

//快读
inline int read(){ 
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    } 
    return x*f;
}


int main(){
    run();

    cin >> n >> s;
    if(n == s){
        cout << n + 1 <<'\n';
        return 0;
    }else if(n  < s){
        cout <<-1 <<'\n';
        return 0;
    }else{ // n > s

        //先求一个根号n
        ll mid = sqrt(n) + 1;
        bool isFind = false;
        //讨论两种情况 b <= mid.,枚举b到合适的值
        for(ll b = 2; b <= mid;b++){ //注意这个题数据很大,不要轻易用int
            ll sum = 0;
            ll tmp = n;
            while(tmp){
                sum += tmp%b;
                tmp /= b;
            }
            if(sum == s){
                cout << b <<'\n';
                isFind = true;
                break;
            }
        }

        //讨论b > mid的情况

        if(!isFind){
            ll num = n - s;
            mid = sqrt(num)+1;
            ll ans ;
            for(int x1 = mid; x1 >= 1;x1--){
                if(num % x1 == 0){
                    ans = (num/x1) + 1;
                    ll x0 = s - x1;
                    //判断得到的结果是否合法
                    //这儿的x1,x0都是ans(即b)取模后得到的,肯定是小于b的
                    if(ans >= 2 && ans > x1 && x0 >= 0 && ans > x0 ){
                        cout << ans <<'\n';
                        isFind = true;
                        break;
                    }
                }
            }
        }

        //情况三的两种讨论都尝试了还是没有找到,那确实是找不到合适的了
        if(!isFind){
            cout <<-1;
            return 0;
        }
    }


    return 0;
}

 

要点总结

 需要学会这里根据数据范围定算法的思维,至于这种情况三这里根据$\sqrt{N}$算法而生的讨论方式,希望能记住吧,原来还可以这种玩🤔🤔🤔

posted @ 2022-08-13 09:05  xxdjx  阅读(43)  评论(0编辑  收藏  举报