题目大意:
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;
}
}