P1043 数字游戏

爆搜+剪枝 or 类似于区间dp的dp

先把题目中的条件转化成我们容易解决的。

首先当然是化环成链。做法就是开两倍的空间,然后枚举每一个点开始的情况。

(或者你用高深的膜法也可以啊)

这里提供两种方法,小菜鸡只会第一种。

法一:爆搜+剪枝

大体思路就是枚举现在已经切成了多少段,当切到\(m\)段了就停止结算答案。

不知道为什么,我们需要记录一个\(pos\)为上一次划分的终点,下一次枚举就从\(pos+1\)开始就可以了。

大概这么写一写再调试看一下就能够写出来一个爆搜的程序了。

ok,交上去。但是T掉了一个点,就是那个数据范围最大的点。

所以我们可以考虑剪枝了。

我们的硬性要求是求出全局的最大值和最小值,那么我们进行最优性剪枝。

剪枝方法:如果当前数大于等于当前最小值 并且 当前数乘以9的\(m-t+1\)次方还小于等于当前最大值时,剪枝!

所以就可以轻松地跑过这道题啦!

代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
const int maxn = 55, maxm = 10;
const int INF = 0x3f3f3f3f;
int minv = INF, maxv = -INF;
int a[maxn * 2];
int n, m;
int turn(int x)
{
    return (x % 10 + 10) % 10;
}
int pao(int x, int y)
{
    int ans = 1;
    for(int i = 1; i <= y; i++) ans *= x;
    return ans;
}
void dfs(int t, int pos, int res, int start)
{
    if(res * pao(9, m - t + 1) <= maxv && res >= minv) return;
    if(t == m)
    {
        res *= turn(a[n + start - 1] - a[pos]);
        minv = std::min(minv, res);
        maxv = std::max(maxv, res);
        return;
    }
    for(int i = pos + 1; i < start + n - m + t; i++)// 选当前区间的右端点(闭) 
    {
        dfs(t + 1, i, res * turn(a[i] - a[pos]), start);
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    for(int i = 1; i <= n + n; i++) a[i] += a[i - 1];
    for(int i = 1; i <= n; i++) dfs(1, i - 1, 1, i);
    printf("%d\n%d\n", minv, maxv);
    return 0;
}

法二:类似于区间dp的dp

这道题其实跟“石子合并”是很像的,只是多了一维而已。

那么我们要处理的,就是一段区间的数字,分成若干段区间的最大最小值了。

dp[i][j][l]\([i,j]\)这段区间内的数字被分为\(l\)段时的最大最小值。

那么可以有

\[dp[i][j][l]=dp[i][k][l - 1] \times turn(a_{k+1}+...+a_j) \]

那一段区间和显然可以直接用前缀和咯。

然后就差不多是这个样子。注意初始化就可以了。

但是这道题还时反应了我的一个错误的思路:

这道题的划分个数显然是小于等于区间长度的,所以我们不能完全像区间dp地那样套模板。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
const int maxn = 110, maxm = 10;
const int INF = 0x3f3f3f3f;
int a[maxn];
ll dp1[maxn][maxn][maxm], dp2[maxn][maxn][maxm];// min and max
ll minv = INF, maxv = -INF;
int n, m;
int turn(int x)
{
    return (x % 10 + 10) % 10;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    for(int i = 1; i <= n + n; i++) a[i] += a[i - 1];
    for(int i = 1; i <= n + n; i++)
    {
        for(int j = i; j <= n + n; j++)
        {
            dp1[i][j][1] = turn(a[j] - a[i - 1]);
            dp2[i][j][1] = turn(a[j] - a[i - 1]);
        }
    }
    for(int len = 2; len <= m; len++)
    {
        for(int i = 1; i <= n + n; i++)
        {
            for(int j = i + len - 1; j <= n + n; j++)
            {
                dp1[i][j][len] = INF;
            }
        }
    }
    for(int len = 2; len <= m; len++)
    {
        for(int i = 1; i <= n + n; i++)
        {
            for(int j = i + len - 1; j <= n + n; j++)
            {
                for(int k = i + len - 2; k < j; k++)
                {
                    dp1[i][j][len] = std::min(dp1[i][j][len], dp1[i][k][len - 1] * turn(a[j] - a[k]));
                    dp2[i][j][len] = std::max(dp2[i][j][len], dp2[i][k][len - 1] * turn(a[j] - a[k]));
                }
            }
        }
    }
    for(int i = 1; i <= n; i++)
    {
        minv = std::min(minv, dp1[i][i + n - 1][m]);
        maxv = std::max(maxv, dp2[i][i + n - 1][m]);
    }
    printf("%lld\n%lld\n", minv, maxv);
    return 0;
}
posted @ 2018-10-31 22:06  Garen-Wang  阅读(323)  评论(0编辑  收藏  举报