题型: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;
}