Atcoder Grand Contest 022 C

Atcoder Grand Contest 022 C

C题

题意

希望把给定的一个序列转化为另一个序列,每次操作可以选择对任意一个元素求余数或不求,每次操作的花费为2^k求,求转化的最小花费。

笺释

关键在于转变思路,我们并不是要把一个序列一步步地转化为另一个序列,这样的话必然设计到记录每一个数字的数值作为一个状态,而记录这样的状态是不可能做到的。
我们要做的是枚举余数,因为数字最大是50,所以余数也最大是50(其实是49吧喂!),可以通过二进制的思想将每一个余数用与没用作为一位保留在一个数A中,判断使用这些数字能否由A转化为B。
二进制的写法见详细代码,这里说一下判断能否转化的方法。

 for(int i=1;i<=n;i++)
    {
        if(a[i]==b[i])
        {
            continue;
        }
        flag=0;
        memset(dp,0,sizeof(dp));
        dp[a[i]]=1;
        for(int j=a[i];j>=0;j--)
        {
        //注意两个内层循环一定是从a[i]向0,并且0一定能取到。
        //在第一个循环中0代表着余数1的用与不用,在第二个循环中0代表着转化为的一个数0
        //逆序是因为要让dp顺利完成更新(每次取余所得结果一定变小)
            if(((1ll<<j)&ans)>0)
            {
             //   printf(" a %d\n",j);
                for(int k=a[i];k>=0;k--)
                {
                    if(dp[k])
                    {
                        dp[k%(j+1)]=1;
                        if((k%(j+1))==b[i])
                        {
                            flag=1;
                        }
                    }
                }
            }
        }
        if(!flag)
        {
        //    printf("%d\n",i);
            return 0;
        }
    }
    return 1;

还有一种更快的方法,从50向1,每次去掉一个余数,当一个余数必须使用的时候将其保留,然后更新状态。
详见完整代码

完整代码

#include<bits/stdc++.h>
#define MAXN 50
using namespace std;
typedef long long ll;
int n,flag;
int a[MAXN+1],b[MAXN+1];
int dp[MAXN];
long long ans;
bool solve(ll ans)
{
    for(int i=1;i<=n;i++)
    {
        if(a[i]==b[i])
        {
            continue;
        }
        flag=0;
        memset(dp,0,sizeof(dp));
        dp[a[i]]=1;
        for(int j=a[i];j>=0;j--)
        {
            if(((1ll<<j)&ans)>0)
            {
             //   printf(" a %d\n",j);
                for(int k=a[i];k>=0;k--)
                {
                    if(dp[k])
                    {
                        dp[k%(j+1)]=1;
                        if((k%(j+1))==b[i])
                        {
                            flag=1;
                        }
                    }
                }
            }
        }
        if(!flag)
        {
        //    printf("%d\n",i);
            return 0;
        }
    }
    return 1;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
    }
    ans=(1ll<<MAXN)-1;
    if(!solve(ans))
    {
        printf("-1");
        return 0;
    }
    for(int i=MAXN-1;i>=0;i--)
    {
        //printf("%lld\n",ans);
        ans^=1ll<<i;
        if(!solve(ans))
        {
            ans^=1ll<<i;
        }
    }
    printf("%lld\n",ans<<1);
}
#include<bits/stdc++.h>
#define MAXN 50
using namespace std;
int n;
int a[MAXN+1],b[MAXN+1];
bool solve[MAXN+1][MAXN+1][MAXN+1];
bool dp[MAXN+1][MAXN+1];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
    }
    memset(solve,false,sizeof(solve));
    memset(dp,false,sizeof(dp));
    for(int i=0;i<=MAXN;i++)
    {
        dp[i][i]=true;
        solve[0][i][i]=true;
    }
    for(int i=1;i<=MAXN;i++)
    {
        for(int j=0;j<=MAXN;j++)
        {
            for(int k=0;k<=MAXN;k++)
            {
                solve[i][j][k]=solve[i-1][j][k];
            }
        }
        for(int j=0;j<=MAXN;j++)
        {
            for(int k=0;k<=MAXN;k++)
            {
                if(solve[i][j%i][k])
                {
                    solve[i][j][k]=true;
                }
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(!solve[MAXN][a[i]][b[i]])
        {
            printf("%d\n",-1);
            return 0;
        }
    }
    long long ans=0;
    for(int i=MAXN;i>=1;i--)
    {
        int j,k;
        //这一段的精髓之处在于这样的转化关系:想要由a变成b
        //一定可以看作由a变为一个中间变量k变到b的(k可能等于a)
        //在当前已经取了一定量的余数的情况下,dp[a[j]][k]存在意味着dp可以变到k
        //而只要存在中间变量k能够使a[j]变到k变到b[j]即可
        //这实际上是包含了a[j]->A->B->C->D->b[j]这种多个中间变量的情况的
        for(j=1;j<=n;j++)
        {
            for(k=0;k<=MAXN;k++)
            {
                if(dp[a[j]][k])
                {
                    if(solve[i-1][k][b[j]])
                    {
                       // printf("ss\n");
                        break;
                    }
                }
            }
            //每一个都没走完MAXN个,可以跳过 就continue
            //有一个走完了MAXN个,不能跳过,就不continue
            if(k>MAXN)
            {
             //   printf("A %d\n",j);
                break;
            }
        }
        if(j==n+1)
        {
            continue;
        }
        ans+=1ll<<i;
      //  printf("%d\n",i);
        for(j=1;j<=MAXN;j++)
        {
            for(k=1;k<=MAXN;k++)
            {
                if(dp[j][k])
                {
                    dp[j][k%i]=true;
                }
            }
        }
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值