题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=5330
★如果n小很多,真的可以暴力awa 然而n大了 暴力还是出不了奇迹
这题目前只有两篇题解,而且感觉只有代码,看了好久才看懂,于是我就写了这篇详细一丢丢的题解
题意:
有n个点(暂且称为点吧),每个点都有一个012串(三进制,且长度都为m),每两个点的距离就是(这两个三进制串的每位相减的绝对值之和)。最后要求输出 距离和为 0到2*m 的点(a,b)的对数
另外(a,b)与(b,a)等价 不能重复,且(a,a)不考虑
思路:
暴力就不说了,不可能 我已经试过水了
我们可以构造一个 dp[ i ][ j ][ k ] 表示 与状态 j 距离为 k 的个数 ,那个 i 就是更新次数 最少m次 ★注意这里为了 节约内存 ,用了一点小技巧。因为每次更新之和他上一次有关,而最后只要求 最终结果,所以上上次及更上次都没必要保存,我们只需要 这次和上次 的结果 now 和 pre(这应该是叫 滚动数组?)
至于状压当然是 三进制状压,第二维开多大就看 3的11次方多大咯~
对于 答案为什么要除2 的问题,题意里面说清楚了,就是我们算了(a,b)和(b,a),所以要除2。那有人可能会问,我可以在 循环里面先除2 答案直接输出 吗?答案是 不行,如果ans[j]+=1ll*num[i]*dp[now][i][j]/2;
这里的后面是 3*5/2 那岂不是损失了部分答案 算少了?所以不行。
★我自己也有个小问题没解决,如果不用 1ll ,把所有的数组开long long ,为什么会T,好奇怪。。。。
代码:
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<deque>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=2e5+5;
const int sz=1<<16;
const int inf=2e9;
const int mod=1e9+7;
const double pi=acos(-1);
typedef long long LL;
int n,m;
int p[12],num[maxn]; //p数组是3进制要用的 num是记录出现次数
int dp[2][maxn][25]; // 详见上文
LL ans[25]; //最终答案的二倍
template<class T>
inline void read(T &x)
{
char c;x=1;
while((c=getchar())<'0'||c>'9') if(c=='-') x=-1;
T res=c-'0';
while((c=getchar())>='0'&&c<='9') res=res*10+c-'0';
x*=res;
}
int main()
{
p[0]=1;
for(int i=1;i<=11;i++) p[i]=p[i-1]*3;
// cout<<p[11]<<endl;
int t; read(t);
while(t--){
read(n); read(m);
memset(num,0,sizeof num);
for(int i=1;i<=n;i++){
char c[20];
scanf("%s",c);
int sum=0;
for(int j=1;j<=m;j++){
sum=sum*3+c[j-1]-'0';
}
num[sum]++;
}
int now=0,pre=1;
memset(dp[now],0,sizeof dp[now]);
for(int i=0;i<p[m];i++) dp[now][i][0]=num[i];
for(int i=0;i<m;i++){
swap(now,pre);
memset(dp[now],0,sizeof dp[now]);
for(int j=0;j<p[m];j++){
for(int k=0;k<=2*m;k++){
if(dp[pre][j][k]){
int tmp=j/p[i]%3;
for(int l=0;l<=2;l++){
dp[now][j-(tmp-l)*p[i]][k+abs(tmp-l)]+=dp[pre][j][k];
}
}
}
}
}
memset(ans,0,sizeof ans);
for(int i=0;i<p[m];i++){
if(num[i]){
ans[0]+=1ll*num[i]*(num[i]-1);
for(int j=1;j<=2*m;j++){
ans[j]+=1ll*num[i]*dp[now][i][j];
}
}
}
for(int i=0;i<=2*m;i++) cout<<ans[i]/2<<endl;
}
return 0;
}