给定一个长度为n(n<=15)的数字,每一位为1~9中的一个数字,现在你可以将整个数字进行全排列打乱,问你新组成的数字中有多少个数字是m的倍数?(m<=50)
比如,S=123,总共有6种排列:123,132,213,231,312,321,其中为m=6的倍数有2个:132,312
sample input
123 6
sample output
2
全排列肯定不得行,15!卡死你,所以从状压dp入手。
dp(i,t)表示当前状态为i,且余数为t的方案数。
那就变成一个套路了:第一维枚举当前选择的状态,第二维枚举未被选择的数j,将j插入在当前状态的最后,进行转移。
dp[i ^ (1 << j)][(t * 10 + (s[j] - '0')) % m] += dp[i][t];
不过这个题目中还有一个去重的陷阱,如果1出现了两次或多次,那么你就重复计算了,如何去重呢?
这是我在牛客网上看到的方法:将数据排序,对于两个相同的数A和B,什么时候会重复计算呢?选了A不选B,或者选了B不选A,这两种是重复计算的。所以我们在排序后加一个小判断:
j-1与j不相等,j-1对j没有影响
j-1与j相等,但是之前我已经选择过了j-1,此时我再选j也没有影响(就像选两个4,组成44,也是一种新的组合)
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
ll dp[1 << 16][55];
char s[50];
int main() {
//freopen("E:\\test_data\\in.txt","r",stdin);
//freopen("E:\\test_data\\out1.txt","w",stdout);
ll l, r, n, m, i, j, t;
while (scanf("%s %lld", s, &m) != EOF) {
n = strlen(s);
memset(dp, 0, sizeof(dp));
sort(s, s + n);//对数字进行排序
dp[0][0] = 1;
for (i = 0; i < (1LL << n); i++) {
for (j = 0; j < n; j++) {
if (!(i & (1LL << j))) {
if (j == 0 || s[j] != s[j - 1] || (i & (1LL << (j - 1)))) {//保证对于同一数字按序
for (t = 0; t < m; t++) {
dp[i ^ (1LL << j)][(t * 10 + (s[j] - '0')) % m] += dp[i][t];
}
}
}
}
}
printf("%lld\n", dp[(1 << n) - 1][0]);
}
}
上面的方法有点难想,那么考虑在最后结果去重。
如果在数字串中,2出现了2次,4出现了3次,那么会有多少种重复呢?答案是(2!*3!)。所以我们直接dp方案,最后除以这个数就是答案。
#include
#define int long long
using namespace std;
const int maxn=1<<16;
int dp[maxn][50];
char s[20];
int cnt[20];
signed main(){
while(scanf("%s",s)!=EOF){
int n,m;
scanf("%lld%lld",&n,&m);
int len=strlen(s);
memset(dp,0,sizeof(dp));
memset(cnt,0,sizeof(cnt));
for(int i=0;i
农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
第一行:两个整数M和N,用空格隔开。
第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。
一个整数,即牧场分配总方案数除以100,000,000的余数。
对于每一行,只有12个列,也就是说所有行的状态总数不超过2^12,所以,我们对每一行进行状态压缩,并且预处理每一种状态的合法性:一个状态合法,必须没有连续的两个1,并且对于每行,合法就另说了。
dp(s,j)表示在第j行,状态为s的方案数,从j推向j+1,可以再遍历2^12的所有状态,如果合法就累加,推导j+1行。
#include
#include
#include
#include
#include
#define int long long
using namespace std;
const int maxn=1<<12+10;
const int mod=100000000;
int dp[20][maxn];
int mp[maxn];
int mp1[maxn];
signed main(){
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int temp;
scanf("%lld",&temp);
mp[i]+=(temp<<(j-1));//状态压缩
}
}
//判断哪些状态是合法的
for(int i=0;i<(1<>1)&i);//不能同时有两个相邻的1
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1<
给定一个数组n(1<=n<=10),其中有n个数,如A1,A2....An。
再给一个能量数组mp(n,n)
任何两个数相碰撞,将会释放出mp(i,j)的能量,并且随意留下i和j中的一个。
你的任务是安排这些数的碰撞顺序,使得最后获得的能量最大。
注意多组输入,10!不可取。
sample input
2 0 4 1 0 3 0 20 1 12 0 1 1 10 0 0
sample output
4 22
很明显又是状压解决全排列的经典模型。
dp(s)表示状态s能够获得的最大能量。
则直接枚举不在s中的元素j,与在s中的元素k任意碰撞,最后留下j的最大能量就ok(为什么留下j呢?其实留下谁都无所谓,最后肯定是所有状态都搜索到。)
#include
using namespace std;
const int maxn=1<<11;
int dp[maxn];
int mp[12][12];
void solve(int n){
for(int i=0;i