Comet OJ - Contest #7 C临时翻出来的题

https://cometoj.com/contest/52/problem/C
题目描述

有一年暑假,真琴来到了鸟白岛。稻荷很开心,于是她们开始玩一个简单的小游戏。

首先拿出 n 张牌,每张牌有一个编号。现在裁判员苍规定这 n 张牌合法的排列要满足编号为 i 的牌不可以放在位置 p_i上。
然后稻荷会摆放这些牌,同时这样定义这个序列的 key 值:
记编号为 i 的牌的位置为 pos_i 。对于所有两张牌的组合(这两张牌编号为 i 和 j),若满足 j > i 且 pos_j < pos_i,那么对 key 值产生的贡献就是 ∣i−j∣ × ∣pos_ j −pos_i|,否则贡献为 0。

由于这个问题实在是太简单了,真琴每次都能很快的回答上来。为了证明自己的小狐狸稻荷是最强的,苍趁机刁难道:对于所有合法的排列,它们的 key 值之和为多少呢?

这个问题有点困难,真琴不会了。所以希望您能帮她解决。

如果您解决了,她或许会分一个狐不理肉包给你吃哟 ~

输入描述

第一行一个整数 T,描述数据组数。

对于每组数据第一行是一个整数 n,表示牌的数量。

接下来一个长度为 n 的整数序列,第 i 个数为 p_i​。
保证 T≤10 ,1≤n≤16 , 1≤p i ≤n。

输出描述

共 T 行,每行输出一个整数,第 i行的整数代表示第i个数据的答案。

样例输入 1

2
2
1 2
3
1 2 3
样例输出 1

1
8
提示

样例解释

第一个数据有 “22 11” 这一个排列符合要求,贡献为 1 \times 1=11×1=1,所以答案为 11。

第二个数据有 “22 33 11”, “33 11 22” 这两个排列符合要求,贡献为 (2-1) \times (3-1) + (3-1) \times (3-2) + (3-1) \times (2-1) + (3-2) \times (3-1) = 8(2−1)×(3−1)+(3−1)×(3−2)+(3−1)×(2−1)+(3−2)×(3−1)=8,所以答案为 88。

题解:对于一些要求序列合法,故要考虑后续是否合法因此不能在过程中累加对所求答案产生贡献的问题,可以使用dp记录过程中产生的贡献,dp过程一直根据合法条件进行状态转移,所以最后得到的结果一定是合法的贡献。

对于这题使⽤状压dp,按编号从⼩到⼤考虑。令集合S中的位置已放置了牌,已放置了编号不超过∣S∣的牌,仅考虑已放置的牌的每种排列,此时记贡献为f(S),⽅案数为t(S),位置x上的牌在每种⽅案中的编号之和为g(S, x)。对每个S,枚举第y := ∣S∣ + 1张牌的位置x,更新S′ := S ∪ {x}的答案:
f(S′) ←+ f(S) + y ⋅ (∑z>x, z∈S (z − x) ⋅ t(S)) − ∑z>x, z∈S g(S, z) ⋅ (z − x)
g(S′,i) ←+ g(S,i), ∀1 ≤ i ≤ n
g(S′, x) ←+ t(S) ⋅ y
t(S′) ←+ t(S)
其中,:=表⽰定义,a ←+ b表⽰将a的值更新为a + b。
f的更新中,将贡献j > i, posj < posi, (j − i)(posi − posj )拆为j ⋅ (posi − posj )和−i ⋅ (posi − posj )两部分,对z = posi, x = posj , y = j计算贡献。
单组数据的时间复杂度为O(n^2 * 2^n)。

具体地,针对所贴代码:
(1) 状态S----->0101表示第一个位置和第三个位置被卡1和卡2占用
(2) dp1[S]----->表示当前状态S的种类数,比如dp1[0101]=2,因为0101中卡一可以放在第一个位置或者第三个位置
(3)dp2[S][k]----->表示在状态S中位置k处在转移过程产生的编号之和,比如dp2[0111][3]=12,因为0111中的第三张卡片可以放在位置1、2、3,放在位置1时位置3得到的卡片编号之和应为1+2(因为此时可以放卡片1或者2,而这也恰好是dp2[0110][3]),同理位置2时也是1+2(即dp2[0101][3]),位置3时是3+3(即dp1[0011]*3),所以编号之和是1+2+1 + 2+3 + 3=12.这个主要是用于后面计算贡献
(4)dp3[S]----->表示状态S下产生的总贡献,也就是枚举S当前被填充的位置,直接根据dp2累加当前贡献即可

#include<bits/stdc++.h>
using namespace std;
#define debug(x) cout<#x<<" is "<<x<<endl;
typedef long long ll;
ll a[17],ct[(1<<16)+5],dp1[(1<<16)+5],dp2[(1<<16)+5][17],dp3[(1<<16)+5];
int main(){
    int t;
    scanf("%d",&t);
    for(int i=1;i<=(1<<16);i++)ct[i]=ct[(i>>1)]+(i&1);
    while(t--){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        for(int i=1;i<(1<<n);i++){
            dp1[i]=dp3[i]=0;
            for(int j=0;j<n;j++){
                dp2[i][j]=0;
            }
        }
        dp1[0]=1;
        for(int j=1;j<(1<<n);j++){
                for(int k=0;k<n;k++){
                    if(((1<<k)&j)&&a[ct[j]]!=(k+1)){
                        ll sum=0;
                        for(int z=k+1;z<n;z++){
                            if((1<<z)&(j-(1<<k)))sum+=(z-k);
                        }
                        sum*=dp1[j-(1<<k)]*ct[j];
                        for(int z=k+1;z<n;z++){
                            if((1<<z)&(j-(1<<k)))sum=sum-dp2[(j-(1<<k))][z]*(z-k);
                        }
                        dp3[j]+=sum+dp3[j-(1<<k)];
                        dp1[j]+=dp1[j-(1<<k)];
                        for(int z=0;z<n;z++){
                            dp2[j][z]+=dp2[(j-(1<<k))][z];
                        }
                        dp2[j][k]+=dp1[j-(1<<k)]*ct[j];
                    }
                }
            }
       printf("%lld\n",dp3[(1<<n)-1]);
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值