题面
分析
蒟蒻真的真的不会搞如此高级的状压啊啊啊啊啊啊啊
于是一发爆搜打了50分
如果要打爆搜的话,要注意判重的问题
若是
000
的话,它的全排列(不管第一个1和第二个1是一样的)有6种,但实际这些都是重复的
于是我开了一个一亿的vis数组,(没怎么考虑空间开销),幸好没爆
但判重其实可以用一个比较巧妙的数学方法(正解也是这样),一开始统计每一个数出现的次数cnt[i],然后把结果依次除掉
c
n
t
[
i
]
!
cnt[i]!
cnt[i]!即可
其依据是,若某个数出现了i次,则长度为i,值全部是某个数的序列的全排列个数为
i
!
i!
i!,即原排列中每个数都被重复计算了
i
!
i!
i!次,于是直接除掉
i
!
i!
i!
(如3010012,cnt[0]=3,cnt[0]!=6,cnt[1]=2,cnt[1]!=2即全排列中3010012出现了cnt[0]
×
\times
×cnt[1]次,实际上只该有一次)
本题正解状压DP,十分玄学,非功力高深不可得
方程:
d p [ i | ( 1 < < j ) ] [ ( k * 1 0 + f [ j ] ) % d ) ] + = d p [ i ] [ k ]
意思就是,我们把这些数的选和不选两种状态化为0和1,1选,0不选,然后对于每一种选择状态i,我们可以枚举其子状态 i | ( 1 < < j ) ——即 向i中添加未添加过的第j个数的状态 ——,此时新状态组成的数对d取余数得到的数等于( k * 1 0 + f [ j ] ) % d )
由于我们由方程得到,上一行的状态可以更新下一行的状态,又有dp[0][0]=1
我们就可以以此来扩展状态,拓展到dp[len<<1-1][0](len 指字符串长度)
方程写出来了,代码就可以根据方程的要求来写出来了
注意判重
code
DFS(50pts 其实可以搜过的,懒得改)
#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;i++)
#define anti_loop(i,start,end) for(register int i=start;i>=end;i--)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define min(a,b) ((a<b)?a:b)
#define ll long long
inline int read()
{
int neg=1;int ans=0;char r=getchar();
while(r>'9'||r<'0'){if(r=='-')neg=-1;r=getchar();}
while(r>='0'&&r<='9'){ans=ans*10+r-'0';r=getchar();}
return ans*neg;
}
const int maxn=15;
char data[maxn];
bool vis[maxn];
bool cic[100000000];
int num;
int yu;
int T;
ll res;
void dfs(int layer,ll sum)
{
if(layer==num)
{
if(sum<100000000&&cic[sum])return;
else if(sum<100000000)cic[sum]=true;
if(sum%yu==0)res++;
return;
}
for(int i=0;i<=num-1;i++)
{
if(vis[i])continue;
vis[i]=true;
dfs(layer+1,sum*10+data[i]-'0');
vis[i]=false;
}
}
int main()
{
//freopen("datain.txt","r",stdin);
clean(data,10);T=read();
while(T--)
{
scanf("%s",&data);num=strlen(data);
yu=read();clean(vis,false);res=0;clean(cic,false);
dfs(0,0);
printf("%lld\n",res);
}
return 0;
}
DP
#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;i++)
#define anti_loop(i,start,end) for(register int i=start;i>=end;i--)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define min(a,b) ((a<b)?a:b)
#define ll long long
const int maxn=15;//len
const int maxk=1100;//yu
const int maxs=1<<13;//maxium sitiuation
char data[maxn];
int f[maxn];
int Tt,mod;
int dp[maxs][maxk];
int show_up[maxn];
inline int read()
{
int neg=1;int ans=0;char r=getchar();
while(r>'9'||r<'0'){if(r=='-')neg=-1;r=getchar();}
while(r>='0'&&r<='9'){ans=ans*10+r-'0';r=getchar();}
return ans*neg;
}
int main()
{
freopen("perm3.in","r",stdin);
Tt=read();
while(Tt--)
{
scanf("%s%d",data,&mod);clean(dp,0);
clean(show_up,0);clean(f,0);
int len=strlen(data);
dp[0][0]=1;
loop(i,0,len-1)
{
f[i]=data[i]-'0';
show_up[f[i]]++;
}
loop(i,0,(1<<len)-1)
{
loop(k,0,mod-1)
{
if(dp[i][k])
{
loop(j,0,len-1)
{
if(i&(1<<j))continue;
dp[i|(1<<j)][(k*10+f[j])%mod]+=dp[i][k];
}
}
}
}
int chu=1;
loop(i,0,9)//
{
while(show_up[i]>0)
{
chu*=show_up[i];
show_up[i]--;
}
}
printf("%d\n",dp[(1<<len)-1][0]/chu);//
}
return 0;
}
学到的东西
1.vis数组到底可以开多大????
首先来回顾一下进制关系
<1>bit,比特,计算机中最小的数据单位
<2>B,字节,相当于8位二进制数(8个bit)
<3>其他单位(从小到大,相邻单位进率为2的10次方):KB,MB,GB,TB,PB,EB,ZB,YB,NB,DB,CB
而已知,当值为 false 的时候,bool型实际上存储的是 0x00,为ture时实际上存储的是 0x01,即一个bool占用1个字节
而题中给了128 MB,假设装满,
128
M
B
=
128
×
2
10
K
B
=
128
×
2
20
B
=
1.34217728
×
10
8
B
128MB=128\times{2}^{10}KB=128\times{2}^{20}B=1.34217728\times {10}^{8}B
128MB=128×210KB=128×220B=1.34217728×108B
即你需要开一个大小为134217728个bool的数组才可以用完空间,那么,1亿个bool就还好啦
当然,相应的,在128MB下,由于
int a; sizeof(a)=4;
char a; sizeof(a)=1;
long long a; sizeof(a)=8;
double a; sizeof(a)=8;
(sizeof返回单位为B)
于是相应的最大数组大小为
int a[33554432];//3千万
char a[134217728];//1亿3千万
long long a[1677216];//167万
double a[1677216];//167万
2.没有了