【状压DP】
【状压DP】
O(2^n)
n<20时常用(1e6)
把状态压到一个二进制数里
TSP问题
拓展:轮廓线dp,插头dp
概念
利用整型status表示状态,利用status位信息
->某个样本是否还能使用,然后利用信息进行尝试
写出尝试的递归函数->记忆化搜索->严格位置依赖的动态规划->空间压缩等优化
状态
一般是2^k个
status范围:(0-2^k-1)
->一般样本量在20个以内
例题
我能赢嘛
https://leetcode.cn/problems/can-i-win/description/
题目大意
思路
※任何动态规划都只关注最关键的可变参数,被决定的可变参数不用管!不重要
两个可变参数
status:还有哪些数字可以使用
rest:被status决定 m-已经被挑走的部分就是rest
->只对status挂缓存表(dp数组)
代码
int n,m;
/*
f:数字范围1-n,当前的先手面对status给定的数字状态
在累加和还剩rest的情况下
当前的先手能不能赢
状态表示:
刚开始是全1(0位不用)
1 1 1 1 1 1 1 1
7 6 5 4 3 2 1 0
有用过就置0
1 0 1 1 0 1 1 1
7 6 5 4 3 2 1 0
dp[status]==0 没算过
dp[status]==1 算过,答案为true
dp[status]==-1 算过,答案为false
*/
vector<int> dp;
bool f(int status,int rest){
if(rest<=0) return false;
if(dp[status]!=0) return (dp[status]==1);
bool ans=0;
for(int i=1;i<=n;i++){
//注意这里递归是怎么调的:对手做先手的情况下把这个位数调走了 但没赢->我调走了我能赢->break
if((status&(1<<i))!=0 && !f((status^(1<<i)),rest-i)){
ans=1;
break;
}
}
dp[status]=(ans?1:-1);
return ans;
}
bool canIWin(int maxChoosableInteger, int desiredTotal){
n=maxChoosableInteger;
m=desiredTotal;
dp.resize((1<<(n+1))+5,0);
if(desiredTotal==0) return true;
if((maxChoosableInteger+1)*maxChoosableInteger/2<desiredTotal) return false;
return f(((1<<(maxChoosableInteger+1))-1),desiredTotal);
}
题目积累
https://ac.nowcoder.com/acm/contest/75174/F
https://www.acwing.com/problem/content/description/293/
小红的陡峭值(五)(easy)
https://ac.nowcoder.com/acm/contest/103152/F
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const ll MOD=1e9+7;
/*
【思路】
n = 10 -> n!
n = 15~25 -> 2^n可接受 ->状态压缩 -> 状压dp
【状压dp介绍】
o o o o o o o o o o o o o o o o o o
1 0 1 1 0 0 0 0 1 0 0 0 0 0 1 1 0 1 <= (1<<19)-1
->S
dp[S] -> a[1]选了 a[2]没选 a[3]a[4]选了 ......
dp[S]这个状态下选择a[17] -> dp[S|(1<<17)]
dp[S][a[16]] -> dp[S|(1<<17)][a[17]] 代价:多了个陡峭值abs(a[16]-a[17]) * f[cnt-2](乘上去掉这两个数的贡献) 除了16,17外的其他数字的排列个数
【状压dp】
dp[i,j] : i是状态(2^n) dp[1<<n][n]
dp[i,j] : 用了的数字的状态是 i , 用的最后一个数字是 a[j](a[j]总贡献)
状态并没有展示出顺序
最终状态:dp[1<<(n+1)-1][j]
*/
//概率问题:/n阶乘->乘逆元即可
ll qmi(ll a,ll k,ll p){
ll res=1;
while(k){
if(k&1) res=res*a%p;
k>>=1;//删去k的末位
a=a*a%p;
}
return res;
}
int n;
void solve(){
cin>>n;
vector<ll> a(n);
vector<ll> f(n+1,1);//求阶乘
vector<vector<ll>> dp( 1<<n ,vector<ll>(n));
for(int i=0;i<n;i++){
cin>>a[i];
f[i+1]=f[i]*(i+1)%MOD;
}
for(int i=0;i<1<<n;i++){
int c=0;
for(int j=0;j<n;j++){//i的二进制中1的个数
if((i>>j)&1) c++;
}
for(int j=0;j<n;j++){
if((i>>j)&1){//i的二进制第j位是1 -> dp[i][j]:已用状态是i,结尾数字式a[j]
for(int k=0;k<n;k++){
if((i>>k)&1) continue;//i二进制第k位不能为1
//考虑去哪里:dp[i,j] -> dp[i|(1<<k)][k]
//注意这里为什么不是c-2:该点还没被算进去
dp[i|(1<<k)][k]+=(dp[i][j]+abs(a[j]-a[k])*f[c-1]%MOD)%MOD;
dp[i|(1<<k)][k]%=MOD;
}
}
}
}
ll ans=0;
//计算总贡献
for(int j=0;j<n;j++){
ans+=dp[(1<<n)-1][j];
ans%=MOD;
}
//算概率:求逆元即可
ans=(ans*qmi(f[n],MOD-2,MOD))%MOD;
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}