状压杂题
注意状压时状态的设计:
如果状态之间更新涉及到上一个节点是什么(例如 \(i\) 点和 \(j\) 点之间的距离),那么需要加上最后节点一个维度。
否则直接设置状态即可。
经典/板题
P1879 [USACO06NOV] Corn Fields G
第一道状压。
首先读入处理每一行的肥沃土地,记在 \(ff\) 数组中。
设 \(f[s][i]\) 表示当前为状态 \(s\),\(dp\) 到第 \(i\) 行的方案数总和。
当上下状态没有交集,且上一行和本行合法,且本行之内没有相邻的点(用 \(i\&(i<<1)\) 和 \(i\&(i>>1)\) 来判断即可),那么可以转移。
最终计入答案就是 \(\sum_{i=1}^n f[(1<<n)-1][i]\)。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 15 + 5;
const int M = ( 1 << 13 );
const int mod = 100000000;
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , ff[N] , ans , f[M][N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 , x ; j <= m ; j ++ )
ff[i] = ( ff[i] << 1 ) + read();
f[0][0] = 1;
for ( int i = 1 ; i <= n ; i ++ )
for ( int s = 0 ; s < ( 1 << m ) ; s ++ )
if ( ( ( s & ff[i] ) == s ) && ! ( s & s << 1 ) && ! ( s & s >> 1 ) )
for ( int pre = 0 ; pre < ( 1 << m ) ; pre ++ )
if ( ( ( pre & ff[i-1] ) == pre ) && ! ( pre & s ) && ! ( pre & pre << 1 ) && ! ( pre & pre >> 1 ) )
( f[s][i] += f[pre][i-1] ) %= mod;
for ( int s = 0 ; s < ( 1 << m ) ; s ++ ) ( ans += f[s][n] ) %= mod;
cout << ans << endl;
return 0;
}
P1896 [SCOI2005] 互不侵犯
很经典的状压题()
我们设置 \(f[i][j][s]\) 表示前 \(i\) 行,用了 \(j\) 个国王,当前行使用国王状态为 \(s\) 的方案数。
那么我们可以预处理不相邻的二进制状态,记录在 \(ok\) 数组中,并预处理每一个状态对应的使用国王个数来减少时间复杂度。
枚举本行和上一行安置国王的状态并进行转移统计即可。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 10 + 5;
const int K = 100 + 5;
const int M = ( 1 << 9 ) + 5;
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , k , ok[M] , cnt[M] , f[N][K][M] , tot , ans;
int count ( int s ) { int cnt = 0; while ( s ) cnt += ( s & 1 ) , s >>= 1; return cnt; }
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , k = read();
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
if ( ! ( ( s << 1 | s >> 1 ) & s ) ) ok[++tot] = s , cnt[s] = count(s);
f[0][0][0] = 1;
for ( int i = 1 ; i <= n ; i ++ )
for ( int prev = 1 ; prev <= tot ; prev ++ )
{
int pre = ok[prev];
for ( int nowv = 1 ; nowv <= tot ; nowv ++ )
{
int now = ok[nowv];
if ( ! ( ( pre << 1 | pre >> 1 | pre ) & now ) )
for ( int j = 0 ; j <= k ; j ++ ) //已经放了几个东西
if ( j >= cnt[now] ) f[i][j][now] += f[i-1][j-cnt[now]][pre];
}
}
for ( int i = 1 ; i <= tot ; i ++ ) ans += f[n][k][ok[i]];
cout << ans << endl;
return 0;
}
P8756 [蓝桥杯 2021 省 AB2] 国际象棋
显然是状压。
设置 \(f_{i,j,k,l}\) 表示到第 \(i\) 列,本行和上一行状态分别为 \(j,k\),马的个数为 \(l\) 的时候的方案数。
那么枚举列,三行状态和马的个数并进行转移即可。
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define eb emplace_back
#define pb pop_back
#define endl '\n'
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int inf = 0x3f3f3f3f;
const int M = ( 1 << 7 ) + 5;
const int mod = 1e9 + 7;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , K , f[105][M][M][25] , cnt[M] , ans;//行数,本行状态,上行状态,马的个数。
int check ( int i , int j , int k )
{
return ! ( i & j << 2 ) && ! ( i & j >> 2 ) && ! ( i & k << 1 ) && ! ( i & k >> 1 ) && ! ( j & k << 2 ) && ! ( j & k >> 2 );
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , K = read();
swap ( n , m );
for ( int i = 0 ; i < ( 1 << m ) ; i ++ ) cnt[i] = __builtin_popcount(i);
f[0][0][0][0] = 1;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 0 ; j < ( 1 << m ) ; j ++ )//本行
for ( int k = 0 ; k < ( 1 << m ) ; k ++ )//上一行
for ( int l = 0 ; l < ( 1 << m ) ; l ++ )//上上一行
for ( int s = cnt[j] + cnt[k] + cnt[l] ; s <= K ; s ++ )//数量
if ( check ( j , k , l ) )
( f[i][j][k][s] += f[i-1][k][l][s-cnt[j]] ) %= mod;
for ( int i = 0 ; i < ( 1 << m ) ; i ++ )
for ( int j = 0 ; j < ( 1 << m ) ; j ++ )
( ans += f[n][i][j][K] ) %= mod;
cout << ans << endl;
return 0;
}
P4329 [COCI2006-2007#1] Bond
不用设置二维状态的原因:任务和任务之间是独立的。
那么我们对于每一个状态,查一下已经用了多少人数,并枚举当前这个人应该做哪个任务。
设 \(f[i]\) 表示任务集合状态为 \(i\) 的时候的最大概率,\(dp\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define mid (l+r>>1)
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define double long double
const int N = 20 + 5;
const int M = ( 1 << 20 ) + 5;
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n;
double a[N][N] , f[M];
int count ( int s )
{
int cnt = 0;
while ( s ) { if ( s & 1 ) cnt ++; s >>= 1; }
return cnt;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 , x ; j <= n ; j ++ )
x = read() , a[i][j] = x * 0.01;
f[0] = 1;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ ) //任务的使用集合
{
int cnt = count(s);//人的个数
for ( int i = 1 ; i <= n ; i ++ )//人依次做任务 枚举这个人做哪个任务
if ( s & 1 << i - 1 )
f[s] = max ( f[s] , f[s^1<<i-1] * a[cnt][i] );
}
cout << fixed << setprecision(6) << f[(1<<n)-1] * 100 << endl;
return 0;
}
P1433 吃奶酪
简单的状压,设 \(f[s][i]\) 表示当前节点访问状态为 \(s\),最后到达的节点为 \(i\) 的情况下的最小距离。
那么我们的初始值为仅访问过一个点,且访问状态为 \(s\) 的情况,最小距离为两点间距离(可以预处理)。
最后,枚举当前层状态,枚举上层和本层节点进行转移即可。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const double inf = 0x3f3f3f3f;
const int N = 20 + 5;
const int M = ( 1 << 15 + 1 );
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
void write ( int x )
{
if ( x < 0 ) x = -x , cout.put ( '-' );
if ( x > 9 ) write ( x / 10 );
cout.put ( x % 10 + '0' );
}
int T , o , n , m , q;
double x[N] , y[N] , a[N][N] , f[N][M] , ans = inf;
double dis ( int i , int j ) { return sqrt ( ( x[i] - x[j] ) * ( x[i] - x[j] ) + ( y[i] - y[j] ) * ( y[i] - y[j] ) ); }
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) cin >> x[i] >> y[i];
for ( int i = 0 ; i <= n ; i ++ )
for ( int j = i + 1 ; j <= n ; j ++ )
a[i][j] = a[j][i] = dis ( i , j );
for ( int i = 1 ; i <= n ; i ++ )
for ( int s = 1 ; s < ( 1 << n ) ; s ++ )
f[i][s] = inf;
for ( int i = 1 ; i <= n ; i ++ ) f[i][1<<i-1] = a[0][i];
for ( int s = 1 ; s < ( 1 << n ) ; s ++ )
for ( int i = 1 ; i <= n ; i ++ )//前置节点
if ( s & 1 << i - 1 )
for ( int j = 1 ; j <= n ; j ++ )//当前节点
if ( ( s & 1 << j - 1 ) && j != i )
f[j][s] = min ( f[j][s] , f[i][s^1<<j-1] + a[i][j] );
for ( int i = 1 ; i <= n ; i ++ ) ans = min ( ans , f[i][(1<<n)-1] );
cout << fixed << setprecision(2) << ans << endl;
return 0;
}
P8733 [蓝桥杯 2020 国 C] 补给
与吃奶酪不同的一点是,本题需要提前将所有不合法的边全部舍弃,再用 \(floyd\) 预处理两点之间的最短距离并转移。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
#define getchar() cin.get()
const int N = 20 + 5;
const int M = ( 1 << 20 ) + 5;
const double inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n;
double D , x[N] , y[N] , dis[N][N] , f[M][N] , minn = inf;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , D = read();
for ( int i = 1 ; i <= n ; i ++ ) x[i] = read() , y[i] = read();
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ )
{
dis[i][j] = sqrt ( ( x[i] - x[j] ) * ( x[i] - x[j] ) + ( y[i] - y[j] ) * ( y[i] - y[j] ) );
if ( dis[i][j] > D ) dis[i][j] = inf;
}
for ( int k = 1 ; k <= n ; k ++ )
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ )
dis[i][j] = min ( dis[i][j] , dis[i][k] + dis[k][j] );
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int i = 1 ; i <= n ; i ++ )
f[s][i] = inf;
f[1][1] = 0;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ )
if ( ( s & 1 << j - 1 ) && ( s & 1 << i - 1 ) && i != j )
f[s][j] = min ( f[s][j] , f[s^(1<<j-1)][i] + dis[i][j] );
for ( int i = 1 ; i <= n ; i ++ ) minn = min ( minn , f[(1<<n)-1][i] + dis[i][1] );
cout << fixed << setprecision(2) << minn << endl;
return 0;
}
P7859 [COCI2015-2016#2] GEPPETTO
将原材料压成一个状态,和 \(m\) 种冲突的原材料直接异或起来即可。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
#define int long long
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , x[N] , y[N] , ans;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= m ; i ++ ) x[i] = read() , y[i] = read();
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
{
int flag = 1;
for ( int i = 1 ; i <= m ; i ++ ) if ( ( s & ( 1 << x[i] - 1 ) ) && ( s & ( 1 << y[i] - 1 ) ) ) { flag = 0; break; }
ans += flag;
}
cout << ans << endl;
return 0;
}
P1171 售货员的难题
经典的一类问题,还是设 \(f_{i,j}\) 表示状态为 \(i\) 的时候,到了第 \(j\) 个节点的最小权值。
那么枚举当前状态和上一个状态,看是否可以更新即可。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
const int N = 20 + 5;
const int M = ( 1 << 20 ) + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , k , a[N][N] , f[M][N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) a[i][j] = read();
memset ( f , inf , sizeof f );
f[1][1] = 0;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int j = 1 ; j <= n ; j ++ )
for ( int k = 1 ; k <= n ; k ++ )
if ( ( s & 1 << j - 1 ) && ( s & 1 << k - 1 ) && j != k && !( f[s^(1<<k-1)][j] == inf ) )
f[s][k] = min ( f[s][k] , f[s^(1<<k-1)][j] + a[j][k] );
int ans = inf;
for ( int i = 2 ; i <= n ; i ++ ) ans = min ( ans , f[(1<<n)-1][i] + a[i][1] );
cout << ans << endl;
return 0;
}
P4802 [CCO2015] 路短最
注意初始值为 \(-inf\),只有 \(f_{1,0}=0\),因为是起点。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int N = 20 + 5;
const int M = ( 1 << 18 ) + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int ans , n , m , f[M][N];
vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 , u , v , w ; i <= m ; i ++ ) u = read() , v = read() , w = read() , add ( u , v , w );
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int i = 0 ; i < n ; i ++ )
f[s][i] = -inf;
f[1][0] = 0;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int i = 0 ; i < n ; i ++ )//上一次的城市
if ( s & 1 << i )
for ( auto p : e[i] )
{
int v = p.fi , w = p.se;
if ( ! ( s & 1 << v ) ) f[s|1<<v][v] = max ( f[s|1<<v][v] , f[s][i] + w );
}
for ( int s = 0 ; s < ( 1 << n ) ; s ++ ) ans = max ( ans , f[s][n-1] );
cout << ans << endl;
return 0;
}
P8687 [蓝桥杯 2019 省 A] 糖果
只能刷表,但是思路非常自然的题目。
我们先处理每一个物品,用二进制数来记录每一个物品的状态。
然后枚举每一个状态做 \(01\) 背包即可。
内外两层枚举顺序实测没有硬性要求,故采用较易理解的先枚举物品再枚举容积(状态)的写法。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
const int N = 100 + 5;
const int M = ( 1 << 20 ) + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , k , f[M] , a[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= k ; j ++ )
a[i] |= ( 1 << read() - 1 );
memset ( f , inf , sizeof f );
f[0] = 0;
for ( int i = 1 ; i <= n ; i ++ )
for ( int s = 0 ; s < ( 1 << m ) ; s ++ )
f[s|a[i]] = min ( f[s] + 1 , f[s|a[i]] );
cout << ( f[(1<<m)-1] == inf ? -1 : f[(1<<m)-1] ) << endl;
return 0;
}
P1283 平板涂色
搜索写法的题解在搜索博客中,实测常数要小于状压。
显然我们需要预处理对于每一个 \(i\) 节点,它正上方的所有矩形,用一个 \(vector\) 记录即可。
然后我们设置 \(f_{s,i}\) 表示当前矩形染色状态为 \(s\),上一次染的颜色是 \(i\) 的最小花费。
那么我们可以枚举每一次的染色矩形,如果在 \(s\) 这个状态中,这个矩形的所有正上方矩形都被覆盖过了,那么可以转移,枚举上一次使用的颜色即可。
最后注意在全选的状态里取 \(\min\)。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
#define getchar() cin.get()
const int N = 20 + 5;
const int M = ( 1 << 16 ) + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int ans = inf , n , vis[N] , maxx , f[M][N];
struct node { int lx , ly , rx , ry , col; } a[N];
vector<int> up[N];
int check ( int s , int i )
{
for ( auto v : up[i] ) if ( ! ( s & 1 << v - 1 ) ) return 0;
return 1;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].lx = read() , a[i].ly = read() , a[i].rx = read() , a[i].ry = read() , a[i].col = read() , maxx = max ( maxx , a[i].col );
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ ) //judge j is i up
if ( a[j].rx == a[i].lx && i != j && a[j].ly <= a[i].ry && a[j].ry >= a[i].ly )
up[i].eb(j);
memset ( f , inf , sizeof f );
for ( int i = 1 ; i <= maxx ; i ++ ) f[0][i] = 1; //拿起一个刷子代价为1 方便第一次更新
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int i = 1 ; i <= n ; i ++ )//本次涂色矩形
if ( ( s & 1 << i - 1 ) && check(s,i) )
for ( int j = 1 ; j <= maxx ; j ++ )//上一次使用的颜色
f[s][a[i].col] = min ( f[s][a[i].col] , f[s^(1<<i-1)][j] + ( j != a[i].col ) );
for ( int i = 1 ; i <= maxx ; i ++ ) ans = min ( ans , f[(1<<n)-1][i] );
cout << ans << endl;
return 0;
}
提升/变式
P2396 yyy loves Maths VII
双倍经验 Axis Walking
显然对于每一种状态,它能达到的数字是确定的,那么我们可以预处理这个东西,然后存在每一个状态的数组中。
考虑设 \(f[i]\) 表示状态为 \(i\) 的时候,赢的方案数,那么如果这个状态不是厄运数字的话,那么枚举 \(i\) 状态中所有的 \(1\) 进行转移,转移即为 \(f[i]=\sum f[i\oplus lowbit(i)]\)。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int mod = 1e9 + 7;
const int N = ( 1 << 24 ) + 5;
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , a[N] , bad[N] , sum[N] , f[N];
inl int lowbit ( int x ) { return x & -x; }
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
m = read();
memset ( bad , -1 , sizeof bad );
for ( int i = 1 ; i <= m ; i ++ ) bad[i] = read();
for ( int i = 1 ; i <= n ; i ++ ) sum[1<<i-1] = a[i];
for ( int s = 1 ; s < 1 << n ; s ++ ) sum[s] = sum[lowbit(s)] + sum[s^lowbit(s)];
f[0] = 1;
for ( int s = 0 ; s < 1 << n ; s ++ )
if ( sum[s] != bad[1] && sum[s] != bad[2] )
{
int temp = s;
while ( temp )
{
f[s] += f[s^lowbit(temp)];
if ( f[s] >= mod ) f[s] -= mod;
temp -= lowbit(temp);
}
}
cout << f[(1<<n)-1] << endl;
return 0;
}
P3092 [USACO13NOV] No Change G
设置 \(f[i]\) 表示状态为 \(i\) 的时候的最大价值,\(g[i]\) 表示状态为 \(i\) 的时候的最大编号个数。
然后枚举每一个状态,并枚举本次用的是哪一个硬币,在去掉这个硬币的基础上,二分一个这个硬币能买到的最大值的编号并更新。
如果编号更新到了 \(n\),那么答案取 \(min\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define mid (l+r>>1)
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e5 + 5;
const int M = ( 1 << 17 ) + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , k , c[N] , coin[N] , f[M] , g[M] , sum[N] , tot , ans = inf;
//f[i]表示当前状态为i的时候的最大价值
//g[i]表示当前状态为i的时候的最大个数
int check ( int l , int val )
{
int r = n , temp = l;
while ( l <= r )
{
if ( sum[mid] - sum[temp-1] > val ) r = mid - 1;
else l = mid + 1;
}
return r;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
k = read() , n = read();
for ( int i = 1 ; i <= k ; i ++ ) coin[i] = read() , tot += coin[i];
for ( int i = 1 ; i <= n ; i ++ ) c[i] = read() , sum[i] = sum[i-1] + c[i];
for ( int s = 0 ; s < ( 1 << k ) ; s ++ )
{
for ( int i = 1 , sum ; i <= k ; i ++ )
if ( s & 1 << i - 1 )
{
if ( ( sum = check ( g[s^1<<i-1] + 1 , coin[i] ) ) > g[s] )
g[s] = sum , f[s] = f[s^1<<i-1] + coin[i];
if ( g[s] == n ) ans = min ( ans , f[s] );
}
}
cout << ( tot - ans < 0 ? -1 : tot - ans ) << endl;
return 0;
}
P3052 [USACO12MAR] Cows in a Skyscraper G
我们可以设置 \(f[i]\) 表示 \(i\) 状态下的最小分组数量,\(g[i]\) 表示 \(i\) 状态下的最大剩余背包容量。
当我们从前置状态转移过来的时候,分放的下和放不下两种情况讨论。
如果放的下且可以更新最优答案,那么强制更新 \(g\) 数组(取 \(max\) 的原因是 \(g\) 数组需要取得用 \(1-j\) 中所有状态更新之后的最大值),放不下也是同理。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 18 + 5;
const int M = ( 1 << 19 );
const int inf = 0x3f3f3f3f;
const int mod = 100000000;
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , w , f[M] , g[M] , a[N];//f为分组数量 g为剩余权值
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , w = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
memset ( f , inf , sizeof f );
g[0] = w , f[0] = 1;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
for ( int i = 1 ; i <= n ; i ++ )
{
if ( ! ( s & 1 << i - 1 ) ) continue;
int pre = s ^ 1 << i - 1;
if ( g[pre] >= a[i] && f[s] >= f[pre] )
{
g[s] = max ( g[s] , g[pre] - a[i] );
f[s] = f[pre];
}
else if ( g[pre] < a[i] && f[s] >= f[pre] + 1 )
{
g[s] = max ( g[s] , w - a[i] );
f[s] = f[pre] + 1;
}
}
cout << f[(1<<n)-1] << endl;
return 0;
}
P3869 [TJOI2009] 宝藏
和广搜的结合。
注意 \(map\) 的运算符需要重载干净,避免两个状态不相同但是被判断为相同的情况。
没 \(define\) \(cin.get()\) 调半个多小时,今天怎么了。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
#define getchar() cin.get()
const int dx[5] = { 1 , 0 , -1 , 0 };
const int dy[5] = { 0 , 1 , 0 , -1 };
const int N = 100 + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , k , sx , sy , ex , ey;
char mp[N][N];
struct node { int x , y , st , dis; friend bool operator < ( const node &a , const node &b ) { return a.st == b.st ? ( a.x == b.x ? a.y < b.y : a.x < b.x ) : a.st < b.st; } };
map<node,int> vis;
struct key { int x , y , tx , ty; } a[N];
int check ( int x , int y ) { return 1 <= x && x <= n && 1 <= y && y <= m; }
queue<node> q;
void bfs ()
{
q.push((node){sx,sy,0,0});
vis[(node){sx,sy,0,0}] = 1;
while ( !q.empty() )
{
node u = q.front();
int x = u.x , y = u.y , st = u.st , dis = u.dis; q.pop();
if ( x == ex && y == ey ) { cout << dis << endl; exit(0); }
for ( int i = 0 ; i < 4 ; i ++ )
{
int tx = x + dx[i] , ty = y + dy[i];
if ( !check(tx,ty) ) continue;
int flag = !( mp[tx][ty] == '#' ) , tmpst = st;
for ( int j = 1 ; j <= k ; j ++ )
{
if ( ( st & 1 << j - 1 ) && a[j].tx == tx && a[j].ty == ty ) flag ^= 1;
if ( a[j].x == tx && a[j].y == ty ) tmpst ^= ( 1 << j - 1 );
}
if ( flag && !vis.count((node){tx,ty,tmpst,0}) ) vis[(node){tx,ty,tmpst,0}] = 1 , q.push((node){tx,ty,tmpst,dis+1});
}
}
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
{
cin >> mp[i][j];
if ( mp[i][j] == 'S' ) sx = i , sy = j;
if ( mp[i][j] == 'T' ) ex = i , ey = j;
}
k = read();
for ( int i = 1 ; i <= k ; i ++ ) a[i].x = read() , a[i].y = read() , a[i].tx = read() , a[i].ty = read();
bfs();
return 0;
}
P4011 孤岛营救问题
同样是用状压记录状态,用广搜来解决问题。
需要用 \(map\) 来记录状态避免重复访问。
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 10 + 5;
const int dx[4] = { 1 , -1 , 0 , 0 };
const int dy[4] = { 0 , 0 , 1 , -1 };
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , p , s , k , wall[N][N][N][N] , door[N][N][N][N];
vector<int> key[N][N];
struct node { int x , y , dis , state; friend bool operator < ( const node &a , const node &b ) { return a.state == b.state ? ( a.x == b.x ? a.y < b.y : a.x < b.x ) : a.state < b.state; } };
queue<node> q;
map<node,int> vis;
int check ( int x , int y ) { return 1 <= x && x <= n && 1 <= y && y <= m; }
void bfs ()
{
int st = 0;
for ( auto v : key[1][1] ) st |= 1 << v - 1;
q.push ( (node) { 1 , 1 , 0 , st } );
vis[(node){1,1,0,st}] = 1;
while ( !q.empty() )
{
node u = q.front(); q.pop();
int x = u.x , y = u.y , dis = u.dis , state = u.state;
if ( x == n && y == m ) { cout << dis << endl; exit(0); }
for ( int i = 0 ; i < 4 ; i ++ )
{
int tx = x + dx[i] , ty = y + dy[i];
if ( !check ( tx , ty ) || wall[tx][ty][x][y] || ( door[tx][ty][x][y] && !( ( 1 << door[tx][ty][x][y] - 1 ) & state ) ) ) continue;
int tempst = state;
for ( auto v : key[tx][ty] ) tempst |= 1 << v - 1;
if ( vis.count((node){tx,ty,0,tempst}) ) continue;
vis[(node){tx,ty,0,tempst}] = 1;
q.push ( (node) { tx , ty , dis + 1 , tempst } );
}
}
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , p = read() , k = read();
for ( int i = 1 ; i <= k ; i ++ )
{
int x = read() , y = read() , tx = read() , ty = read() , op = read();
if ( op > 0 ) door[x][y][tx][ty] = door[tx][ty][x][y] = op;
else wall[x][y][tx][ty] = wall[tx][ty][x][y] = 1;
}
s = read();
for ( int i = 1 ; i <= s ; i ++ )
{
int x = read() , y = read() , w = read();
key[x][y].eb(w);
}
bfs();
cout << -1 << endl;
return 0;
}
P2761 软件补丁问题
状压+最短路,用二进制编号作为点的状态,每次扩展到一个 \(v\) 节点,满足 \(u\) 状态没有 \(b_2\) 且全部含有 \(b_1\),跑 \(spfa\) 即可。
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define mid (l+r>>1)
#define getchar() cin.get()
const int N = ( 1 << 20 ) + 5;
const int inf = 0x3f3f3f3f;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , b1[N] , b2[N] , f1[N] , f2[N] , dis[N] , t[N] , in[N];
char ch;
queue<int> q;
void spfa ()
{
memset ( dis , inf , sizeof dis );
q.push((1<<n)-1);
dis[(1<<n)-1] = 0;
in[(1<<n)-1] = 1;
while ( !q.empty() )
{
int u = q.front(); q.pop();
in[u] = 0;
for ( int i = 1 ; i <= m ; i ++ )
if ( ( u & b1[i] ) == b1[i] && ( u & b2[i] ) == 0 )
{
int v = ( ( u | f1[i] ) ^ f1[i] ) | f2[i];
if ( dis[v] > dis[u] + t[i] )
{
dis[v] = dis[u] + t[i];
if ( !in[v] ) q.push(v) , in[v] = 1;
}
}
}
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= m ; i ++ )
{
t[i] = read();
for ( int j = 1 ; j <= n ; j ++ ) cin >> ch , b1[i] |= ( ch == '+' ) ? 1 << j - 1 : 0 , b2[i] |= ( ch == '-' ) ? 1 << j - 1 : 0;
for ( int j = 1 ; j <= n ; j ++ ) cin >> ch , f1[i] |= ( ch == '-' ) ? 1 << j - 1 : 0 , f2[i] |= ( ch == '+' ) ? 1 << j - 1 : 0;
}
spfa();
cout << ( dis[0] == inf ? 0 : dis[0] ) << endl;
return 0;
}
P4163 [SCOI2007] 排列
有意思的状压题目,设置 \(f_{i,j}\) 表示到了 \(i\) 这个状态,且该状态对于 \(d\) 取模的结果为 \(j\) 的方案数量。
那么我们可以枚举后缀节点和余数进行转移即可。
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define eb emplace_back
#define pb pop_back
#define endl '\n'
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int mod = 998244353;
const int inf = 0x3f3f3f3f;
const int N = 1000 + 5;
const int M = ( 1 << 10 ) + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , d , a[N] , vis[N] , f[M][N];
string s;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
int cnt = 0;
cin >> s , d = read();
n = s.size() , s = " " + s;
for ( int i = 1 ; i <= n ; i ++ ) a[i] = s[i] - '0';
memset ( f , 0 , sizeof f );
f[0][0] = 1;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
{
memset ( vis , 0 , sizeof vis );
for ( int i = 1 ; i <= n ; i ++ )
{
if ( s & 1 << i - 1 ) continue;
if ( vis[a[i]] ) continue; vis[a[i]] = 1;
for ( int j = 0 ; j < d ; j ++ )
f[s|(1<<i-1)][(j*10+a[i])%d] += f[s][j];
}
}
cout << f[(1<<n)-1][0] << endl;
}
return 0;
}
Roman and Numbers
和上一道题相同,但是需要改成填表法来快速判断前导 \(0\)。
代码中的判断含义是:如果该位为 \(0\),且整个状态只有这一个 \(0\),那么这个状态显然不合法。
因为我们只可能在它后面加状态,如果我们保留了这个状态,那么使用到这个状态的时候一定会使得转移到的串有前导 \(0\)。
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define eb emplace_back
#define pb pop_back
#define endl '\n'
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int mod = 998244353;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
const int M = ( 1 << 18 ) + 5;
int read()
{
int f = 1 , x = 0;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , d , a[N] , vis[N] , f[M][N];
string s;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
cin >> s , n = s.size() , s = " " + s , d = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = s[i] - '0';
f[0][0] = 1;
for ( int s = 0 ; s < ( 1 << n ) ; s ++ )
{
memset ( vis , 0 , sizeof vis );
for ( int i = 1 ; i <= n ; i ++ )
{
if ( s == ( 1ll << i - 1 ) && !a[i] ) break;
if ( ! ( s & 1ll << i - 1 ) || vis[a[i]] ) continue;
vis[a[i]] = 1;
for ( int j = 0 ; j < d ; j ++ )
f[s][(j*10+a[i])%d] += f[s^(1<<i-1)][j];
}
}
cout << f[(1<<n)-1][0] << endl;
return 0;
}