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 ...
随机推荐
- MATLAB R2019b超详细安装教程(附完整安装文件)
摘要:本文详细介绍Matlab的安装步骤,为方便安装这里提供了完整安装文件的百度网盘下载链接供大家使用.从文件下载到证书安装本文都给出了每个步骤的截图,按照图示进行即可轻松完成安装使用.本文目录如首页 ...
- 标准输入输出() & 打印流 &配置文件
public static void main(String[] args) { //System 类 的 public final static InputStream in = null; // ...
- SoftPool:基于Softmax加权的池化操作 | 2021新文
SoftPool使用softmax进行加权池化,能够保持特征的表达性并且是可微操作.从性能和准确率来看,SoftPool是目前的常规池化方法的一个不错的替代品 来源:晓飞的算法工程笔记 公众号 论 ...
- tensorflwo-gpu win10_64bit 的安装版本问题
tensorflow 1.3 配 cuda8.0 + cudnn5.1tensorflow 1.4 配 cuda8.0 + cudnn6.0 有没有更大的字体???我要配!!!!!
- JZ008和大于等于target的最短数组
title: 长度最小的子数组 题目描述 题目链接:长度最小的子数组.剑指offer008 解题思路 简单滑动窗口题目,需要知道: 窗口左指针移动条件:窗口内总和 ≥ target 即可以不断移动窗口 ...
- 隔离这几天开发了一个带控制台的OAuth2授权服务器分享给大家
停更这些天,业余时间和粉丝群的几个大佬合作写了一个基于Spring Authorization Server的OAuth2授权服务器的管理控制台项目Id Server,我觉得这个项目能够大大降低OAu ...
- 使用MinIO搭建对象存储服务
1.MinIO是什么? MinIO 是一款高性能.分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件.即X86等低成本机器也能够很好的运行MinIO. MinIO与传统的存储和其 ...
- C# settings 文件基础用法
原文 自定义设置项类型 Serializable 修饰的枚举,可作为设置项类型 [Serializable] public enum DeviceBrand { None = 0, [Descript ...
- Unity-UGUI-无限循环列表
前言:项目准备新增一个竞技场排行榜,策划规定只显示0-400名的玩家.我一想,生成四百个游戏物体,怕不是得把手机给卡死?回想原来在GitHub上看到过一个实现思路就是无限循环列表,所以就想自己试试.公 ...
- 深度学习与CV教程(10) | 轻量化CNN架构 (SqueezeNet,ShuffleNet,MobileNet等)
作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...