loj3503.「联合省选 2021 A | B」滚榜

题目链接

看到数据范围应该是个状压。一个自然的想法是 \(dp_{s,i,j,k}\) 表示已经滚榜的集合是 \(s\),上一个滚榜的是 \(i\)\(i\) 新过了 \(j\) 题,一共过了 \(k\) 题,这样是 \(O(2^nn^2m^2)\) 的……怎么比 \(O(n!)\) 还要大?

发现题目要求的只是合法的排列,而不需要求出合法的 \(b\) 序列,我们在上面多保留了很多没用的信息,如果我们可以把 \(j\) 这一维去掉就好了。

发现我们对于每种排列都可以贪心地得到最小的 \(\sum_{i=1}^nb_i\),如果这个总和小于 \(m\) 就有贡献。贪心策略是 \(b_{p_i}=\max(b_{p_{i-1}},a_{p_{i-1}}-a_{p_i}+[i<j])\)

再回来考虑怎样搞掉 \(j\) 那一维。我们直接对差分数组 dp,预处理出来从 \(i\) 转移到 \(j\) 差分数组的取值,这个差分的值 \(d\) 会对后面的所有 \(b\) 都产生 \(d\) 的贡献,我们直接在这里都给加上,然后就直接大力状压背包即可,复杂度 \(O(2^nn^2m)\),跑不满。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define int long long
int n,m,a[14],c[14][14],dp[1ll<<14][14][501],ans;
inline int lowbit(int x)
{
    return x&-x;
}
inline int popcnt(int x)
{
    int res=0;
    while(x)
    {
        ++res;
        x&=x-1;
    }
    return res;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(register int i=1;i<=n;++i)
        scanf("%lld",&a[i]);
    for(register int i=1;i<=n;++i)
        for(register int j=1;j<=n;++j)
            if(i^j)
            {
                c[i][j]=max(0ll,a[i]-a[j]+(i<j));
                c[0][j]=max(c[0][j],c[i][j]);
            }
    for(register int s=1;s<1ll<<n;++s)
        if(s==lowbit(s))
        {
            int p=log2(lowbit(s))+1;
            if(c[0][p]*n<=m)
                dp[s][p][c[0][p]*n]=1;
        }
        else
            for(register int i=1;i<=n;++i)
                if(s&(1ll<<(i-1)))
                    for(register int j=1;j<=n;++j)
                        if(i!=j&&(s&(1ll<<(j-1))))
                        {
                            int minn=(n-popcnt(s)+1)*c[j][i];
                            for(register int k=minn;k<=m;++k)
                                dp[s][i][k]+=dp[s^(1ll<<(i-1))][j][k-minn];
                        }
    for(register int i=1;i<=n;++i)
        for(register int j=0;j<=m;++j)
            ans+=dp[(1ll<<n)-1][i][j];
    printf("%lld\n",ans);
    return 0;
}
posted @ 2021-08-17 16:52  绝顶我为峰  阅读(36)  评论(0编辑  收藏  举报