AtCoder Beginner Contest 210 题解(A-D)

AtCoder Beginner Contest 210 题解(A-D)

A. Cabbages

题目大意:

N N N颗包菜,前 A A A颗包菜售价 x x x元,之后的售价 Y Y Y元,问一共要花多少钱

解题思路:

签到题,直接算即可

代码:
#include
using namespace std;
long long  n,a,x,y;
int main()
{
    cin>>n>>a>>x>>y;
    long long res=n*x;
    if(n>a) res-=(n-a)*(x-y);
    cout<<res<<endl;
    return 0;
}

B. Bouzu Mekuri

题目大意:

给出长度为 N N N的01字符串,两名玩家从字符串开头交替前进,第一个碰到1的玩家输掉比赛。

解题思路:

找出第一个1的下标,判断一下即可。

代码:
#include
using namespace std;
int n;
string s;
int main()
{
    cin>>n>>s;
    for(int i=0;i<n;i++){
        if(s[i]=='1'){
            if(i%2) puts("Aoki");
            else puts("Takahashi");
            break;
        }
    }
    return 0;
}

C. Colorful Candies

题目大意:

N N N个糖果摆成一列,每个糖果有不同的颜色,问连续的 K K K个糖果中不同颜色的个数最大是多少。

解题思路:

从左到右遍历的过程中用map维护一下每种颜色的个数,时间复杂度 O ( N log ⁡ N ) O(N\log N) O(NlogN)

代码:
#include
using namespace std;
const int N=3e5+10;
map<int,int> ma;
int n,k,a[N];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++ ) scanf("%d",&a[i]);
    int cur=0,res=0;
    for(int i=1;i<=n;i++){
        ma[a[i]]++;
        if(ma[a[i]]==1) cur++;
        if(i>k){
            ma[a[i-k]]--;
            if(ma[a[i-k]]==0) cur--;
        }
        if(i>=k) res=max(cur,res);
    }
    printf("%d\n",res);
    return 0;
}

D. National Railway

题目大意:

给出一个 N × M N \times M N×M的二维矩阵 A A A,现在要选择两个点建车站。

如果选择点 ( i , j ) (i,j) (i,j)和点 ( i ′ , j ′ ) (i',j') (i,j)建立车站的话,花费是 A i , j + A i ′ , j ′ + C × ( ∣ i − i ′ ∣ + ∣ j − j ′ ∣ ) A_{i,j}+A_{i',j'}+C\times(|i-i'|+|j-j'|) Ai,j+Ai,j+C×(ii+jj),问最小的花费是多少。

解题思路:

因为 1 ≤ N , M ≤ 1000 1\le N,M\le 1000 1N,M1000,如果是用暴力做法的话时间复杂度是 O ( N 2 M 2 ) O(N^2M^2) O(N2M2),显然是会TLE的。

首先,我们将花费分成两个部分: A i , j A_{i,j} Ai,j A i ′ , j ′ + C × ( ∣ i − i ′ ∣ + ∣ j − j ′ ∣ ) A_{i',j'}+C\times(|i-i'|+|j-j'|) Ai,j+C×(ii+jj)。因为第二部分中是包含绝对值的,所以我们可以按照某种先后顺序来考虑问题来避免绝对值对于计算的影响。我们可以假设对于 ( i , j ) (i,j) (i,j)这个点,我们只考虑左上方的点 ( i ′ , j ′ ) (i',j') (i,j),其中 i ′ ≤ i , j ′ ≤ j i'\le i,j'\le j ii,jj,所以绝对值自然而然地就去掉了。

在这个基础上我们可以想到用动态规划来定义状态,设 d i , j d_{i,j} di,j表示在假设只能从左上方走到点 ( i , j ) (i,j) (i,j)的前提下在点 ( i , j ) (i,j) (i,j)设置站点的最小花费,因为我们只能从左上方移动到这个点,所以可以想到当前状态只能从以下三个状态中选择最优解:

  • 直接在 ( i , j ) (i,j) (i,j)设立站点,花费为 A i , j A_{i,j} Ai,j
  • 从上方的站点转移过来,花费为 d i − 1 , j + C d_{i-1,j}+C di1,j+C
  • 从左侧的站点转移过来,花费为 d i , j − 1 + C d_{i,j-1}+C di,j1+C

所以状态转移方程就是: d i , j = m i n ( A i , j , m i n ( d i − 1 , j + C , d i , j − 1 + C ) ) d_{i,j}=min(A_{i,j},min(d_{i-1,j}+C,d_{i,j-1}+C)) di,j=min(Ai,j,min(di1,j+C,di,j1+C))

其中要注意将不合法的点初始化为正无穷。

经过这一趟动态规划,我们得到了在只能从左上方转移的前提下每个点设立站点的最小花费,然后可以继续考虑第二个站点,为了便于进行状态计算,第二个站点的情况同样要在之前的前提下考虑,我们考虑第二个站点的时候选择一个左上方的点作为第一个站点,那么以这个站点作为第二个站点的最小花费就是 m i n ( d i − 1 , j + A i , j + C , d i , j − 1 + A i , j + C ) min(d_{i-1,j}+A_{i,j}+C,d{i,j-1}+A_{i,j}+C) min(di1,j+Ai,j+C,di,j1+Ai,j+C),所以答案就是以所有点作为第二个站点的最小花费中的最小值。

其实做到这里还没有结束,因为我们在上述过程中只考虑了每个点从左上方的点转移过来的情况,实际上,每个点还有可能跟右上方的点组成一对站点,所以我们只需要再按照上述的做法跑一遍在考虑只从右上方转移过来的情况,取最小值即可。

算法时间复杂度:O(NM)。

代码:
#include
using namespace std;
typedef long long LL;
const int N=1010;
LL a[N][N];
LL d[N][N];
int n,m,k;
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) scanf("%lld",&a[i][j]);

    LL res=1e18;
    memset(d,0x3f,sizeof d);

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) d[i][j]=min(a[i][j],min(d[i-1][j]+k,d[i][j-1]+k));

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
             LL t =min(d[i-1][j]+a[i][j]+k,d[i][j-1]+a[i][j]+k);
             res=min(t,res);
        }

    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--) d[i][j]=min(a[i][j],min(d[i-1][j]+k,d[i][j+1]+k));

    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--){
            LL t=min(d[i-1][j]+a[i][j]+k,d[i][j+1]+a[i][j]+k);
            res=min(t,res);
        }

    printf("%lld\n",res);
    return 0;
}

你可能感兴趣的:(AtCoder,算法,动态规划,贪心算法,数据结构)