题目链接

前言

在这里分享两种的做法。

一种是我第一直觉的 模拟退火。(也就是骗分)

还有一种是看题解才搞懂的神仙折半搜索加上 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\) ,我们来看看其代价的公式。

\[val=\begin{cases}
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\) 的话参见以下的转移方程

\[f_{S\bigcup{i}}=\min\{f_{S\bigcup{i}}\ , f_s+g_{i,s}\}
\]

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 信号传递 做题感想的更多相关文章

  1. HDU 2016.11.12 做题感想

    细数一下这两天做过的值得总结的一些题Orz...... HDU 2571 简单dp,但是一开始WA了一发.原因很简单:没有考虑仔细. 如果指向该点的所有点权值都为负数,那就错了(我一开始默认初始值为0 ...

  2. P2599 [ZJOI2009]取石子游戏 做题感想

    题目链接 前言 发现自己三岁时的题目都不会做. 我发现我真的是菜得真实. 正文 神仙构造,分讨题. 不敢说有构造,但是分讨我只服这道题. 看上去像是一个类似 \(Nim\) 游戏的变种,经过不断猜测结 ...

  3. UOJ 做题记录

    UOJ 做题记录 其实我这么弱> >根本不会做题呢> > #21. [UR #1]缩进优化 其实想想还是一道非常丝播的题目呢> > 直接对于每个缩进长度统计一遍就好 ...

  4. C语言程序设计做题笔记之C语言基础知识(下)

    C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...

  5. C语言程序设计做题笔记之C语言基础知识(上)

    C语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.并且C是相当灵活的,用于执行计算机程序能完成的几乎 ...

  6. 屏蔽Codeforces做题时的Problem tags提示

    当在Codeforces上做题的时,有时会无意撇到右侧的Problem tags边栏,但是原本并不希望能够看到它. 能否把它屏蔽了呢?答案是显然的,我们只需要加一段很短的CSS即可. span.tag ...

  7. ACM 做题过程中的一些小技巧。

    ACM做题过程中的一些小技巧. 1.一般用C语言节约空间,要用C++库函数或STL时才用C++; cout.cin和printf.scanf最好不要混用. 2.有时候int型不够用,可以用long l ...

  8. [日记&做题记录]-Noip2016提高组复赛 倒数十天

    写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...

  9. CodeM美团点评编程大赛复赛 做题感悟&题解

    [T1] [简要题意]   长度为N的括号序列,随机确定括号的方向:对于一个已确定的序列,每次消除相邻的左右括号(右左不行),消除后可以进一步合并和消除直到不能消为止.求剩下的括号的期望.\(N \l ...

随机推荐

  1. 小米路由器3G R3G 刷入Breed和OpenWrt 20.02.2 的记录

    小米 R3G 参数 Architecture: MIPS Vendor: Mediatek Bootloader: U-Boot System-On-Chip: MT7621 family CPU/S ...

  2. 解决go-micro与其它gRPC框架之间的通信问题

    在之前的文章中分别介绍了使用gRPC官方插件和go-micro插件开发gRPC应用程序的方式,都能正常走通.不过当两者混合使用的时候,互相访问就成了问题.比如使用go-micro插件生成的gRPC客户 ...

  3. Linux根目录下各文件目录的作用

    bin        用户二进制可执行文件    boot        系统启动引导文件    dev[device]        系统中使用的外部设备,但不是放的外部设备的驱动.一个访问这些外部 ...

  4. 2022最新IntellJ IDEA的zheng开发部署文档

    目录 前景提示 一.环境整合 构建工具(参考工具部署方式) 二.git 导入编译器 三.模块描述浅析 四.配置文档 1.总配置 2.数据库配置 3.密码设置 4.配置建议 五.在IDEA中执行MySQ ...

  5. XCTF练习题---MISC---小小的PDF

    XCTF练习题---MISC---小小的PDF flag:SYC{so_so_so_easy} 解题步骤: 1.观察题目,下载附件 2.下载完发现是一个PDF文件,经过转Word,查看属性,十六进制查 ...

  6. 架构师必备:Redis的几种集群方案

    结论 有以下几种Redis集群方案,先说结论: Redis cluster:应当优先考虑使用Redis cluster. codis:旧项目如果仍在使用codis,可继续使用,但也推荐迁移到Redis ...

  7. idea打开service窗口

  8. 使用FastJson导出JSON

    概述 fastjson是目前java语言中最快的json库,比自称最快的jackson速度要快,比gson快大约6倍. https://github.com/alibaba/fastjson Fast ...

  9. web渗透学习目录

    一,基础学习 01.基础学习 [[编码总结]] [[JSON三种数据解析方法]] [[js加密,解密]] [[Internet保留地址和非保留地址.内网和公网.VNC和UltraVN]] 代理 [[S ...

  10. 使用requests爬取梨视频、bilibili视频、汽车之家,bs4遍历文档树、搜索文档树,css选择器

    今日内容概要 使用requests爬取梨视频 requests+bs4爬取汽车之家 bs4遍历文档树 bs4搜索文档树 css选择器 内容详细 1.使用requests爬取梨视频 # 模拟发送http ...