ZOJ 3596 Digit Number【状态压缩】【BFS】

6 篇文章 0 订阅
6 篇文章 0 订阅

题目链接

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;
}

顺便跑组数据感叹下数学的神奇:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值