zoukankan      html  css  js  c++  java
  • 「状压DP」「暴力搜索」排列perm

    「状压DP」「暴力搜索」排列

    题目描述:

    题目描述

    给一个数字串 s 和正整数 d, 统计 sss 有多少种不同的排列能被 d 整除(可以有前导 0)。例如 123434 有 90 种排列能被 2 整除,其中末位为 2 的有 30 种,末位为 4 的有 60 种。

    输入格式

    输入第一行是一个整数 TTT,表示测试数据的个数,以下每行一组 s 和 d,中间用空格隔开。s 保证只包含数字 0,1,2,3,4,5,6,7,8,9

    输出格式

    每个数据仅一行,表示能被 d 整除的排列的个数。

    输入输出样例

    输入 #1

    7
    000 1
    001 1
    1234567890 1
    123434 2
    1234 7
    12345 17
    12345678 29

    输出 #1

    1
    3
    3628800
    90
    3
    6
    1398

    说明/提示

    100% 的数据满足:s 的长度不超过 10,1≤d≤1000,1≤T≤15。

    在前三个例子中,排列分别有 1,3,36288001 种,它们都是 1 的倍数。

    解法1:状压DP

    思路:

    s的长度很短,不是暴搜就是状压,然鹅这道题都可以用

    大多数状压中都是当前某个状态对之后于此相关状态产生影响

    因此可以对整个序列的长度进行状压,详见代码

    代码:

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<stack>
    using namespace std;
    const int maxn=(1<<10)+5,INF=0x3f3f3f3f;
    int n,m,f[maxn][1000],sum[11],mol,a[maxn],vis[maxn];
    char str[1000];
    inline int read(){
    	int s=0,w=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    	return s*w;
    }
    int main(){
    	freopen("a.in","r",stdin);
    	int t=read();
    	while(t--){
    		memset(f,0,sizeof(f));
    		memset(a,0,sizeof(a));
    		memset(sum,0,sizeof(sum));
    		scanf("%s",str);
    		int n=strlen(str);
    		mol=read();
    		for(int i=1;i<=n;i++){
    			a[i]=str[i-1]-'0';
    		}
    		int maxs=(1<<n)-1;
    		f[0][0]=1;//0序列为000....的方案肯定只有一种
    		for(int s=0;s<=maxs;s++){//s代表所选的数的状态,00101代表选了第一个数和第三个数的状态
    			memset(vis,0,sizeof(vis));//vis记录这个数是否被访问过,因为相同的数会被重复计入方案,
    			                          //如,对11230排列,11230和11230会被重复计算,用vis就可以去重,当然也可以不用vis用数学方法去重,即记录所有数出现的个数,最后ans/=cnt[i]!(i=0~9)
                            for(int i=1;i<=n;i++){   //i表示下一个要选的数
    				if(s&(1<<(i-1))||vis[a[i]])continue;//s&(i<<(i-1))表示当前枚举的数被包含在了当前状态s里面,不用再次计算
    				vis[a[i]]=1;
    				for(int k=0;k<mol;k++){//枚举所有余数k,f[s][k]代表所选的数的状态为s,且余数为k时的总排列数
    					f[s|(1<<(i-1))][(k*10+a[i])%mol]+=f[s][k];//解释:因为我们的状态是从小到大枚举的,所以f[s][k]会对
                                             //它的所有下一个状态产生影响,而下一个状态的余数恰恰是(k*10+a[i])%mol
                                             //举个例子,序列1234,模数是4,当前状态0011代表12的排列,12会转移到124和123即 原数*10+a[i],相应的余数就会变成 (余数*10+a[i])%mol,即0和3
    					//	cout<<f[s|(1<<(i-1))][(k*10+a[i])%mol]<<endl;
    				}
    			}
    		}
    		cout<<f[maxs][0]<<endl;
    	}
    }
    

    解法2:暴搜

    相对于状压而言,暴搜更好想一些,就是从低位依次枚举至高位,但是时间消耗更大,洛谷上会T两个点,可能剪剪枝会过

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<stack>
    using namespace std;
    const int maxn=(1<<10)+5,INF=0x3f3f3f3f;
    int n,m,f[maxn][1000],sum[11],mol,a[maxn],vis[maxn],ans;
    char str[1000];
    inline int read(){
    	int s=0,w=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    	return s*w;
    }
    void DFS(int now,long long x){//看数据范围,不开long long会炸,x代表当前的数,now代表当前位数
    	if(now>n){//now>n说明x是末状态
    		if(x%mol==0)ans++;
    		return;
    	}
    	for(int i=0;i<=9;i++){
    		if(sum[i]){
    			sum[i]--;//为保证当前这一位选取这个元素对这一位选取其他元素没影响,所以自减后要自加回来
    			DFS(now+1,x*10+i);
    			sum[i]++;
    		}
    	}
    }
    int main(){
    	freopen("a.in","r",stdin);
    	int t=read();
    	while(t--){
    		memset(a,0,sizeof(a));
    		memset(sum,0,sizeof(sum));
    		string str;
    		cin>>str;
    		mol=read();
    		n=str.size();
    		ans=0;
    		for(int i=0;i<str.size();i++){
    			a[i]=str[i]-'0';
    			sum[a[i]]++;
    		}
    		DFS(1,0);
    		cout<<ans<<endl;
    	}
    }
    

    解法3:next_permutation生成全排列

    应该所有人都想过用全排列来写,但是时间开销很大,吾日观洛谷,发现STL中的几个比较有趣的函数:

    next_permutation:从原递增序列中求出所有全排列

    prev_permutation:从原递减序列中求出所有全排列

    atol:将字符串转换为数列

    详见代码

    代码1:不用atol正常写

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<stack>
    using namespace std;
    const int maxn=(1<<10)+5,INF=0x3f3f3f3f;
    int n,m,f[maxn][1000],sum[11],mol,a[maxn],vis[maxn],ans;
    char str[1000];
    inline int read(){
    	int s=0,w=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    	return s*w;
    }
    int main(){
    	freopen("a.in","r",stdin);
    	int t=read();
    	while(t--){
    		memset(a,0,sizeof(a));
    		memset(sum,0,sizeof(sum));
    		string str;
    		cin>>str;
    		mol=read();
    		n=str.size();
    		ans=0;
    		for(int i=0;i<n;i++){
    			a[i]=str[i]-'0';
    		}
    		sort(a,a+n);//严格要求必须递增,否则全排列不对
    		do{
    			long long temp=0;
    			for(int i=0;i<n;i++)temp=temp*10+a[i];//全排列是保存在数组a里的,通过这种方法取出来
    			if(temp%mol==0)ans++;
    		}while(next_permutation(a,a+n));
    		cout<<ans<<endl;
    	}
    }
    

    代码2:用atol

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<stack>
    using namespace std;
    const int maxn=(1<<10)+5,INF=0x3f3f3f3f;
    int n,m,f[maxn][1000],sum[11],mol,a[maxn],vis[maxn],ans;
    char str[1000];
    inline int read(){
    	int s=0,w=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    	return s*w;
    }
    int main(){
    	freopen("a.in","r",stdin);
    	int t=read();
    	while(t--){
    		memset(a,0,sizeof(a));
    		memset(sum,0,sizeof(sum));
    		char str[maxn];//string排序排的是字符串,char排的是字符
    		cin>>str;
    		mol=read();
    		n=strlen(str);
    		ans=0;
    		sort(str,str+n);
    		do{
    			long long temp=atoll(str);//直接转
    			
    			if(temp%mol==0)ans++;
    		}while(next_permutation(str,str+n));
    		cout<<ans<<endl;
    	}
    }
    
  • 相关阅读:
    网络七层参考模型(OSI)
    TCP/IP体系结构
    VC调用外部程序接口
    处事八条
    HFC网络
    sql server 2005中的分区函数用法(partition by 字段) 
    Java实现的几个常用排序算法详细解读
    Java中读取字符文件类FileReader
    如何在Java中进行图片剪裁
    从零开始构建HTML 5 Web页面
  • 原文地址:https://www.cnblogs.com/614685877--aakennes/p/13195811.html
Copyright © 2011-2022 走看看