目录
P1879 [USACO06NOV]Corn Fields G
Victor and World (旅行商问题/TSP问题/最短路径问题 ) ()Floyd + 状压dp)
P2915 [USACO08NOV]Mixed Up Cows G
P3052 [USACO12MAR]Cows in a Skyscraper G
状压dp注意事项:
注意数据范围一般都得long long
^删除, |添加, 看某位是否在状态中存在(1 << j) & S
一.普通型
蒙德里安的梦想
题意:
给你n*m的棋盘, 每次你可以放进去2*1的小长方形, 问你最多能够放多少个.
思路:
(蓝书上是按行枚举, AcWing上是按列枚举, 这里我采用按列枚举的方法.)
整体思路:摆放方块的时候,先放横着的,再放竖着的, 下面按照这个思路来走.
首先我们用表示前列的状态为时能放的最大值.我们一列一列的来看.
我们规定对第列, 的某个状态, 其对应在棋盘中的位置如果为是横着的长方形的左半部分, 那么它的值为1, 否则为0.
下面我们来构造状态转移方程. 显然是: 即从前一列的合法情况转移而来.
下面我们考虑如何才能合法的转移呢? 想要从前一个状态转移到后一个状态有两个关键点:
1)若想要将列的状态转移到第列的状态, 不能同时为1, 因为要1代表的是长方形的前一半.不能两个都是前一半! 表示为 (j & k) == 0. 消除了对应位置同时为1的可能.
2)每一列连续的空着的长度必须是偶数. 是为了能够摆放竖着的, 呼应我们的整体思路.先横后竖 因此若能够转移, 新的状态中一定也不存在上述非法情况; 即. 采用或的原因如下:
j | k 表示的是只要前后状态对应位置有一位为1,连续0就会被隔断,所以,更新后的状态中的对应位置实际的是j | k. 若j | k合法表示原来的和现在的都是合法的.
由此我们得到了进行状态转移的前提: (j & k) == 0 && in_s[j | k]
PS:1.关于处理2)的情况我们需要预处理出所有合法的(对每一列进行预处理). 2.需要设置, 其他全部为0. 因为最小的情况n=1, m=1.可以放下一个.
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll in_s[1<<11];
ll f[12][1<<11];
//按列枚举
void solve() {
int n, m;
while (cin >> n >> m, n || m) {
//预处理没有奇数个连续0的情况
for (int i = 0; i < (1 << 11); i++ ) {
int cnt = 0; // 统计连续0
in_s[i] = 1;// 默认没有奇数个0
//遍历i的m位
for (int j = 0; j < n; j++) {
//当前位是奇数
if ((i >> j) & 1) {
//偶数的数量是奇数个 -> 不合法
if (cnt & 1) {
in_s[i] = 0;
break;
}
}
else //当前位是偶数
cnt ++;
}
if (cnt & 1)
in_s[i] = 0;
}
f[0][0] = 1;
for (int i = 1; i <= m; i++) {
for (int j = 0; j < (1 << n); j++) {
f[i][j] = 0;
for (int k = 0; k < (1 << n); k++) {
if ((j & k) == 0 && in_s[j | k]) {
f[i][j] += f[i - 1][k];
}
}
}
}
cout << f[m][0] << endl;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
while (t--) {
solve();
}
return 0;
}
#2153. 「SCOI2005」互不侵犯
题意:
n*n的格子, 让你放k个国王, 问你可行的方案有多少种? 国王会攻击其上下左右 左上 左下 右上 右下等八个方向的其他人.
思路:
笑死 第一次做根本没思路
我们按行枚举 表示第i行, 状态为j 时, 已经放了k个国王的方案数量.
大概流程就是1)预处理每行合法状态 2)设置第一行的情况全为1 3)循环更新 4)循环累加答案
状压dp最大的问题就是位运算了, 比如 (首先注意二进制从右向左看) 从右向左若1则放0则不放, 由此我们把一行中的一个状态压缩成了一个二进制串, 我们想要方便的访问, 还要把他转化成十进制存在一个数组中方便之后对其访问, 转化成十进制是. 因此我们存储方式为.
对于本题, 存储状态用一个数组, 存储每行中放多少国王用另一个. 并且在实现时候还要注意题目中给的国王间互相排斥的条件, 这个也要通过位运算和与来实现. 转化成二进制画画图能好理解一些.
具体实现不展开来说了, 写在代码里把.
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, goal, cnt;
ll f[250][250][250];
ll sta[250], num[250];
void pre() {
for (int i = 0; i < (1 << n); i++) {
//往左移动, 如果不等于0代表有相邻的国王 不合法
if (i & (i << 1))
continue;
ll sum = 0;
for (int j = 0; j < n; j++)
if (i & (1 << j)) //统计i的二进制下多少位是1
sum ++;
sta[++cnt] = i; //记录可用状态的十进制
num[cnt] = sum; //记录此状态国王数量
}
}
void solve() {
cin >> n >> goal;
pre();
for (int i = 1; i <= cnt; i++)
f[1][i][num[i]] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= cnt; j++) { // 第i行 状态
for (int k = 1; k <= cnt; k++) { // 第i - 1 行 状态
if (sta[j] & sta[k]) //上下重复
continue;
if ((sta[j] << 1) & sta[k])//左上右下重复
continue;
if (sta[j] & (sta[k] << 1))//右上左下重复
continue;
for (int s = goal; s >= num[j]; s--)
f[i][j][s] += f[i-1][k][s - num[j]];
}
}
}
ll res = 0;
for (int i = 1; i <= cnt; i++)
res += f[n][i][goal];
cout << res << endl;
}
int main() {
solve();
return 0;
}
P1879 [USACO06NOV]Corn Fields G
题意:
0, 1 矩阵 给定你条件(见原题), 让你给出最大方案数量
思路:
dp[i][j][k]表示的是前i行状态为j时候的方案数量.
1)预处理状态和数量
2)1.不会选择相邻的 2. 贫瘠的不能种草
3)注意先枚举第i行, 后枚举i-1
code:
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9;
int f[13][5000], sta[5000], num[13];
int a[13][13];
int n, m;
void pre() {
//模拟草地情况的二进制表述
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
num[i] = (num[i] << 1) + a[i][j];
//求出状态
for (int i = 0; i < (1 << n); i++)
sta[i] = ( (i & (i << 1)) == 0) && ( (i & (i >> 1)) == 0);
}
void solve() {
cin >> m >> n;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
pre();
f[0][0] = 1;
for (int i = 1; i <= m; i++)
for (int j = 0; j < (1 << n); j++)
if (sta[j] && ((j & num[i]) == j)) //状态是合法的,且不会在贫瘠的草地上
for (int k = 0; k < (1 << n); k++) //上下两行之间没有相邻的草地
if ((k & j) == 0)
f[i][j] = (f[i][j] + f[i - 1][k]) % MOD;
int res = 0;
for (int i = 0; i < (1 << n); i++)
res = (res + f[m][i]) % MOD;
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
P2704 [NOI2001] 炮兵阵地
题意:
见原题.
思路:
还是按行枚举, 判断前两行有没有炮兵, 判断是不是山丘, 判断左右两列有没有炮兵, 不容易想到正解, 很复杂实现起来.
f[i][k][k] 表示第i行状态为j, 上一行状态为k的情况数. 挺恶心的...
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, f[105][1005][1005], a[105];
char c;
int get(int x) { //二进制下
int sum = 0;
while (x)
sum += x % 2, x >>= 1;
return sum;
}
bool check(int x) {
return (x & (x << 1)) | (x & (x << 2)) | (x & (x >> 1)) | (x & (x >> 2));
}
void solve() {
cin >> n >> m;
for (int i = 2; i <= n + 1; ++i)
for (int j = 1; j <= m; ++j)
cin >> c, a[i] = (a[i] << 1) + (c == 'H');
ll MAX = (1 << m) - 1;
for (int i = 2; i <= n + 1; ++i) {
for (int j = 0; j <= MAX; ++j) {
if (j & a[i - 2] || check(j))
continue;
for (int k = 0; k <= MAX; ++k) {
if ((k & a[i - 1]) || (j & k) || check(k))
continue;
for (int l = 0; l <= MAX; ++l) {
if ((l & a[i]) || (l & j) || (l & k) || check(l))
continue;
f[i][k][l] = max(f[i][k][l], f[i - 1][j][k] + get(l));
}
}
}
}
ll res = 0;
for (int k = 0; k <= MAX; k++) {
if (k & a[n])
continue;
for (int l = 0; l <= MAX; l++)
res = max(res, f[n + 1][k][l]);
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
Most Powerful
题意:
给你n个原子,两两相撞其中一个消失, 产生一定的能量,给出任意两原子相撞能产生的能量,求可能产生的最大能量?
思路:
dp[]表示当前状态下能够达到的最大值是多少
code:
#include <bits/stdc++.h>
using namespace std;
int power[11][11];
int dp[1 << 11];
void solve() {
int n, res = 0;
while (cin >> n && n != 0) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> power[i][j];
}
}
int MAX = (1 << n) - 1;
memset(dp, 0, sizeof dp);
for (int s = 0; s <= MAX; ++s) {
for (int i = 0; i < n; ++i) {
if (((1 << i) & s) == 0)
for (int j = 0; j < n; ++j) {
if (((1 << j) & s) == 0) {
if (i != j) {
dp[s | (1 << i)] = max(dp[s | (1 << i)], dp[s] + power[j][i]);
}
}
}
}
}
res = 0;
for (int s = 0; s <= MAX; ++s)
res = max(res, dp[s]);
cout << res << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
方格取数(1)
题意:
板子题
思路:
略
code:
#include<bits/stdc++.h>
using namespace std;
int dp[25][20000];
int tot[20000];
int a[25][25];
int get_num(int i,int x) {
int t=1;
int sum=0;
while(x){
if(x&1){
sum+=a[i][t];
}
x/=2;
t++;
}
return sum;
}
int main(){
int n;
while(cin >> n){
memset(dp,0,sizeof(dp));
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
scanf("%d",&a[i][j]);
}
}
int cut=0;
for(int i=0; i<(1<<n); i++){
if((i&(i>>1))==0){
tot[++cut]=i;
}
}
for(int i=1; i<=n; i++){
for(int j=1; j<=cut; j++){
int va=get_num(i,tot[j]);
for(int k=1; k<=cut; k++){
if((tot[j]&tot[k])==0)
dp[i][j]=max(dp[i][j],dp[i-1][k]+va);
}
}
}
int ma=0;
for(int i=1; i<=cut; i++){
ma=max(ma,dp[n][i]);
}
printf("%d\n",ma);
}
return 0;
}
中国象棋
题意:
见原题
思路:
学习的大佬的思路
code:
#include <bits/stdc++.h>
#define int long long
#define MOD 9999973
using namespace std;
int n, m, res;
int f[111][111][111];
int C(int x) {
return ((x * (x - 1)) / 2) % MOD;
}
signed main() {
cin >> n >> m;
f[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= m - j; k++) {
f[i][j][k] = f[i - 1][j][k];
if (k >= 1)
(f[i][j][k] += f[i - 1][j + 1][k - 1] * (j + 1));
if (k >= 1)
(f[i][j][k] += f[i - 1][j][k - 1] * j * (m - j - k + 1));
if (k >= 2)
(f[i][j][k] += f[i - 1][j + 2][k - 2] * (((j + 2) * (j + 1)) / 2));
if (j >= 1)
(f[i][j][k] += f[i - 1][j - 1][k] * (m - j - k + 1));
if (j >= 2)
(f[i][j][k] += f[i - 1][j - 2][k] * C(m - j - k + 2));
f[i][j][k] %= MOD;
}
}
}
for (int i = 0; i <= m; i++)
for (int j = 0; j <= m; j++)
(res += f[n][i][j]) %= MOD;
cout << (res + MOD) % MOD << endl;
return 0;
}
[JXOI2012]奇怪的道路
题意:
略
思路:
待补? 异或操作很奇怪...
code:
#include <bits/stdc++.h>
const int MOD = 1e9+7;
using namespace std;
int n, m, k;
int f[44][44][555][11];
signed main() {
cin >> n >> m >> k;
f[2][0][0][0]=1;
for (int i=2;i<=n;i++)
for (int j=0;j<=m;j++)
for (int s=0;s<(1<<(k+1));s++) {
for (int l=0;l<k;l++){
f[i][j][s][l+1]+=f[i][j][s][l];
f[i][j][s][l+1]%=MOD;
if(i-k+l>0&&j<m)
f[i][j+1][s^(1<<k)^(1<<l)][l]+=f[i][j][s][l],
f[i][j+1][s^(1<<k)^(1<<l)][l]%=MOD;
}
if(!(s&1)) {
f[i+1][j][s>>1][0]+=f[i][j][s][k];
f[i+1][j][s>>1][0]%=MOD;
}
}
cout << f[n + 1][m][0][0] << endl;
return 0;
}
Victor and World (旅行商问题/TSP问题/最短路径问题 ) ()Floyd + 状压dp)
题意:
有n个点, m条边, 从一个点到另一个点有一个花费w. 问你从1号点开始遍历所有的点最后回到1号点的最小花费是多少?
思路:
本题采用Floyd + 状压dp
1)首先用Floyd处理两点之间的最小权值
2)进行状压dp, 我们规定表示, 在i节点, 状态为j时的最小权值和, 枚举合法状态, 并且注意当前状态是由前一个状态转移而来即f[i][S] = min(f[i][S], f[j][S ^ (1 << i)] + g[j][i] .
3)遍历除了原点外的所有点, 找到那个到原点权值和最小的, 并替代
tips:
1)u, v我改成从0开始的了
2)记得初始化 f,和g
3)f[j][S ^ (1 << i)] 表示的是把i节点从当前状态中拿走, 如果是 | 的话相当于加上来
4)为什么最后要找最小的而不是直接输出f[0][(1 << n) - 1], 而要用f[i][(1 << n) - 1] + g[i][0]来和其取min呢? 因为最后访问的点不会是起点. 枚举最后访问的点是哪个(遍历完所有的了已经用(1<<n)-1表示状态, 因此我们枚举最后一个点到原点的距离的最小值方可得到正确答案!
code:
#include <bits/stdc++.h>
using namespace std;
int f[25][1<<17]; //f[i][j] 表示当前在i节点, 当前状态j的最小权值之和
int g[25][25];
int n, m;
void solve() {
memset(g, 0x3f, sizeof g);
cin >> n >> m;
while (m--) {
int u, v, w;
cin >> u >> v >> w;
u--, v--; //改成从0开始
g[u][v] = g[v][u] = min(g[u][v], w);
}
//Floyd
for(int k = 0; k < n; k++) //
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
//状压dp
memset(f,0x3f,sizeof(f));
f[0][1] = 0;
for (int S = 1; S < (1 << n); S++) //枚举状态
for (int i = 0; i < n; i++) if (S & (1 << i)) //先枚举目标节点
for (int j = 0; j < n; j++) //枚举出发节点
if (S & (1 << j) && g[j][i])
f[i][S] = min(f[i][S], f[j][S ^ (1 << i)] + g[j][i]);
//找到最小的
for(int i = 1; i < n; i++)
f[0][(1 << n) - 1] = min(f[0][(1 << n) - 1], f[i][(1 << n) - 1] + g[i][0]);
cout << f[0][(1 << n) - 1] << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
P2915 [USACO08NOV]Mixed Up Cows G
题意:
重排序列, 相邻的差必须大于k.
思路:
根据数据范围可知一定是状压dp. 设计dp的时候想到一定有一个维度是表示状态的.另一个想到是编号. 那我们由无后效性联想到, 可以设计成f[i][j] i表示当前以i为结尾, 状态为j时候的方案数量.由此我们步骤基本明朗.
1)初始化每个点, 只有自己且是队尾的f[i][(1<<n)]应该设置成1, 因为这只有一种情况
2)for循环枚举状态, 枚举起点, 终点, 记得既要满足该点在状态中存在, 也要满足题目中的距离差. 接下来有两种做法j->k and k->j, 见代码.
3)把每个点做队尾且满状态的情况累加得到答案.
ps:似乎^的做法跑得更快?
code:
#include <bits/stdc++.h>
using namespace std;
int a[25];
long long f[25][1 << 17];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
f[i][(1 << i)] = 1;
}
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++) {
//1) 从k到j加上 k是起点
if (i & (1 << k) && abs(a[j] - a[k]) > m) {
f[j][i] += f[k][i ^ (1 << j)];
}
//2) 从j到k加上 j是起点
/*
if (!(i & (1 << k)) && abs(a[j] - a[k]) > m) {
f[k][i | (1 << k)] += f[j][i];
}
*/
}
}
}
}
long long ans = 0;
for (int i = 0; i < n; i++) {
ans += f[i][(1 << n) - 1];
}
cout << ans << "\n";
return 0;
}
最短Hamilton路径
题意:
给你任意两点之间长度, 问你经过每个点恰好一次的最小长度是多少.
思路:
和TSP问题很相似, 不过这个是经过每个点恰好一次, 那个是最后还要回到原点, 因此那个得套Floyd.
乍一看属于常规思路: 我们用f[i][j]的一个维度表示当前节点, 另一个表示状态. 即f[i][j]表示当前租到了i节点, 状态j的方案数量.经典步骤如下:
1)memset f 无穷大, 特判下起点是f[0][1] = 0, 节点0, 状态1的情况.一定不存在, (相当于没出发怎么会存在)
2)和TSP问题一样的dp过程
3)输出以n-1为终点的状态为(1<<n)-1的结果即可. 因为最终一定会走到n-1这个节点!
code:
#include <bits/stdc++.h>
using namespace std;
int g[25][25];
long long f[25][1 << 20];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
memset(f, 127, sizeof f);
f[0][1] = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> g[i][j];
//dp
for (int S = 0; S < (1 << n); S++) //staus
for (int i = 0; i < n; i++) if (S & (1 << i)) //from
for (int j = 0; j < n; j++) if (S & (1 << j)) //to
f[j][S] = min(f[j][S], f[i][S ^ (1 << j)] + g[i][j]);
cout << f[n - 1][(1 << n) - 1] << endl;
return 0;
}
P3052 [USACO12MAR]Cows in a Skyscraper G
题意:
给你n个物品, 每个重量为s[i], 给你每个组的重量上限m, 问你最少分多少个组?
思路:
本题属于反套路型的, 一般人会用dfs来做.
如果用状压需要两个存状态的一维数组 f:i状态最小坐组数量 i状态最小重量
注意更新时加的那个限制条件, 很恶心人的. 加第二个限制条件是因为要更新f为更小的!
code:
#include <bits/stdc++.h>
using namespace std;
long long f[1 << 18]; //i状态最小坐数量
long long g[1 << 18]; //i状态最小重量
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
vector<long long> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
memset(f, 127, sizeof f); memset(g, 127, sizeof g);
f[0] = 1; f[0] = 0;
for (int i = 0; i < (1 << n); i++) //枚举状态
for (int j = 0; j < n; j++) if (!((1 << j) & i)) {//枚举具体的人
//能放这个组 并且要开新的组
if (g[i] + a[j] > m && f[i | (1 << j)] >= f[i] + 1) {
f[i | (1 << j)] = f[i] + 1;
g[i | (1 << j)] = min(a[j], g[i | (1 << j)]);
}
//不能放这个组 并且不用开新的组
if (g[i] + a[j] <= m && f[i | (1 << j)] >= f[i]) {
f[i | (1 << j)] = f[i];
g[i | (1 << j)] = min(g[i] + a[j], g[i | (1 << j)]);
}
}
cout << f[(1 << n) - 1] << "\n";
return 0;
}
P2831 [NOIP2016 提高组] 愤怒的小鸟
题意:
略
思路:
这个题目很恶心的, 有时间补下, 当时没做出来...
code:
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
int f[1<<19], st[405];
double x[25], y[25];
int n, m, top;
void solve() {
cin >> n >> m;
memset(f, 127, sizeof f); memset(st, 0, sizeof st);
f[0] = 0; top = 0;
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
//找抛物线
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (x[i] == x[j])
continue;
double ta = (y[i] - y[j] * x[i] / x[j]) / x[i] / (x[i] - x[j]);
double tb = (y[i] * x[j] * x[j] / x[i] / x[i] - y[j]) / (x[j] * x[j] / x[i] - x[j]);
if (ta < 0) {
top ++;
//找能被打掉的猪
for (int k = 1; k <= n; k++)
if (fabs(ta * x[k] * x[k] + tb * x[k] - y[k]) <= eps)
st[top] |= (1 << (k - 1));
}
}
}
//枚举状态
for (int k = 0; k < (1 << n); k++) {
for (int i = 1; i <= top; i++)
f[k | st[i]] = min(f[k | st[i]], f[k] + 1);
for (int i = 1; i <= n; i++)
f[k | (1 << (i - 1))] = min(f[k | (1 << (i - 1))], f[k] + 1);
}
cout << f[(1 << n) - 1] << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
P3959 [NOIP2017 提高组] 宝藏
题意:
略
思路:
首先我们要知道枚举子集的技巧
for (int son = S; son; son = (son - 1) & S) {
// son 为 S 的子集
}
其次这个题目就是个**题, 恶心人有一手的.(我智商低...
基本参照yxc的写的, 过一段时间回来补吧.
code:
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int f[1 << 12][12], g[1 << 12];
int dis[12][12];
int n, m;
void solve() {
cin >> n >> m;
memset(dis, 0x3f, sizeof dis);
memset(f, 0x3f, sizeof f);
for (int i = 0; i < n; i++) {
dis[i][i] = 0;
}
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
u --, v--;
dis[u][v] = dis[v][u] = min(w, dis[u][v]);
}
for (int i = 1; i < 1 << n; i++)
for (int j = 0; j < n; j++) if (i >> j & 1)
for (int k = 0; k < n; k++)
if (dis[j][k] != INF)
g[i] |= 1 << k;
for (int i = 0; i < n; i++) {
f[1 << i][0] = 0;
}
for (int i = 1; i < 1 << n; i++) {
for (int j = (i - 1); j; j = (j - 1) & i) {
if ((g[j] & i) == i) {
int remain = i ^ j;
int cost = 0;
for (int k = 0; k < n; k++)
if (remain >> k & 1) {
int t = INF;
for (int u = 0; u < n; u++)
if (j >> u & 1)
t = min(t, dis[k][u]);
cost += t;
}
for (int k = 1; k < n; k ++ ) {
f[i][k] = min(f[i][k], f[j][k - 1] + cost * k);
}
}
}
}
int res = INF;
for (int i = 0; i < n; i ++ ) {
res = min(res, f[(1 << n) - 1][i]);
}
cout << res << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
AC Challenge2018 南京网络赛 E
题意:
有很多道问题,在做某一道题前,要先完成某些题目,做出题目会得到指定的分数,分数可能是负数,并且分数跟做出这道题的时间有关.问你能得到的最大分数是多少?
思路:
枚举现在做出的题目为状态, 1的数量就是做出题目的数量, 根据题目要求进行位运算一同操作.
注意开long long
code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
int pre[1 << 22], f[1 << 22];
int a[25], b[25];
int n, m;
//算二进制下的1个数
int cac(int S) {int cnt = 0;while (S) { if (S & 1) cnt ++; S >>= 1;} return cnt;}
typedef long long ll;
ll count(ll x){
ll num=0;
for (int j=0;j<25;j++)
if (x&(1<<j)) num++;
return num;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i] >> m;
while (m --) {
int t;
cin >> t;
pre[i] |= (1 << (t - 1));
}
}
for (int i = 0; i < (1 << n); i++) {
f[i] = -INF;
}
f[0] = 0;
int ans = 0;
for (int i = 1; i < (1 << n); i++) {
for (int j = 0; j < n; j++) {
if (! ((1 << j) & i))
continue;
//能够枚举子集
int u = i ^ (1 << j);
if ((u & pre[j + 1]) == pre[j + 1]) {
int w = f[u] + cac(i) * a[j + 1] + b[j + 1];
f[i] = max(f[i], w);
ans = max(ans, f[i]);
}
}
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
二.高维前缀和 / 动态高维前缀和
Or Plus Max
题意:
求满足0 <= i, j <= 2^n-1, (i or j)<=k 条件的 ai + aj的最大值.
思路:
对于每个i枚举其超集, 更新超集中所包含子集的最大值, 同时更新k.
code:
#include<bits/stdc++.h>
using namespace std;
int n;
int a[300010];
int MAX[300010];
int ans[300010];
signed main() {
cin >> n;
for (int i = 0;i < (1 << n); i++)
cin >> a[i];
for (int i = 0;i < (1 << n); i++)
for (int j = i; j < (1 << n); j = (j+1)|i) {
ans[j] = max(ans[j], MAX[j] + a[i]);
MAX[j] = max(MAX[j], a[i]);
}
for (int i = 1; i < (1 << n); i++) {
ans[i] = max(ans[i], ans[i - 1]);
cout << ans[i] << "\n";
}
return 0;
}
Bits And Pieces
题意:
求出 ai|(aj&ak)的最大值,i < j < k
思路:
有些迷糊
f[i][j] -> 有多少个数可以将前j j位的某些位置1变为0,从而变为i.
因为是a_i | (a_j & a_k), 采用从高位向低位来枚举的时候.
1)a_i当前是1, j,k任意取, res更新
2)a_i当前是0, 若想让值更大 则(a_j & a_k)都为1时才能更大, 即子集里至少有两个都满足当前位位1才行, 这时候更新sta, res. 否则不更新.
3)更新a_i, 这里不太懂...
4)用res更新ans.
code:
#include <bits/stdc++.h>
using namespace std;
int f[1 << 21][21];
int a[1000005];
void update(int num, int k) {
if (k > 20) return;
if (f[num][k] > 1) return;
f[num][k] ++;
update(num, k + 1);
if ((num >> k) & 1)
update(num ^ (1 << k), k);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int ans = 0;
for (int i = n; i >= 1; i--) {
int res = 0, sta = 0;
for (int j = 20; j >= 0; j--) {
if ((a[i] >> j) & 1) {
res |= 1 << j;
}
else if (f[sta|(1 << j)][20] > 1) {
res |= 1 << j, sta |= 1 << j;
}
}
update(a[i], 0);
if (i <= n - 2) {
ans = max(ans, res);
}
}
cout << ans << "\n";
return 0;
}
Jzzhu and Numbers
题意:
待补
思路:
待补
code:
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1000000007;
const int N = 1000000;
int n;
int ksm[N+1];
int sum[1<<20];
void init() {
ksm[0]=1;
for(int i=1;i<=n;i++)ksm[i]=2*ksm[i-1]%MOD;
}
signed main(){
cin >> n;
init();
for (int i = 1; i <= n; i++) {
int t;
cin >> t;
t = (1 << 20) - 1 ^ t;
sum[t] ++;
}
for (int i = 0; i < 20; i++) for (int j = 0; j < 1<<20; j++)
if (j & (1 << i)) sum[j] += sum[j ^ (1<<i)];
int ans = ksm[n];
for (int i = 0; i < (1 << 20) - 1; i++)
(ans += (__builtin_popcount(i) & 1 ? -1 : 1) * ksm[sum[i]]) %= MOD;
cout << (ans + MOD) % MOD << endl;
return 0;
}
Vowels
题意:
思路:
code:
Compatible Numbers
题意:
给你1~n个a_i, 对于每个a_i都要找到对应a_j, 使得a_i & a_j == 0, 若不能实现输出-1
思路:
code:
#include <bits/stdc++.h>
using namespace std;
int a[1000005],f[1<<22];
int n;
int main(void) {
cin >> n;
memset(f,-1,sizeof(f));
for (int i = 0;i < n; i++) {
cin >> a[i];
f[(1<<22) - 1 ^ a[i]] = a[i];
}
for (int i= (1<<22) - 1;i >= 0;i --) {
if (f[i] >= 0) continue;
for (int j = 0; j < 22; j++)
if (f[i | (1<<j)] >= 0) {
f[i] = f[i | (1<<j)];
break;
}
}
for (int i = 0; i < n; i++) {
cout << f[a[i]] << " \n"[i == n - 1];
}
return 0;
}