惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
T
Threatpost
Latest news
Latest news
N
News | PayPal Newsroom
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
TaoSecurity Blog
TaoSecurity Blog
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
T
Threat Research - Cisco Blogs
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Securelist
小众软件
小众软件
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Martin Fowler
Martin Fowler
S
SegmentFault 最新的问题
Cisco Talos Blog
Cisco Talos Blog
云风的 BLOG
云风的 BLOG
AWS News Blog
AWS News Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
美团技术团队
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
S
Schneier on Security
博客园 - 聂微东
V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
SecWiki News
SecWiki News
S
Secure Thoughts
B
Blog RSS Feed
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
腾讯CDC
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed

gyro永不抽风!

深入理解 Zyzzyva 协议 - gyro永不抽风! 深入理解 PBFT 协议 - gyro永不抽风! 西瓜书 绪论 习题 - gyro永不抽风! PyTorch Data Augmentation 数据增广 - gyro永不抽风! 读论文——YOLO v1 - gyro永不抽风! P1363 幻象迷宫 - DFS - gyro永不抽风! P3572 [POI2014] PTA-Little Bird - DP 单调队列 POJ 2823 滑动窗口 单调队列 - 致逝去的青春 leetcode 1787 使所有区间的异或结果为零 - DP - 随机跳题计划 VSCode绕开腾讯云COS防盗链 | Markdown魔改过程 - gyro永不抽风! GAN(对抗生成网络)的基本原理以及数学证明 - gyro永不抽风! Canny边缘检测算法(基于OpenCV的Java实现) - gyro永不抽风!
P2210 Haywire - 状压 DP
博主: gyro永不抽风 · 2022-04-07 · via gyro永不抽风!
  • 发布时间:
  • 2444次浏览
  • 暂无评论
  • 1647字数
  • 分类: 技术
  1. 首页
  2. 正文  
  3. 分享到:

题面

https://www.luogu.com.cn/problem/P2210

题解

一开始没反应出来是状压,看了少数题解才知道可以状压。今天姑且 A 掉状压的写法,明天写写看模拟退火。

首先,我们来设状压的状态方程:

$$
f[i]
$$

  • $i$ 代表 $i$ 表示成二进制的时候有 $1$ 那些位所代表的牛组成的集合
  • 比方说 $i = 6 = (110)_2$ 代表有 $2, 3$ 号牛组成的集合
  • $f[i]$ 代表牛的集合为 $i$ 时对答案产生的贡献。

什么是贡献?这个说法很模棱两可。其实是这样的:两个朋友都在集合里面,那答案必然要包括这对朋友连线的贡献。但是如果只有一头牛在集合里呢?很简单,贡献就是:按照某种最佳排列时,这头牛所处的位置到集合队尾的距离。这么说很抽象,我们举个例子:

$$
~ * ~ * ~ * ~ \triangle ~ * ~ *
$$

上图中,$*$ 代表无所谓的某头牛,$\triangle$ 代表我们现在考察的单个朋友,在这个状态下,$\triangle$ 对答案的贡献就是 $2$,因为 $\triangle$ 之后还有两头牛.

而如果在这个队列之后紧跟就是 $\triangle$ 的朋友:另一个 $\triangle$,那么在增加前 $\triangle$ 给出的贡献 $2$ 就是这对朋友对答案的贡献了:

$$
~ * ~ * ~ * ~ \triangle ~ * ~ * ~ \triangle
$$

好。赘述完关于状态的定义,我们来思考状态怎么转移。对于集合 $i$,我们可以枚举位于队列尾端的到底是哪头牛。每次转移我们只需要加上原来的集合中孤立朋友的数量即可。最终给出的代码是做了一些小的优化,稍微难以理解,所以下面给出一个等价的形式,但是时间复杂度多一个 $n$,为 $O(n^22^n)$:

for (int i = 1; i < (1 << n); i ++) {
    for (int j = 0; j < n; j ++) {
        if ((1 << j) & i) {
            int pending_links = 0;
            int ii = i & ~(1 << j);
            for (int k = 0; k < n; k ++)
                if ((1 << k) & ii)
                    pending_links += 3 - 
                        (((ii >> nbr[k][0]) & 1) + 
                        ((ii >> nbr[k][1]) & 1) + 
                        ((ii >> nbr[k][2]) & 1));
            f[i] = min(f[i], f[ii] + pending_links);
        }
    }
}

代码

最终给出的代码时间复杂度为 $O(n2^n)$:

#include <iostream>
#include <cstring>

using namespace std;

const int maxn = 12;

int f[1 << maxn], nbr[maxn + 5][3];

