题目链接
这道题的其实也可以用数学知识来解,不过需要高中数学
30分
\(s\le5\),那么枚举\(0\)到\(99999\),判断每个数字和输入的是否相同就行了
50分
50分没有什么准确的做法,应该是暴力写的好或者是正解写的丑的
Part.1
先说说我们今天比赛时一个不认识的dalao的做法
他把每个序列进行排序构造出\(MAX\)和\(MIN\),在这个范围内做30分的做法就行
代码应该可是实现我就不贴了
Part.2
我自己的做法
首先我观察到如果去枚举序列,复杂度会很高
我们考虑枚举d的整数倍,在考虑是否能够被构造出来
怎么判断能否被构造呢
先开一个桶,把序列中每个数字出现的次数存下来
对于每次枚举出来的d的倍数,另开一个桶用相同的方式分解
然后比较两个桶,如果两个同相同这这个数可以被构造出来
然后我发现过\(d=1\)时这种方法会T飞
所以当\(d=1\)时我们考虑组合数给他算出来
首先假设序列长为\(n\),桶用\(t_i\)表示,就可以推出方案数为
\[\frac{A_n^n}{\Pi_{i=0}^{9}A_{ti}^{ti}}\]
然后我又发现假如\(d=2\)时,方案数只和末位奇偶有关
假设序列长为\(n\),桶用\(t_i\)表示,偶数个数为\(m\),就可以推出方案数为
\[\frac{A_{n-1}^{n-1}\times A_m^m}{\Pi_{i=0}^{9}A_{ti}^{ti}}\]
用这种方法同时可以推出\(d=5\)和\(d=10\)的情况,另外假如全是0,则只有一种情况
#include
using namespace std;
const int N = 13;
int T,t[N],a[N],n,m,k,maxn;
int cnt,x;
bool flag;
string s;
inline void work_1()
{
cnt = 1;
for(register int i = 2;i <= n;i ++) cnt *= i;
for(register int i = 0;i <= 9;i ++)
{
if(!t[i]) continue;
for(register int j = 2;j <= t[i];j ++) cnt /= j;
}
printf("%d\n",cnt);
}
inline void work_2()
{
cnt = 0;
for(register int i = 0;i <= 9; i += 2)
{
if(t[i] > 0) cnt +=t[i];
}
if(!cnt)
{
puts("0");
return ;
}
for(register int i = 2;i < n;i ++) cnt *= i;
for(register int i = 0;i <= 9;i ++)
{
if(!t[i]) continue;
for(register int j = 2;j <= t[i];j ++) cnt /= j;
}
printf("%d\n",cnt);
}
inline void work_3()
{
cnt = t[5] + t[0];
if(!cnt)
{
puts("0");
return ;
}
for(register int i = 2;i < n;i ++) cnt *= i;
for(register int i = 0;i <= 9;i ++)
{
if(!t[i]) continue;
for(register int j = 2;j <= t[i];j ++) cnt /= j;
}
printf("%d\n",cnt);
}
inline void work_4()
{
cnt = t[0];
if(!cnt)
{
puts("0");
return ;
}
for(register int i = 2;i < n;i ++) cnt *= i;
for(register int i = 0;i <= 9;i ++)
{
if(!t[i]) continue;
for(register int j = 2;j <= t[i];j ++) cnt /= j;
}
printf("%d\n",cnt);
}
int main()
{
//freopen("arrange.in","r",stdin);
//freopen("arrange.out","w",stdout);
cin >> T;
while(T--)
{
memset(t,0,sizeof(t));
cin >> s >> k;
n = s.size();
for(register int i = 0;i < n;i ++) t[s[i] - '0'] ++;
m = n - t[0];
if(!m)
{
puts("1");
continue;
}
if(k == 1)
{
work_1();
continue;
}
if(k == 2)
{
work_2();
continue;
}
if(k == 5)
{
work_3();
continue;
}
if(k == 10)
{
work_4();
continue;
}
cnt = maxn = 0;
for(register int i = 9;i >= 0;i --)
{
for(register int j = 1;j <= t[i];j ++) maxn = (maxn<<3)+(maxn<<1) + i;
}
for(register int i = 1;i * k <= maxn;i ++)
{
x = i * k; flag = 1; memset(a,0,sizeof(a));
while(x >= 9) a[x % 10] ++,x /= 10;
a[x] ++;
for(register int j = 1;j <= 9;j ++)
{
if(t[j] == a[j]) continue;
flag = 0;
break;
}
if(flag)
{
cnt ++;
}
}
printf("%d\n",cnt);
}
}
100分
\(s\)的长度不会超过\(10\)
若没有重复的数字,则所有可能情况为\(362880\)
若有所有重复的数字,则所有情况不会超过\(362880\)
又\(T\le 15\),所有情况不会超过\(5443200<10^8\)
所以此题正解为勇敢暴力,枚举即可,不用剪枝
暴力枚举所有的序列,枚举序列可以用next_permutation()
#include
using namespace std;
const int N = 13,mod = 19260817;
int T,cnt,k,len,a[N];
long long sum;
string s;
int main()
{
cin >> T;
while(T --)
{
cin >> s >> k;
len = s.size();cnt = 0;
for(register int i = 1;i <= len;i ++) a[i] = s[i-1] - '0';
sort(a+1,a+1+len);
do
{
sum = 0;
for(register int i = 1;i <= len;i ++) sum = (sum<<3)+(sum<<1)+a[i];
if(sum % k == 0) cnt ++;
}while(next_permutation(a+1,a+1+len));
printf("%d\n",cnt);
}
}