Comet OJ contest #7 C题 (状压dp)

题目大意:

n张牌编号从1到n,给定每张牌不能放的位置,记编号i的牌位置为posi,那么两张牌编号 i、j 满足 j > i && posj < posi,那么对整个序列key贡献为 | i - j | * | posj - posi |,否则贡献为0

求所有符合要求的牌的排列的key值之和

题目链接: https://cometoj.com/contest/52/problem/C

思路:

因为大编号只会与小编号产生key值贡献,考虑编号从小放到大,公式分解为 j * ( posi - posj ) - i * ( posi - posj )
对每次放置新牌时向后枚举已放置牌的位置,并计算贡献
对 ( posi - posj ) 可用位置差 * 方案数得到
对 i * ( posi - posj ) 则需维护每一位在之前所有方案中放置牌的编号和

因此共需维护
dp[1<<n] 代表当前状态下总key值
t [1<<n] 代表当前状态下方案数
g [1<<n] [n] 代表当前状态下每一位置在所有方案中放置牌的编号和

总结:
在分析状态转移的过程中,不要一味盲目只通过dp数组以及一些固定量进行转移,有时也需要维护多个变量

代码:

#include<bits/stdc++.h>
using namespace std;

long long dp[1<<17],t[1<<17],g[1<<17][20],p[20];

int main(){
    int T;
    cin>>T;
    while(T--){
        memset(dp,0,sizeof(dp));
        memset(t,0,sizeof(t));
        memset(g,0,sizeof(g));
        t[0]=1;
        int n;
        cin>>n;
        for(int i=1;i<=n;i++) cin>>p[i];

        for(int s=1;s<(1<<n);s++){
            int y=__builtin_popcount(s);

            for(int x=1;x<=n;x++){
                if(!(s&(1<<(x-1)))) continue;
                if(x==p[y]) continue;
                for(int i=1;i<=n;i++) g[s][i]+=g[s&~(1<<(x-1))][i];
                g[s][x]+=t[s&~(1<<(x-1))]*y;
                dp[s]+=dp[s&~(1<<(x-1))];
                t[s]+=t[s&~(1<<(x-1))];

                for(int z=x+1;z<=n;z++){
                    if(!(s&(1<<(z-1)))) continue;
                    dp[s]+=y*(z-x)*t[s&~(1<<(x-1))]-g[s&~(1<<(x-1))][z]*(z-x);
                }
            }
        }
        cout<<dp[(1<<n)-1]<<endl;

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值