URAL_1932_The Secret of Identifier(状态压缩+容斥)

题型:DP


题意:

给n个长度为4的字符串,字符串由0~9和a~f组成。

问对应位上有1、2、3、4个不同的字符串各有多少对。


分析:

这是一个我理解了很久的状态压缩。

字符串其实是十六进制,根据这个,

定义dp[16][1<<16],即dp[1111][1111 1111 1111 1111]。

第一维0000~1111,表示当前状态有那几位相同,比如0011表示第3位和第4位相同的字符串。

第二维0~(1<<16),可用于表示所有的字符串。

例如dp[0011][abcd]表示对于字符串abcd,符合xxcd的字符串个数。

然后对每个字符串统计一下,这边的操作可以看下代码,应该好懂。

然后将1、2、3、4位相同的字符串对数答案统计出来。

接下来就是计算1、2、3、4位不同的字符串对数了。

1个不同 = 3个相同

2个不同 = 2个相同 - 3个相同*3

3个不同 = 1个相同 - 2个相同*2  + 3个相同*3

4个不同 = 总数 - 1个不同 - 2个不同 - 3个不同

这边使用了容斥原理,为什么呢?

因为在计算dp的时候,有重算的部分,3个相同在2个相同里被计算了3次,2个相同在1个相同里被计算了2次,所以最后大答案里需要去掉这些部分。


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>

#define mt(a,b) memset(a,b,sizeof(a))
#define LL __int64
using namespace std;

LL dp[16][1<<16];
LL tmp[5];
LL ans[5];
char str[6];
int n;

int trans(char ch){
    if(ch>='0' && ch<='9') return ch-'0';
    else return ch-'a'+10;
}
int cal(int s){
    int num = 0;
    while(s){
        if(s&1) num++;
        s >>= 1;
    }
    return num;
}
int main(){
    while(~scanf("%d",&n)){
        mt(dp,0);
        mt(ans,0);
        for(int i=0;i<n;i++){
            scanf("%s",str);
            for(int s=1;s<16;s++){
                int t = 0;
                if(s&8) t+=trans(str[0])*(1<<12);
                if(s&4) t+=trans(str[1])*(1<<8);
                if(s&2) t+=trans(str[2])*(1<<4);
                if(s&1) t+=trans(str[3]);
                dp[s][t]++;
            }
        }

        int big = (1<<16);

        for(int s=1;s<16;s++){
            int x = cal(s);
            for(int t=0;t<big;t++){
                tmp[x] += dp[s][t]*(dp[s][t]-1)/2;
            }
        }

        ans[1] = tmp[3];
        ans[2] = tmp[2] - 3*tmp[3];
        ans[3] = tmp[1] - 2*tmp[2] + 3*tmp[3];
        ans[4] = 1LL*n*(n-1)/2 - ans[1] - ans[2] - ans[3];

        printf("%I64d %I64d %I64d %I64d\n",ans[1],ans[2],ans[3],ans[4]);

    }


    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值