JZOJ 5436. 【NOIP2017提高A组集训10.30】Group

来源:互联网 发布:广东开放大学网络 编辑:程序博客网 时间:2024/05/21 01:27

题目

给定n个数,将这n个数分为若干组,求每一组最大值与最小值的差的和不超过K的方案数。

题解

暴力可以过N10的数据。
N50,设f[i][j][k]表示做到i,分了j组,每一组最大值与最小值的差的和为k的方案数。但是这样k的范围很大,会爆。
这时,一个有序的序列会比一个无序的序列更容易被想到做法。
此时有序胜无序
题目的条件:
①只有每组的最大/最小值会对答案有贡献。
②最大值与最小值的差的和受到了不超过K的限制。
如果去掉条件②那很好做,就是斯特林数。
f[i][j]表示做到i分了j组。f[i][j]=f[i1][j]j+f[i1][j1]
加上了条件②,我们第二维的设法可能要改一下。
如果第二维只表示分了j组,在不知道每组的最大值/最小值的情况下很难去设第三维。这是最困惑人的地方。
由于不能直接确定每组的最大值和最小值导致Σ,所以考虑当前的Σ,即此时的Σ不代表当前i个元素最后所属集合的每一组最大值与最小值的差的和。

表示不了目前确切的Σ,可以尝试表示一下暂时匹配完+没匹配完的Σ
f[i][j][k]表示做到i,还有j组没有分完,暂时的Σ=k的方案数。
分两种情况。
①新开一组。
f[i][j+1][k+(a[i]a[i1])j]+=f[i1][j][k](元素a[i]不是新组的max)
f[i][j][k+(a[i]a[i1])j]+=f[i1][j][k](元素a[i]是新组的max)
②继续把新元素分入旧组。
f[i][j][k+(a[i]a[i1])]+=f[i1][j][k]j(元素a[i]不是旧组的max)
f[i][j1][k+(a[i]a[i1])]+=f[i1][j][k]j(元素a[i]是旧组的max)
用滚动数组就可以将空间控制在范围之内。

正确性

由于序列的无序性,我们排下序,不管是有没有匹配完的组的max-min,设min的位置是j1,max的位置是j2,那么这一组对答案的贡献为

a[j1+1]a[j1]+a[j1+2]a[j1+1]+...+a[j2]a[j21]=a[j2]a[j1]

总结

①对于DP题目明确题目关键条件(老梗不继续吹)
②若有困惑你的条件,试想将这个条件去掉怎么做,然后再加上这个条件,分类讨论的情况大概跟去掉这个条件后分类讨论的情况差不多。
DP不一定要表现形象的东西(这也是老梗),一个数看看能否通过拆散(这种老套路的题目很多啊)从而有利于DP转移。
④如果序列有无序性,那么此时可以排排序。

代码

#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<algorithm>#define N 210#define LL long long#define mo 1000000007#define fo(i,a,b) for(i=a;i<=b;i++)using namespace std;LL i,j,k,l,v,n,m,ans,tmp,o;LL a[N];LL f[2][N][N*5];bool cmp(LL x,LL y){return x<y;}void add(LL &x,LL y){x=(x+y)%mo;}int main(){    scanf("%lld%lld",&n,&m);    fo(i,1,n)scanf("%lld",&a[i]);    sort(a+1,a+n+1,cmp);    o=0;    f[o][0][0]=1;    fo(i,1,n){        o=1^o;        memset(f[o],0,sizeof(f[o]));        fo(j,0,i-1){            tmp=j*(a[i]-a[i-1]);            fo(k,0,m){                if(tmp+k>m)break;                v=tmp+k;                add(f[o][j+1][v],f[o^1][j][k]);                add(f[o][j][v],f[o^1][j][k]);                 if(j){                    add(f[o][j][v],(f[o^1][j][k]*j)%mo);                    add(f[o][j-1][v],(f[o^1][j][k]*j)%mo);                }            }        }    }    fo(i,0,m)ans=(ans+f[o][0][i])%mo;    printf("%lld\n",ans);    return 0;}
原创粉丝点击