题目链接
http://icpc.moe/onlinejudge/showProblem.do?problemId=4680
思路
给你n,m,问n的倍数中,最小的,只用了m个数字的(可重复用)是什么。
这是我第一次见到这么鬼畜的题,看着题解都打了一下午。
首先是状态压缩,用十位二进制数表示选了哪些数,后面跟三位十进制数表示当前的数除以n的余数。
然后用BFS保证位数递增,然后大循环里新加的数从小到大遍历,这样就能保证小的数在前面出现。这样一来设一个vis,后来出现的状态一样的数就可以不用考虑了,因为比之前的数小。
然后对于每一个状态推出新的余数和状态,直到用掉的数的个数正好为m,且余数为0就行。
然后最后输出答案的时候还牵扯到大数除法,模拟手工除法就行。
在这之前我一直以为状态压缩仅在dp里用到,搜索里用到还是第一次见,涨姿势了。
不禁让我想起某大神的一段话:
前一个状态直接推出后一个状态,这是递推。
前面某几类状态推出后一个状态,这是动态规划。
前面所有的状态推出后一个状态,这是暴力搜索。
也就是说,无论什么算法,其实都是状态与状态之间的转移罢了,那么“状态压缩”这种手段,用在搜索里也就没什么稀奇的了。不同的仅仅是,动态规划中每个状态一旦推出立刻是最优解,而搜索里这仅仅是记录状态的一种手段而已。
AC代码
#include <bits/stdc++.h>
using namespace std;
struct node_t
{
bool vis;
char num;
int cnt;
int pre;
}
node[(1<<10)*1000+10];//前10位表示用掉了哪些数,后三位表示余数
string bfs(int n, int m)
{
queue<int> que;
que.push(0);
node[0].vis=1;
node[0].pre=-1;
node[0].cnt=0;
while(!que.empty())//从高位到低位进行广搜,每次在后面加一个数
{
int cur=que.front(); que.pop();
for(int i=0 ; i<10 ; ++i)//从小到大遍历加的数
{
if(cur==0 && i==0) continue;//首位不能为0
int mod=((cur%1000)*10+i)%n;//计算新余数
int state=((cur/1000)|(1<<i))*1000+mod;//计算新状态
if(node[state].vis) continue;//优先取先出现的(即位数小的)
node[state].vis=1;
node[state].num='0'+i;
node[state].pre=cur;
node[state].cnt=node[cur].cnt+(((cur/1000)&(1<<i))?0:1);//用了几个数
if(node[state].cnt==m && mod==0)
{
string t;
while(node[state].pre!=-1)
{
t+=node[state].num;
state=node[state].pre;
}
reverse(t.begin(),t.end());
return t;
}
if(node[state].cnt<=m) que.push(state);
}
}
return "Impossible";
}
string get_quotient(string a,int b)//模拟手工除法
{
int cur=0,i=0;
string res;
while(cur<b)
{
cur=cur*10+a[i++]-'0';
}
res+=cur/b+'0';
while(i<(int)a.length())
{
cur=(cur%b)*10+a[i++]-'0';
res+=cur/b+'0';
}
return res;
}
void init()
{
memset(node,0,sizeof node);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
init();
int n,m;
scanf("%d%d",&n,&m);
string ans=bfs(n,m);
if(ans=="Impossible")
printf("%s\n",ans.c_str());
else
printf("%s=%d*%s\n", ans.c_str(), n, get_quotient(ans,n).c_str());
}
return 0;
}
顺便跑组数据感叹下数学的神奇: