版权属于yhzq,想要引用此题(包括题面)的朋友请联系博主

T2.Van




分析:
这样把博弈黑出翔真的好吗。。。

首先我们要明确怎么计算一个序列的最长Van序列
这就和花匠那道题有异曲同工之妙了
我们只需要找到序列的所有折点即可

k=1

这种情况下我们只能把序列中的每一个数变成一样的
显然都变成中位数最优
“中位数定理”的玄学证明

k=2

这种情况下我们需要使整个序列单调不升或者不降
于是我就想到了这道题
mmp,完全是原题好伐。。。不讲

k=3

枚举中间折点,转化成k=2的情况

k<=10

注意到这个数据的n很小,那么我们可以预处理使每个区间上升或下降的最小代价
然后就可以dp了
f[i][j][k]f[i][j][k]f[i][j][k]表示第iii位,已经有了j" role="presentation" style="position: relative;">jjj段,最后一段是上升还是下降

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 20010
#define M 1010
using namespace std;
int tot, a[N], n, k;
struct heap {int ch[N][2], dis[N], val[N];int merge(int x, int y) {if (x * y == 0) return x + y;if (val[x] < val[y]) swap(x, y);ch[x][1] = merge(ch[x][1], y);if (dis[ch[x][0]] < dis[ch[x][1]])swap(ch[x][0], ch[x][1]);dis[x] = dis[ch[x][1]] + 1;return x;}int pop(int x) {return merge(ch[x][0], ch[x][1]);}void init(int x, int v) {ch[x][0] = ch[x][1] = dis[x] = 0;val[x] = v;}
} h;
struct seqs {int l, r, root, insum, innum, sum;int cost() {int v = h.val[root];return (v * innum - insum) + (sum - insum - v * (r - l + 1 - innum));}void merge(seqs now) { //now must behind thisr = now.r, root = h.merge(root, now.root);innum += now.innum, insum += now.insum, sum += now.sum;while (innum * 2 > r - l + 2)innum--, insum -= h.val[root], root = h.pop(root);}
} seq[N];
void add(int pos, int v, int &cost) {seq[++tot].root = seq[tot].l = seq[tot].r = pos;seq[tot].innum = 1, seq[tot].insum = seq[tot].sum = v;h.init(pos, v), cost += seq[tot].cost();while(tot > 1 && h.val[seq[tot].root] < h.val[seq[tot - 1].root]) {cost -= seq[tot].cost() + seq[tot - 1].cost();seq[tot - 1].merge(seq[tot]), tot--;cost += seq[tot].cost();}
}
void solve_1() {sort(a + 1, a + 1 + n);int ans = 0, mid = a[n / 2];for (int i = 1; i <= n; i++) ans += abs(a[i] - mid);printf("%d\n", ans);
}
void solve_2() {int ans = 0x3f3f3f3f, cost;cost = 0, tot = 0;for (int i = 1; i <= n; i++) add(i, a[i], cost);ans = min(ans, cost);cost = 0, tot = 0;for (int i = 1; i <= n; i++) add(i, -a[i], cost);ans = min(ans, cost);printf("%d\n", ans);
}
void solve_3() {static int fwd1[N], fwd2[N], bac1[N], bac2[N];int ans = 0x3f3f3f3f, cost;cost = tot = 0;for (int i = 1; i <= n; i++) add(i, a[i], cost), fwd1[i] = cost;cost = tot = 0;for (int i = 1; i <= n; i++) add(i, -a[i], cost), fwd2[i] = cost;cost = tot = 0;for (int i = n; i >= 1; i--) add(n - i + 1, a[i], cost), bac1[i] = cost;cost = tot = 0;for (int i = n; i >= 1; i--) add(n - i + 1, -a[i], cost), bac2[i] = cost;for (int i = 1; i <= n + 1; i++)ans = min(ans, fwd1[i - 1] + bac1[i]), ans = min(ans, fwd2[i - 1] + bac2[i]);printf("%d\n", ans);
}
void solve_4() {k--;static int fwd[M][M], bac[M][M], f[M][15], g[M][15];for (int i = 1; i <= n; i++) {int cost = 0; tot = 0;for (int j = i; j <= n; j++) add(j, a[j], cost), fwd[i][j] = cost;cost = 0, tot = 0;for (int j = i; j <= n; j++) add(j, -a[j], cost), bac[i][j] = cost;}memset(f, 0x3f, sizeof f), memset(g, 0x3f, sizeof g);f[0][0] = g[0][0] = 0;for (int i = 1; i <= n; i++)for (int j = 1; j <= k; j++)for (int l = 0; l < i; l++)f[i][j] = min(f[i][j], g[l][j - 1] + fwd[l + 1][i]), g[i][j] = min(g[i][j], f[l][j - 1] + bac[l + 1][i]);int ans = 0x3f3f3f3f;for (int i = 1; i <= k; i++) ans = min(ans, f[n][i]);for (int i = 1; i <= k; i++) ans = min(ans, g[n][i]);printf("%d\n", ans);
}
main() {freopen("van.in", "r", stdin);freopen("van.out", "w", stdout);scanf("%d%d", &n, &k);for (int i = 1; i <= n; i++)scanf("%d", &a[i]);if (k == 1) solve_1();else if (k == 2) solve_2();else if (k == 3) solve_3();else solve_4();
}

T3.Gay




分析:
题目来源:33IQ
曲神表示:啊~原题是狗,我改成了博弈,没什么太大区别嘛
。。。博弈被黑的最惨的一次。。。

算法一

最简单的还是完全图的20分

如果第一天,有人走出去看到其他所有人的博弈都正常,那么就可以确定ta的博弈病变了,于是ta就会怀着沉重的心情用炒栗子超离子垃圾处理器把ta的博弈融掉

如果第一天,有人走出去看见一家的博弈病变了,但是到了这一天没有人去融掉博弈,说明这个住宅区中一定还存在一个病变的博弈,就是自己的那只?那个?那位?那坨?
反正第二天两个人会同时怀着沉重的心情用炒栗子超离子垃圾处理器把博弈融掉(嘶~~)

以此类推

因此有xxx个病变博弈的社区需要x" role="presentation" style="position: relative;">xxx天才能确定下来,进行融化
因此两问答案都是∑ni=1C(n,i)∗i∑i=1nC(n,i)∗i\sum_{i=1}^{n}C(n,i)*i

算法二

上一个算法也还是挺有启发性的,我们发现我们在分析自己的博弈有没有病变的时候的方法是:假设没有病变,然后看前一天是否应该有熔化。而这个方法对任意图都适用的。

考虑状压dp,令dpUdpUdp_U表示UUU集合中的博弈病变时的熔化时间
然后我们接着分析:如果我的博弈没病,那么什么时候应该熔化呢?
于是要枚举了所有可能的病变情况V" role="presentation" style="position: relative;">VVV,那么如果他的博弈没有病变,最迟应该在
max dpVmaxdpVmax~dp_V的时间有人熔化自己的博弈
所以他会在第max dpV+1maxdpV+1max~dp_V+1天熔化

所以我们就知道怎么DP了!
对于一个状态,我们枚举这个状态中每个病变的博弈的主人,那么这个状态的熔化时间应该是这些病变的博弈主人中熔化时间的最小值,从中我们也可以知道有多少人同时熔化

那么一个人会枚举哪些可能的病变情况呢:首先对于他看到的博弈,他枚举的病变情况一定是和当前情况相同的,其次他自己一定是没有病变的,而至于那些他看不到的博弈,他就只好枚举这些博弈的所有病变状况了

最后,如果有限时间内不会熔化怎么办?事实上是存在这种情况的,但是没关系,如果我们发现转移出现了环,所以这个环中的所有状态都是在有限时间内不会熔化的

时间复杂度似乎是O(4nn)O(4nn)O(4^nn)?
可以通过前两个测试点

算法三

有了算法二,我们可以把表打出来看看,然后就可以发现一个小小的规律:
对于病变状态U和V,如果U∈V,那么必然有dpU≤dpVdpU≤dpVdp_U≤dp_V

至于这一个规律的证明放在后面

所以就可以只枚举一种病变情况:首先对于所有他看到的博弈,他枚举的病变情况一定是和当前情况相同的,其次他自己一定是没有病变的,而至于那些他看不到的博弈,由刚才的规律,可以全部当成病变的

所以时间复杂度就可以降到O(2nn)O(2nn)O(2^nn),可以通过前四个测试点

算法四

为了取得更多的分,我们需要一个多项式算法
我们重新来考虑有限时间内熔化的条件:转移无环
实际上这是一个很强的条件

根据转移建一个新图,如果iii不能看到j" role="presentation" style="position: relative;">jjj,那么就在iii到j" role="presentation" style="position: relative;">jjj连条有向边
这个新图中可能有若干个强连通分量,如果一个病变状态中有病变的博弈的主人是在一个多于一个点的强连通分量中,那么这一个状态一定无法在有限时间内熔化(转移有环),否则一定能在有限时间内熔化(转移无环)

那么我们先把所有可以到达大小大于1的强连通分量的点连同和他们相关的边一起删掉,于是我们得到了一个DAG!

接着我们继续站在这张图的立场上来想怎么求一个状态的熔化时间:
首先这张图上有一些点被染黑了,黑点表示这个博弈有病,白点表示这个博弈没病
接着每一时刻,我们可以把一个黑点染白,然后把这个点连向的点的集合的一个子集染黑
若干轮之后,所有点都变白了
假设kkk个点一度被染黑,那么熔化时间就是所有操作方案中最大的k" role="presentation" style="position: relative;">kkk

看起来这个转化比较意识流,但是如果我们考虑怎么DP所有操作方案中最大的kkk,我们会发现这个DP方法和算法二是等价的
如果再要解释的话,首先枚举博弈主人相当于枚举反转的黑点,算博弈主人熔化时间中的max体现在求最大的一度被染黑的点数,算状态的熔化时间中对博弈主人熔化时间取min体现在多次被染黑的点只被计算一次

而在这个模型下,算法三的小规律就很显然了
增加黑点对答案的贡献一定非负,所以问题又变成了:

一个DAG上有一些点被染黑了,接着每一时刻,我们可以把一个黑点染白,然后把这个点连向的点全部染黑。若干轮之后,所有点都变白了
假设有k个点一度被染黑,求所有操作方案中最大的k" role="presentation" style="position: relative;">kkk
这不就是求DAG上一个点集能直接或者间接到达的点数吗?

所以我们把判断一个病变状态在第几天熔化转化为了求DAG上一个集合能直接或间接到达的点数。这样第一问已经非常简单了。考虑算每一个点的贡献,如果一个点能被iii​个点到达(包括自己),而DAG上一共有N" role="presentation" style="position: relative;">NNN​个点,那么显然这个点的贡献就是(2i−1)×2N−i(2i−1)×2N−i(2^i−1)×2^{N−i}

接下来考虑第二问,一个人要在DAG上满足什么样的条件才会在第一天熔化呢?
在原来的DP中,我们是在取min的过程中得到同时熔化的人数的,而我们知道,在现在的模型中,算状态的熔化时间中对博弈主人熔化时间取min体现在多次被染黑的点只被计算一次。

所以我们脑补一下可以得到:如果一个点第一个反转,且之后得到的状态无论怎么操作都无法再把这个点染黑,那么这个点对应着一个最早熔化的博弈主人。

所以第二问也可以很简单的解决了,还是考虑算每一个点的贡献,如果一个点能被iii个点到达(包括自己),而DAG上一共有N" role="presentation" style="position: relative;">NNN个点,那么显然这个点的贡献就是2N−i2N−i2^{N−i}

因为计算DAG中每一个点可以被多少点到达是O(nm)O(nm)O(nm)的,而在这个DAG中mmm是O(n2)" role="presentation" style="position: relative;">O(n2)O(n2)O(n^2)的,所以时间复杂度是O(n3)O(n3)O(n^3),可以通过前六个点。

算法五

最后的技术含量就很低了,这个问题显然是可以压位的嘛,所以可以把时间复杂度搞到O(n332)O(n332)O({n^3 \over 32}),可以通过所有测试点

#include <cstdio>
#include <bitset>
using namespace std;
typedef long long ll;
const int N = 3030;
const int mod = 998244353;
bitset <N> b[N];
int d[N], que[N], g[N][N], n, m, t;
int pw2[N], ans1, ans2;
char s[N];
main() {freopen("gay.in", "r", stdin);freopen("gay.out", "w", stdout);scanf("%d", &n);for (int i = 1; i <= n; ++i) {scanf("%s", s + 1), s[i] = '1';for (int j = 1; j <= n; ++j)if (s[j] == '0')d[i]++, g[i][j] = 1;}pw2[0] = 1;for (int i = 1; i <= n; ++i) pw2[i] = (pw2[i - 1] << 1) % mod;for (int i = 1; i <= n; ++i) if (!d[i]) que[++t] = i;for (int i = 1; i <= t; ++i)for (int j = 1, now = que[i]; j <= n; ++j)if (g[j][now] && !--d[j]) que[++t] = j;for (int i = t; i; --i) {int now = que[i];b[now][now] = 1;int cnt = b[now].count();ans1 = (ans1 + 1ll * (pw2[cnt] - 1) * pw2[t - cnt]) % mod;ans2 = (ans2 + pw2[t - cnt]) % mod;for (int j = 1; j <= n; ++j)if (g[now][j]) b[j] |= b[now];}printf("%d %d\n", ans1, ans2);
}

曲神的hu测 T2.Van(左偏树+dp)T3.Gay相关推荐

  1. P3642 [APIO2016]烟火表演(左偏树、函数)

    解析 感觉是左偏树的神题了. 首先有一个比较显然的结论,一个合法的方案中,两个叶子到它们 lca\text{lca}lca 的距离必须相等. 考虑设计 dp\text{dp}dp : fi,xf_{i ...

  2. 可并堆——左偏树 Leftist Heap

    今天学习了左偏树,这是一个好理解而且好写的数据结构,和二叉堆一样可以在O(1)时间内取出优先级最高的值,O(logn)时间内删除优先级最高的值,不同的是如果要合并两个堆那么二叉堆就只能跪了.而左偏树能 ...

  3. 洛谷P4971:断罪者(左偏树)

    解析 看起来就是左偏树的基本操作啊- 然而就是调不过去 吐了qwq 参考了望月大神的实现 感觉清晰的多 就定义并查集维护的是每个点所在的堆的根节点 一下子少了很多恶心的套娃 代码 #include&l ...

  4. 左偏树(可并堆)初步及其应用

    作者:hsez_yyh 链接:左偏树(可并堆)初步及其应用_hsez_yyh的博客-CSDN博客 来源:湖北省黄石二中信息竞赛组        著作权归作者所有.商业转载请联系作者获得授权,非商业转载 ...

  5. 《程序设计解题策略》——1.6 利用左偏树实现优先队列的合并

    本节书摘来自华章计算机<程序设计解题策略>一书中的第1章,第1.6节,作者:吴永辉 王建德 更多章节内容可以访问云栖社区"华章计算机"公众号查看. 1.6 利用左偏树实 ...

  6. 【洛谷3377】 左偏树(可并堆)

    前言 其实我是不小心翻线性基的时候看见的. Solution 左偏树只会模板,挖坑待补 代码实现 #include<stdio.h> #include<stdlib.h> #i ...

  7. 【学习笔记】浅谈短小可爱的左偏树(可并堆)

    文章目录 左偏树 左偏树的合并(merge)操作 例题 罗马游戏 [Apio2012]dispatching [JLOI2015]城池攻占 [Baltic2004]sequence 左偏树 左偏树是一 ...

  8. YbtOJ#631-次短路径【左偏树,最短路】

    正题 题目链接:https://www.ybtoj.com.cn/contest/114/problem/1 题目大意 给出nnn个点mmm条边的一张无向图,对于每个点iii求不经过i∼1i\sim ...

  9. P4331-[BalticOI2004]Sequence数字序列【左偏树】

    正题 题目链接:https://www.luogu.com.cn/problem/P4331 题目大意 给出一个序列aaa,求一个单调上升的序列bbb使得∑i=1n∣ai−bi∣\sum_{i=1}^ ...

最新文章

  1. 【Qt】QMainWindow最大化按钮是灰色(不能最大化)的解决方法
  2. kmeans聚类算法_聚类算法入门:k-means
  3. Second Week: Git与Github的使用
  4. C#写的windows应用程序打包
  5. 【转】[iOS] 关于 self = [super init];
  6. 计算机应用基础理论,计算机应用基础理论A卷.doc
  7. Linux信号实践(2) --信号分类
  8. docker pull 国内镜像_MAC版 的最新Docker 2.2版本配置国内代理的解决办法
  9. python程序启动其他python程序_python中使用fork创建新的进程
  10. clouder manager卸载
  11. 微信群越来越多,我该如何科学地管理?
  12. pytorch---线性回归实现
  13. 前端展示m3u8视频
  14. 3Dmax专用快捷键大全(保姆式手把手教)
  15. codeforces每日5题(均1500)-第八天
  16. Linux命令curl详解(一)
  17. Boosting(一)
  18. 麦克风离计算机主机多远,唱歌的时候,嘴离麦克风多远最好听?
  19. Tensorflow Win10 stage.2
  20. 雨果奖得主刘慈欣(《三体》作者)如何看待人工智能?

热门文章

  1. 京东销售联盟佣金支付始末
  2. 封闭类(Sealed Classes)
  3. 【详解】分页方式:Page Helper的使用
  4. 记SWPU2021 GFCTF线下AWD赛
  5. 数组c语言抓小偷,警察抓小偷C语言源码
  6. 解决Vivado HLS 高层综合失败
  7. mysql事务隔离级别之锁实现原理,脏读、不可重复读、幻读出现原因及解决方案
  8. 用html、javascript写一个网页,网页内容包括一个下拉列表,内容为语文、数学、英语,一个单选按钮,内容为苹果、桃子、西瓜,默认选中数学、西瓜
  9. 拨开发展迷雾,将“智慧”嵌入全业务场景【2022戴尔科技峰会预告】
  10. 航空航天与国防数字化验证解决方案 | 达索系统百世慧®