[SCOI2007]排列 luogu P4163

20 篇文章 0 订阅
18 篇文章 0 订阅

题面

在这里插入图片描述

分析

蒟蒻真的真的不会搞如此高级的状压啊啊啊啊啊啊啊
于是一发爆搜打了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.没有了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值