状压dp个人刷题记录

目录

一.普通型

蒙德里安的梦想

题意:

思路:

code:

#2153. 「SCOI2005」互不侵犯

题意:

思路:

code:

P1879 [USACO06NOV]Corn Fields G

题意:

思路:

code:

P2704 [NOI2001] 炮兵阵地

题意:

思路:

code:

Most Powerful

题意:

思路:

code:

方格取数(1)

题意:

思路:

code:

中国象棋

题意:

思路:

code:

[JXOI2012]奇怪的道路

题意:

思路:

code:

Victor and World (旅行商问题/TSP问题/最短路径问题 ) ()Floyd + 状压dp)

题意:

思路:

code:

P2915 [USACO08NOV]Mixed Up Cows G

题意:

思路:

code:

最短Hamilton路径

题意:

思路:

code:

P3052 [USACO12MAR]Cows in a Skyscraper G

题意:

思路:

code:

P2831 [NOIP2016 提高组] 愤怒的小鸟

题意:

思路:

code:

P3959 [NOIP2017 提高组] 宝藏

题意:

思路:

code:

AC Challenge2018 南京网络赛 E

题意:

思路:

code:

二.高维前缀和 / 动态高维前缀和

Or Plus Max

题意:

思路:

code:

Bits And Pieces

题意:

思路:

code:

Jzzhu and Numbers

题意:

思路:

code:

Vowels

题意:

思路:

code:

Compatible Numbers

题意:

思路:

code:

三.轮廓线型 / 插头dp


状压dp注意事项:

注意数据范围一般都得long long

^删除, |添加, 看某位是否在状态中存在(1 << j) & S

一.普通型

蒙德里安的梦想

题意:

给你n*m的棋盘, 每次你可以放进去2*1的小长方形, 问你最多能够放多少个.

思路:

(蓝书上是按行枚举, AcWing上是按列枚举, 这里我采用按列枚举的方法.)

整体思路:摆放方块的时候,先放横着的,再放竖着的, 下面按照这个思路来走.

首先我们用f[i][j]表示前i列的状态为j时能放的最大值.我们一列一列的来看.

我们规定对第i列, 的某个状态k, 其对应在棋盘中的位置(k,i)如果为是横着的长方形的左半部分, 那么它的值为1, 否则为0.

下面我们来构造状态转移方程. 显然是:f[i][j] += f[i - 1][k] 即从前一列的合法情况转移而来.

下面我们考虑如何才能合法的转移呢? 想要从前一个状态转移到后一个状态有两个关键点:

1)若想要将i-1列的状态k转移到第i列的状态jj k不能同时为1, 因为要1代表的是长方形的前一半.不能两个都是前一半! 表示为 (j & k) == 0. 消除了对应位置同时为1的可能.

2)每一列连续的空着的长度必须是偶数. 是为了能够摆放竖着的, 呼应我们的整体思路.先横后竖 因此若能够转移, 新的状态中一定也不存在上述非法情况; 即j | k == 1. 采用或的原因如下:

j | k 表示的是只要前后状态对应位置有一位为1,连续0就会被隔断,所以,更新后的状态中的对应位置实际的是j | k. 若j | k合法表示原来的和现在的都是合法的.

由此我们得到了进行状态转移的前提: (j & k) == 0 && in_s[j | k]

PS:1.关于处理2)的情况我们需要预处理出所有合法的(对每一列进行预处理). 2.需要设置f[0][0] = 1, 其他全部为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个国王, 问你可行的方案有多少种? 国王会攻击其上下左右 左上 左下 右上 右下等八个方向的其他人.

思路:

笑死 第一次做根本没思路   

我们按行枚举 f[i][j][k] 表示第i行, 状态为j 时, 已经放了k个国王的方案数量.

大概流程就是1)预处理每行合法状态 2)设置第一行的情况全为1 3)循环更新 4)循环累加答案

状压dp最大的问题就是位运算了, 比如(101011)_2   (首先注意二进制从右向左看) 从右向左若1则放0则不放, 由此我们把一行中的一个状态压缩成了一个二进制串, 我们想要方便的访问, 还要把他转化成十进制存在一个num数组中方便之后对其访问, (101011)_2转化成十进制是(43)_{10}. 因此我们存储方式为sta[++cnt] = 43.

对于本题, 存储状态用一个数组sta, 存储每行中放多少国王用另一个num. 并且在实现时候还要注意题目中给的国王间互相排斥的条件, 这个也要通过位运算和与来实现. 转化成二进制画画图能好理解一些.

具体实现不展开来说了, 写在代码里把.

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, 我们规定f[i][j]表示, 在i节点, 状态为j时的最小权值和, 枚举合法状态, 并且注意当前状态是由前一个状态转移而来即f[i][S] = min(f[i][S], f[j][S ^ (1 << i)] + g[j][i] .

3)遍历除了原点外的所有点, 找到那个到原点权值和最小的, 并替代f[0][(1 << n) - 1]

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;
}

三.轮廓线型 / 插头dp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值