P6622 信号传递 做题感想
前言
在这里分享两种的做法。
一种是我第一直觉的 模拟退火。(也就是骗分)
还有一种是看题解才搞懂的神仙折半搜索加上 dp 。
模拟退火
众所周知,模拟退火 是我这种没脑子选手用来骗分的好算法。
具体呢就是通过不断随机,来不断接近所谓的正解。

不得不说,那个调参是真滴恶心,经过我不断的调参,分数在 \(50\ pts \sim 75\ pts\) 之间横跳。
最后 \(75 \ pts\) 实在是吐了,所以就看题解了(bushi)。
这题的话模拟退火的思路还是很显然的。
每次模拟退火直接选出两个不相同的位置 \(x\) 和 \(y\) 交换即可。
注意:每次更新我们只需要更新 \(x\) 和 \(y\) 及其相关的节点
Code
#include <bits/stdc++.h>
#define file(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
#define quad putchar(' ')
#define Enter putchar('\n')
const int N = 100005;
const int M = 25;
const double eps = 0.0001;
#define int long long
int n, m, k, s[N], mid, left, right;
int pr[M][M], a[M], ans = 0x3f3f3f3f, nows;
std::mt19937 seed(1609745220);
template <class T> T rand(T l, T r) {
return std::uniform_int_distribution<int>(l, r)(seed);
}
inline int dis (int a, int b) {
if (a < b) return b - a;
else return (a + b) * k;
}
inline void query() {
int res = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m; j++) {
if (i == j) continue;
res += pr[i][j] * dis(a[i], a[j]);
}
}
nows = res;
ans = std::min(ans, res);
}
inline void Del(int x) {
for (int i = 1; i <= m; i++) {
if (i == x) continue;
nows -= pr[x][i] * dis(a[x], a[i]);
nows -= pr[i][x] * dis(a[i], a[x]);
}
}
inline void add(int x) {
for (int i = 1; i <= m; i++) {
if (i == x) continue;
nows += pr[x][i] * dis(a[x], a[i]);
nows += pr[i][x] * dis(a[i], a[x]);
}
}
inline void add(int x, int y) {
nows += pr[x][y] * dis(a[x], a[y]);
nows += pr[y][x] * dis(a[y], a[x]);
}
inline void Del(int x, int y) {
nows -= pr[x][y] * dis(a[x], a[y]);
nows -= pr[y][x] * dis(a[y], a[x]);
}
inline void fire_ans() {
query();
double T = 1014919.1919191919191, deltT = 0.99992919191;
while (T > eps) {
int x = rand() % m + 1, y = rand() % m + 1;
while (x == y)
x = rand() % m + 1, y = rand() % m + 1;
int lasts = nows;
Del(x), Del(y), add(x, y);
std::swap(a[x], a[y]);
add(x), add(y), Del(x, y);
int delt = nows - lasts;
if (delt < 0)
ans = std::min(ans, nows);
else {
if (exp(-delt / T) * RAND_MAX <= rand())
nows = lasts, std::swap(a[x], a[y]);
}
T *= deltT;
}
}
signed main(void) {
// file("P6622");
srand(1609745220);
std::cin >> n >> m >> k;
if (n == 1) {
printf("0\n");
return 0;
}
for (int i = 1; i <= n; i++)
scanf("%lld", &s[i]);
for (int i = 1; i < n; i++)
pr[s[i]][s[i + 1]] ++;
for (int i = 1; i <= m; i++)
a[i] = i;
while(((double)clock())/(double)CLOCKS_PER_SEC <= 2.4)
fire_ans();
std::cout << ans << std::endl;
}
状压 DP
没脑子选手想不到这样的做法。
观察对于任意一对 \(x\) 和 \(y\) ,我们来看看其代价的公式。
pos_y - pos_x \quad (posx \leq pos_y)\\
k\times(pos_x + pos_y)\quad(pos_x > posy)
\end{cases}
\]
因为如果每次都调用 \(S_i\) 来进行查询的话显然是不现实的。
考虑把代价转移到每一个坐标之上。
具体来说,对于最终在坐标为 \(x\) 的点,序列中每有一条 \(x\rightarrow y\) 的边,若 \(x\le y\) 则付出 \(k\) 的代价,否则付出 \(−1\) 的代价。同理对于 \(y\rightarrow x\) 的边分别付出 \(1\) 和 \(k\) 的代价。最终总代价等于每个点的代价分别乘其坐标再求和。
直接照搬题解上的话,没脑子选手不可能想出来。
显然,之后我们计算答案时只需要考虑一个点它前面的点的集合即可。
我们定义 \(num_{i,j}\) 表示在 \(S_i\) 中 \(i\rightarrow j\) 的条数。
定义 \(g_{i,mask}\) 表示当前 \(i\) 点,之前集合是 \(mask\) 时每一位的系数大小。
定义 \(f_i\) 表示集合为 \(i\) 时的最小代价。
对于 \(g_{i,mask}\) 我们直接对照一开始给出的公式计算即可。
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
if (i == j) continue;
g[i][0] += -num[i][j] + k * num[j][i];
}
} // 考虑所有的都在 i 的后面
for (int i = 0; i < m; i++) {
for (int j = 1; j < mid; j++) {
int now = lowbit(j);
int z = lg[now];
if (z >= i) z ++;
g[i][j] = g[i][j ^ now] + num[i][z] * (1 + k) + num[z][i] * (1 - k);
}
} // 考虑删掉其中的一个点,然后直接套公式
注意到,我们没有必要考虑 \(i\in S\) 的 \(g_{i,S}\) 。这样对于每个 \(i\) 而言只有 \(2^{22}\) 种 \(S\) 需要计算。
至于 \(f_i\) 的话参见以下的转移方程
\]
Code
#include <bits/stdc++.h>
#define file(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
#define quad putchar(' ')
#define Enter putchar('\n')
const int N = 100005;
const int M = 25;
const int MAX = (1 << 23) + 5;
int n, m, k, s[N], num[M][M], g[M][MAX / 2];
int lg[MAX], siz[MAX], f[MAX], mid, mxmask;
inline int lowbit(int x) {
return x & -x;
}
signed main(void) {
// file("P6622");
std::cin >> n >> m >> k;
mxmask = 1 << m;
mid = mxmask >> 1;
for (int i = 1; i <= n; i++)
scanf("%d", &s[i]), s[i] --;
for (int i = 1; i < n; i++)
if (s[i] != s[i + 1])
num[s[i]][s[i + 1]] ++;
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
if (i == j) continue;
g[i][0] += -num[i][j] + k * num[j][i];
}
}
lg[0] = -1;
for (int i = 1; i < mxmask; i++) {
lg[i] = lg[i / 2] + 1;
siz[i] = siz[i / 2] + i % 2;
}
for (int i = 0; i < m; i++) {
for (int j = 1; j < mid; j++) {
int now = lowbit(j);
int z = lg[now];
if (z >= i) z ++;
g[i][j] = g[i][j ^ now] + num[i][z] * (1 + k) + num[z][i] * (1 - k);
}
}
// for (int i = 0; i < m; i++, printf("\n"))
// for (int j = 0; j < mid; j++)
// printf("%d ", g[i][j]);
memset(f, 0x3f, sizeof(f));
f[0] = 0;
int y;
for (int i = 1; i < mxmask; i++) {
for (int x = i; y = lowbit(x); x ^= y) {
int z = lg[y];
int last = i ^ y;
f[i] = std::min(f[i], f[i ^ y] + g[z][last & y - 1 | last >> z + 1 << z] * siz[i]);
}
}
std::cout << f[mxmask - 1] << std::endl;
}
P6622 信号传递 做题感想的更多相关文章
- HDU 2016.11.12 做题感想
细数一下这两天做过的值得总结的一些题Orz...... HDU 2571 简单dp,但是一开始WA了一发.原因很简单:没有考虑仔细. 如果指向该点的所有点权值都为负数,那就错了(我一开始默认初始值为0 ...
- P2599 [ZJOI2009]取石子游戏 做题感想
题目链接 前言 发现自己三岁时的题目都不会做. 我发现我真的是菜得真实. 正文 神仙构造,分讨题. 不敢说有构造,但是分讨我只服这道题. 看上去像是一个类似 \(Nim\) 游戏的变种,经过不断猜测结 ...
- UOJ 做题记录
UOJ 做题记录 其实我这么弱> >根本不会做题呢> > #21. [UR #1]缩进优化 其实想想还是一道非常丝播的题目呢> > 直接对于每个缩进长度统计一遍就好 ...
- C语言程序设计做题笔记之C语言基础知识(下)
C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...
- C语言程序设计做题笔记之C语言基础知识(上)
C语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.并且C是相当灵活的,用于执行计算机程序能完成的几乎 ...
- 屏蔽Codeforces做题时的Problem tags提示
当在Codeforces上做题的时,有时会无意撇到右侧的Problem tags边栏,但是原本并不希望能够看到它. 能否把它屏蔽了呢?答案是显然的,我们只需要加一段很短的CSS即可. span.tag ...
- ACM 做题过程中的一些小技巧。
ACM做题过程中的一些小技巧. 1.一般用C语言节约空间,要用C++库函数或STL时才用C++; cout.cin和printf.scanf最好不要混用. 2.有时候int型不够用,可以用long l ...
- [日记&做题记录]-Noip2016提高组复赛 倒数十天
写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...
- CodeM美团点评编程大赛复赛 做题感悟&题解
[T1] [简要题意] 长度为N的括号序列,随机确定括号的方向:对于一个已确定的序列,每次消除相邻的左右括号(右左不行),消除后可以进一步合并和消除直到不能消为止.求剩下的括号的期望.\(N \l ...
随机推荐
- springmvc-02(配置版与注解版区别)
首先,我们来看配置版和注解版的相同步骤: 1.新建一个Moudle , springmvc-02-hello , 添加web的支持! 2.确定导入了SpringMVC 的依赖! 3.配置web.xml ...
- Next.js 在 Serverless 中从踩坑到破茧重生
作者 杨苏博,偏后端的全栈开发,目前负责腾云扣钉的 Cloud Studio 产品.在团队中负责接技术架构设计与 Review.Cloud Studio 编辑器内核设计与开发.部分核心插件设计与开发: ...
- 143. Reorder List - LeetCode
Question 143. Reorder List Solution 题目大意:给一个链表,将这个列表分成前后两部分,后半部分反转,再将这两分链表的节点交替连接成一个新的链表 思路 :先将链表分成前 ...
- shellcode编写
shellcode编写 shellcode是一段用于利用软件漏洞而执行的代码,通常使用机器语言编写,其目的往往是让攻击者获得目标机器的命令行shell而得名,其他有类似功能的代码也可以称为shellc ...
- Python数据分析--Numpy常用函数介绍(4)--Numpy中的线性关系和数据修剪压缩
摘要:总结股票均线计算原理--线性关系,也是以后大数据处理的基础之一,NumPy的 linalg 包是专门用于线性代数计算的.作一个假设,就是一个价格可以根据N个之前的价格利用线性模型计算得出. 前一 ...
- Grafana+Prometheus 搭建 JuiceFS 可视化监控系统
作为承载海量数据存储的分布式文件系统,用户通常需要直观地了解整个系统的容量.文件数量.CPU 负载.磁盘 IO.缓存等指标的变化. JuiceFS 没有重复造轮子,而是通过 Prometheus 兼容 ...
- C++primer第二章
第二章 :变量和基本类型 2.1 基本内置类型 C++定义了一套包含算术类型(arithmetic type)和空类型(void)在内的基本数据类型 2.1.1 算术类型 算术类型的分类: 整型(in ...
- 最强肉坦:RUST多线程
Rust最近非常火,作为coder要早学早享受.本篇作为该博客第一篇学习Rust语言的文章,将通过一个在其他语言都比较常见的例子作为线索,引出Rust的一些重要理念或者说特性.这些特性都是令人心驰神往 ...
- Docker打包镜像并上传
Docker打包镜像并上传 登录 账号 docker login --username=yourusername 密码 yourPassword 推送到仓库 docker镜像打标签 docker ta ...
- 拥抱Spring全新OAuth解决方案
以下全文 Spring Authorization Server 简称为: SAS 背景 Spring 团队正式宣布 Spring Security OAuth 停止维护,该项目将不会再进行任何的迭代 ...