int main() {
    int n; cin >> n;
    for (int i = 0; i < n; i ++) {
        cin >> nbr[i][0] >> nbr[i][1] >> nbr[i][2];
        nbr[i][0] --; nbr[i][1] --; nbr[i][2] --;
    }
    memset(f, 0x7f, sizeof(f));
    f[0] = 0;
    for (int i = 1; i < (1 << n); i ++) {
        int pending_links = 0;
        for (int j = 0; j < n; j ++)
            if ((1 << j) & i)
                pending_links += 3 - 
                    (((i >> nbr[j][0]) & 1) + 
                    ((i >> nbr[j][1]) & 1) + 
                    ((i >> nbr[j][2]) & 1));
        for (int j = 0; j < n; j ++) {
            if ((1 << j) & i) {
                f[i] = min(f[i], f[i & ~(1 << j)] + pending_links - (3 - 
                    (((i >> nbr[j][0]) & 1) + 
                    ((i >> nbr[j][1]) & 1) + 
                    ((i >> nbr[j][2]) & 1))) + 
                    ((i >> nbr[j][0]) & 1) + 
                    ((i >> nbr[j][1]) & 1) + 
                    ((i >> nbr[j][2]) & 1));
            }
        }
    }
    cout << f[(1 << n) - 1] << endl;
    return 0;
}

赞赏作者

13

P2210 Haywire - 状压 DP

题面

https://www.luogu.com.cn/problem/P2210

题解

一开始没反应出来是状压,看了少数题解才知道可以状压。今天姑且 A 掉状压的写法,明天写写看模拟退火。

首先,我们来设状压的状态方程:

$$
f[i]
$$

  • $i$ 代表 $i$ 表示成二进制的时候有 $1$ 那些位所代表的牛组成的集合
  • 比方说 $i = 6 = (110)_2$ 代表有 $2, 3$ 号牛组成的集合
  • $f[i]$ 代表牛的集合为 $i$ 时对答案产生的贡献。

什么是贡献?这个说法很模棱两可。其实是这样的:两个朋友都在集合里面,那答案必然要包括这对朋友连线的贡献。但是如果只有一头牛在集合里呢?很简单,贡献就是:按照某种最佳排列时,这头牛所处的位置到集合队尾的距离。这么说很抽象,我们举个例子:

$$
~ * ~ * ~ * ~ \triangle ~ * ~ *
$$

上图中,$*$ 代表无所谓的某头牛,$\triangle$ 代表我们现在考察的单个朋友,在这个状态下,$\triangle$ 对答案的贡献就是 $2$,因为 $\triangle$ 之后还有两头牛.

而如果在这个队列之后紧跟就是 $\triangle$ 的朋友:另一个 $\triangle$,那么在增加前 $\triangle$ 给出的贡献 $2$ 就是这对朋友对答案的贡献了:

$$
~ * ~ * ~ * ~ \triangle ~ * ~ * ~ \triangle
$$

好。赘述完关于状态的定义,我们来思考状态怎么转移。对于集合 $i$,我们可以枚举位于队列尾端的到底是哪头牛。每次转移我们只需要加上原来的集合中孤立朋友的数量即可。最终给出的代码是做了一些小的优化,稍微难以理解,所以下面给出一个等价的形式,但是时间复杂度多一个 $n$,为 $O(n^22^n)$:

for (int i = 1; i < (1 << n); i ++) {
    for (int j = 0; j < n; j ++) {
        if ((1 << j) & i) {
            int pending_links = 0;
            int ii = i & ~(1 << j);
            for (int k = 0; k < n; k ++)
                if ((1 << k) & ii)
                    pending_links += 3 - 
                        (((ii >> nbr[k][0]) & 1) + 
                        ((ii >> nbr[k][1]) & 1) + 
                        ((ii >> nbr[k][2]) & 1));
            f[i] = min(f[i], f[ii] + pending_links);
        }
    }
}

代码

最终给出的代码时间复杂度为 $O(n2^n)$:

#include <iostream>
#include <cstring>

using namespace std;

const int maxn = 12;

int f[1 << maxn], nbr[maxn + 5][3];

int main() {
    int n; cin >> n;
    for (int i = 0; i < n; i ++) {
        cin >> nbr[i][0] >> nbr[i][1] >> nbr[i][2];
        nbr[i][0] --; nbr[i][1] --; nbr[i][2] --;
    }
    memset(f, 0x7f, sizeof(f));
    f[0] = 0;
    for (int i = 1; i < (1 << n); i ++) {
        int pending_links = 0;
        for (int j = 0; j < n; j ++)
            if ((1 << j) & i)
                pending_links += 3 - 
                    (((i >> nbr[j][0]) & 1) + 
                    ((i >> nbr[j][1]) & 1) + 
                    ((i >> nbr[j][2]) & 1));
        for (int j = 0; j < n; j ++) {
            if ((1 << j) & i) {
                f[i] = min(f[i], f[i & ~(1 << j)] + pending_links - (3 - 
                    (((i >> nbr[j][0]) & 1) + 
                    ((i >> nbr[j][1]) & 1) + 
                    ((i >> nbr[j][2]) & 1))) + 
                    ((i >> nbr[j][0]) & 1) + 
                    ((i >> nbr[j][1]) & 1) + 
                    ((i >> nbr[j][2]) & 1));
            }
        }
    }
    cout << f[(1 << n) - 1] << endl;
    return 0;
}