狀壓dp水題練習
吉比特筆試第二題(狀壓dp)
題目大意:
給定一個長度爲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 <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
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 <bits/stdc++.h>
#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<n;i++){
cnt[s[i]-'0']++;
}
dp[0][0]=1;
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(!(i&(1<<j))){
for(int t=0;t<m;t++){
dp[i|(1<<j)][(t*10+s[j]-'0')%m]+=dp[i][t];
}
}
}
}
int ans=dp[(1<<n)-1][0];
for(int i=1;i<=9;i++){
if(cnt[i]){
int temp=1;
for(int j=1;j<=cnt[i];j++){
temp*=j;
}
ans/=temp;
}
}
printf("%lld\n",ans);
}
return 0;
}
洛谷P1879玉米田
題目大意:
農場主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 <iostream>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#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<<m);i++){
mp1[i]=!((i<<1)&i&&(i>>1)&i);//不能同時有兩個相鄰的1
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<m);j++){
if(mp1[j]&&((mp[i]&j)==j)){
for(int k=0;k<(1<<m);k++){
if(!(j&k)){
dp[i][j]+=dp[i-1][k];
dp[i][j]%=mod;
}
}
}
}
}
int ans=0;
for(int i=0;i<(1<<m);i++){
ans+=dp[n][i];
ans%=mod;
}
printf("%lld\n",ans);
return 0;
}
ZOJ3471:Most Powerful
題目大意:
給定一個數組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 <bits/stdc++.h>
using namespace std;
const int maxn=1<<11;
int dp[maxn];
int mp[12][12];
void solve(int n){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
scanf("%d",&mp[i][j]);
}
}
memset(dp,0,sizeof(dp));
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(!(i&(1<<j))){
for(int k=0;k<n;k++){
if(i&(1<<k)){
dp[i|(1<<j)]=max(dp[i|(1<<j)],dp[i]+mp[k][j]);
}
}
}
}
}
printf("%d\n",dp[(1<<n)-1]);
}
signed main(){
int n;
while(scanf("%d",&n)!=EOF&&n){
solve(n);
}
}