AtCoder AGC013两题

题目链接
今天A了a题和d题,其实本来是来做d题的,然而因为d题的代码提交到a题去了,于是就顺便把a题做了。
先来简单的a题,a题的大意就是给出一串序列,要你把它分成若干连续段,保证每段都是单调的(单调不上升或不下降)。求最少分成多少段。这题只要for一遍判断一下即可。
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,f[MAXN],ans=1,pact;
int main()
{
    n=read();pact=0;
    for(int i=1;i<=n;i++) f[i]=read();
    for(int i=2;i<=n;i++){
        if(!pact){
            if(f[i]>f[i-1]) pact=1;
            if(f[i]<f[i-1]) pact=-1;
            continue;
        }
        if((f[i]<=f[i-1]&&pact<0)||(f[i]>=f[i-1]&&pact>0)) continue;
        ans++;pact=0;
    }
    printf("%d",ans);
    return 0;
}
d题倒是比较有难度,题目的翻译如下:

Joisino有很多红色和蓝色的砖块和一个大盒子。 她将按照以下方式把这些砖头建造成一座塔。

首先,她会选择总共N块砖块并放入箱子中。框中可以有各种颜色的砖块。 可能有零块红砖或零块蓝砖。 然后,她将重复M次操作,其中包括以下三个步骤:

从盒子里拿出一块任意的砖块放在塔顶。
把一块红砖和一块蓝砖放进盒子里。
从箱子里拿出另一块任意的砖块放在塔顶。

在M操作之后,Joisino将按照取出的顺序堆叠从箱子中取出的2×M块砖建造一座塔。 她对以下问题感兴趣:这些2×M砖块有多少种不同的颜色序列是可能的? 编写一个程序来找到答案。 由于它可能非常大,请打印计数模10^9 + 7。

1≤N≤3000
1≤M≤3000

这道题难的地方就是去重,刚开始的想法是暴力的dfs,然后用map来判断去重。
#include<bits/stdc++.h>
#define MD 1000000007
#include<map>
#define ll long long
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,ans;
map<int,int> f;
ll pows(ll a,int b){
    ll base=1;
    while(b){
        if(b&1) base=base*a;
        a=a*a;b/=2;
    }
    return base;
}
int dfs(int r,int b,int t,ll rec){
    if(t==m){
        if(!f[rec]){
            f[rec]=1;return 1;
        }
        else return 0;
    }
    int ans=0;
    if(r){
        ans=(ans+dfs(r,b,t+1,rec+pows(2,2*(t+1)-1)))%MD;
        ans=(ans+dfs(r-1,b+1,t+1,rec+pows(2,2*(t+1)-1)+pows(2,2*(t+1))))%MD;
    }
    if(b){
        ans=(ans+dfs(r,b,t+1,rec+pows(2,2*(t+1))))%MD;
        ans=(ans+dfs(r+1,b-1,t+1,rec))%MD;
    }
    return ans;
}
int main()
{
    n=read();m=read();
    for(int i=0;i<=n;i++){
        ans=(ans+dfs(i,n-i,0,0))%MD;
    }
    printf("%d",ans%MD);
    return 0;
}
但是暴力的dfs只能拿小部分分值,于是我们又想用dp来做。
我们设f[i][j][k]表示进行了i次操作,手头还剩下j块红砖,并且红砖是否为0过的答案。这样说可能比较抽象,比如f[2][3][1]表示进行了2次操作,手头还剩下3块红砖,并且之前有红砖为0的状态。那么这里记录红砖为0有什么用呢?我们考虑什么时候会有重复的情况,比如我们从手头有k块红砖到手头有p块红砖时都能推到,且k是红砖最少的情况,那么从k推到状态时肯定会出现红砖为0的情况(因为如果不为0,那么k-1也可以推到),所以我们要记录0的状 。然后我们就进行dp即可。
#include<cstdio>
#define MD 1000000007
#define MAXN 3005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,f[MAXN][MAXN][2],ans;
int main()
{
    n=read();m=read();
    f[0][0][1]=1;
    for(int i=1;i<=n;i++) f[0][i][0]=1;
    for(int i=0;i<=m-1;i++)
     for(int j=0;j<=n;j++)
      for(int k=0;k<=1;k++){
        if(j) f[i+1][j][k|(j==1)]=(f[i+1][j][k|(j==1)]+f[i][j][k])%MD;
        if(j) f[i+1][j-1][k|(j==1)]=(f[i+1][j-1][k|(j==1)]+f[i][j][k])%MD;
        if(n-j) f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%MD;
        if(n-j) f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k])%MD;
      }
    for(int i=0;i<=n;i++) ans=(ans+f[m][i][1])%MD;
    printf("%d",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/stevensonson/article/details/80384954