【2020年牛客暑假第八场】E题 Enigmatic Partition

【2020年牛客暑假第八场】E题 Enigmatic Partition 一阶差分+隔项差分

    • 题意
    • 思路
      • 方法一
      • 方法二
    • Code
    • 反思

题目链接: https://ac.nowcoder.com/acm/contest/5673/E

题意

【2020年牛客暑假第八场】E题 Enigmatic Partition_第1张图片
n n n的拆分,要求最大值与最小值相差为2,且相邻两个数之间相差小于1, f [ i ] f[i] f[i]表示拆分的个数。
有T组测试,每组给出l和r,求出 ∑ i = l r f [ i ] \sum_{i=l}^rf[i] i=lrf[i]

思路

首先知道 n n n是由三个连续自然数之和相加组成的,当然,这三个自然数的个数是不知道的,那么就是求 f [ i ] = a ∗ l + b ∗ ( l + 1 ) + c ∗ ( l + 2 ) ( a , b , c ≥ 1 ) f[i]=a*l+b*(l+1)+c*(l+2)(a,b,c\geq1) f[i]=al+b(l+1)+c(l+2)abc1

方法一

根据标程给的题解,它是枚举 l l l,所以这三个数为 l , l + 1 , l + 2 l,l+1,l+2 l,l+1,l+2,表示为 l , m i d , r l,mid,r l,mid,r,我们知道两个 m i d mid mid可以拆成 l 和 r l和r lr,所以当有 m m m m i d mid mid的时候,就有 ( m − 1 ) / 2 (m-1)/2 (m1)/2中拆法,例如:(下面三个数字分别为 l , m i d 和 r 的 个 数 l,mid和r的个数 lmidr
m = 5 : 有 2 种 拆 法 m=5:有2种拆法 m=52
1 , 2 , 1 1,2,1 121
2 , 0 , 2 2,0,2 202
m = 8 : 有 3 种 拆 法 m=8:有3种拆法 m=83
1 , 5 , 1 1,5,1 151
2 , 3 , 2 2,3,2 232
3 , 1 , 3 3,1,3 313

为什么 m i d mid mid可以等于0,因为之前 m − 1 m-1 m1,这个1就是 m i d mid mid
为什么 l 和 r l和r lr不可以等于0,这样的话就不保证 a , c ≥ 1 a,c\geq1 a,c1

这样枚举 l l l的过程中,再分别枚举 m m m的个数,再枚举往左加 l l l的个数和往右加 r r r的个数,最后再做个前缀和答案就出来了,不过问题就是 l ≤ 100 l\leq100 l100,标程说枚举会比较慢(是非常慢),所以要用dp优化,不过我也不会就咕咕咕了,然后就有了第二种方法。
【2020年牛客暑假第八场】E题 Enigmatic Partition_第2张图片

方法二

应该也算枚举,不过是很巧妙的枚举。我也是看别人的博客才稍微懂一点点点点的。

这里应用了差分的思想。

最后是一阶差分+隔项差分。

参考大佬的博客:
大佬1
大佬2
大佬3

关于差分,可以参考:https://blog.csdn.net/qq_44786250/article/details/100056975

一阶差分就是后一项减去前一项 d [ i ] = a [ i ] − a [ i − 1 ] d[i] = a[i]-a[i-1] d[i]=a[i]a[i1]
隔项差分就是后一项的后一项减去前一项 d [ i ] = a [ i ] − a [ i − 2 ] d[i] =a[i]-a[i-2] d[i]=a[i]a[i2]

然后回到本题。
我们先把式子改变一下。
m = b 1 + b 2 + b 3 m=b1+b2+b3 m=b1+b2+b3
n = a ∗ b 1 + ( a + 1 ) ∗ b 2 + ( a + 2 ) ∗ b 3 = a ∗ ( b 1 + b 2 + b 3 ) + b 2 + 2 b 3 = a m + b 2 + 2 b 3 n=a*b1+(a+1)*b2+(a+2)*b3=a*(b1+b2+b3)+b2+2b3=am+b2+2b3 n=ab1+(a+1)b2+(a+2)b3=a(b1+b2+b3)+b2+2b3=am+b2+2b3
具体细节请看上面三个大佬的博客。
最后我们得出来:
f [ a m + 3 ] + + , f [ ( a + 1 ) m + 1 ] − − , f [ ( a + 1 ) m + 2 ] − − , f [ ( a + 2 ) m ] + + f[am+3]++,f[(a+1)m+1]--,f[(a+1)m+2]--,f[(a+2)m]++ f[am+3]++f[(a+1)m+1]f[(a+1)m+2]f[(a+2)m]++
这里一定要看图解,不然真的很昏。
只要我们枚举 a 和 m a和m am之后,然后对上述位置进行加减操作,最后在隔项差分、一阶差分求出原数组,最后在前缀和得出答案。

Code

#include 

using namespace std;

typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;

#define INF 0x7f7f7f
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)

// const ll mod = 1e9 + 7;
// const int maxn = 1e5 + 10;
// const double eps = 1e-6;

const int N = 1e5 + 10;

ll f[N * 2];
ll F[N * 2];

void Init()
{
    for(int m = 3;m < N; m++) {
        for(int a = 1;a * m < N; a++) {
            f[a * m + 3]++;
            f[(a + 1) * m + 1]--;
            f[(a + 1) * m + 2]--;
            f[(a + 2) * m]++;
        }
    }

    for(int i = 3;i < N; i++)
        f[i] += f[i - 2];

    for(int i = 2;i < N; i++)
        f[i] += f[i - 1];

    for(int i = 1;i < N; i++)
        F[i] = F[i - 1] + f[i];
}

void solve() {
    Init();
    int Case = 1;
    int T;
    cin >> T;
    while(T--)
    {
        int l, r;
        cin >> l >> r;
        cout << "Case #" << Case++ << ": " << F[r] - F[l - 1] << endl;
    }
}


signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    solve();
#else
    solve();
#endif
    return 0;
}

反思

比赛再也不在一题上花5个小时了,呜呜呜~~~

你可能感兴趣的:(题解,数据结构)