状压杂题

注意状压时状态的设计:

如果状态之间更新涉及到上一个节点是什么(例如 \(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;
}
posted @ 2023-10-14 07:55  Echo_Long  阅读(16)  评论(0编辑  收藏  举报