description

小 R 与小 W 在玩游戏。

他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3,\dots,n\)。最开始凸多边形中有 \(n\) 条线段,即多边形的 \(n\) 条边。这里我们用一个有序数对 \((a,b)\)(其中 \(a<b\))来表示一条端点分别为顶点 \(a,b\) 的线段。

在游戏开始之前,小 W 会进行一些操作。每次操作时,他会选中多边形的两个互异顶点,给它们之间连一条线段,并且所连的线段不会与已存的线段重合、相交(只拥有一个公共端点不算作相交)。他会不断重复这个过程,直到无法继续连线,这样得到了状态 \(S_0\)。\(S_0\) 包含的线段为凸多边形的边与小 W 连上的线段,容易发现这些线段将多边形划分为一个个三角形区域。对于其中任意一个三角形,其三个顶点为 \(i,j,k(i<j<k)\),我们可以给这个三角形一个标号 \(j\),这样一来每个三角形都被标上了 \(2,3,\dots,n-1\) 中的一个,且没有标号相同的两个三角形。

小 W 定义了一种「旋转」操作:对于当前状态,选定 \(4\) 个顶点 \(a,b,c,d\),使其满足 \(1\leq a<b<c<d\leq n\) 且它们两两之间共有 \(5\) 条线段—— \((a,b),(b,c),(c,d),(a,d),(a,c)\),然后删去线段 \((a, c)\),并连上线段 \((b,d)\)。那么用有序数对 \((a,c)\) 即可唯一表示该次「旋转」。我们称这次旋转为 \((a,c)\) 「旋转」。显然每次进行完“旋转”操作后多边形中依然不存在相交的线段。

当小 W 将一个状态作为游戏初始状态展示给小 R 后,游戏开始。游戏过程中,小 R 每次可以对当前的状态进行「旋转」。在进行有限次「旋转」之后,小 R 一定会得到一个状态,此时无法继续进行「旋转」操作,游戏结束。那么将每一次「旋转」所对应的有序数对操作顺序写下,得到的序列即为该轮游戏的操作方案

为了加大难度,小 W 以 \(S_0\) 为基础,产生了 \(m\) 个新状态。其中第 \(i\) 个状态 \(S_i\) 为对 \(S_0\) 进行一次「旋转」操作后得到的状态。你需要帮助小 R 求出分别以 \(S_0,S_1,\dots,S_m\) 作为游戏初始状态时,小 R 完成游戏所用的最少「旋转」次数,并根据小 W 的心情,有时还需求出旋转次数最少不同操作方案数。由于方案数可能很大,输出时请对 \(10^9+7\) 取模。

原题传送门。

solution

把三角剖分每条边 \((a, b)\) 看成区间 \((a, b)\)(不妨假设 \(a < b\))。

则根据三角剖分的性质,任意两区间要么相离,要么包含,且存在区间 \((1,2),(2,3)\dots,(n-1,n)\) 与 \((1,n)\)。

考虑建树:区间 \((l, r)\) 的儿子为它所包含的极大区间 \((p, q)\)(不存在区间介于 \((p, q)\) 与 \((l, r)\) 之间)。

那么该树叶子为 \((1,2),(2,3)\dots,(n-1,n)\),根为 \((1,n)\)。

考虑这棵树的特殊性质:不存在儿子个数恰好为 \(1\) 的非叶结点,非叶结点数量为 \(n - 2\)(算上根结点),叶子数量为 \(n - 1\)。

那么显然这棵树是二叉树,且非叶结点的儿子个数恰为 2。

考虑旋转操作对应到树上的含义:右旋非叶结点(真就“旋转”操作啊)。

考虑终止状态的含义:非叶结点形成一条右链。也就是说不存在非叶结点 x 在 y 的左子树内。

我们可以先删掉叶结点。

每次旋转最多会使一个点不在某点左子树中,且总可以旋转最右链上的点使得左子树点减少。

那么最小次数即最右链上的点的左子树大小总和 cnt。

考虑方案数,每次可以任选最右链上某一点进行旋转。通过简单的组合推导可得方案数为:

\[\frac{cnt!}{\prod_{x是左子树点}siz_x}
\]

一次右旋只会影响 \(O(1)\) 个点,简单分类回答询问即可。

accepted code

#include <map>
#include <cstdio>
#include <vector>
#include <cassert>
#include <iostream>
#include <algorithm>
using namespace std; typedef pair<int, int> pii;
#define pr make_pair
#define last(v) v[v.size() - 1]
#define all(v) v.begin(), v.end()
#define pb(x) push_back(x) const int MOD = int(1E9) + 7;
const int MAXN = 100000; inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int)(1LL * x * y % MOD);} int fct[MAXN + 5], inv[MAXN + 5];
void init() {
fct[0] = 1; for(int i=1;i<=MAXN;i++) fct[i] = mul(fct[i - 1], i);
inv[1] = 1; for(int i=2;i<=MAXN;i++) inv[i] = sub(0, mul(inv[MOD % i], MOD / i));
} map<pii, int>mp; int ncnt;
int id(int l, int r) {
if( mp.count(pr(l, r)) ) return mp[pr(l, r)];
else return mp[pr(l, r)] = (++ncnt);
}
vector<int>vl[MAXN + 5], vr[MAXN + 5]; bool rgt[MAXN + 5]; int siz[MAXN + 5], ch[2][MAXN + 5], fa[MAXN + 5];
int build(int l, int r, bool f) {
if( l + 1 == r ) return 0;
int x = id(l, r), m = last(vr[l]);
assert(last(vl[r]) == m);
vr[l].pop_back(), vl[m].pop_back(), fa[ch[0][x] = build(l, m, false)] = x;
vr[m].pop_back(), vl[r].pop_back(), fa[ch[1][x] = build(m, r, f)] = x;
siz[x] = siz[ch[0][x]] + siz[ch[1][x]] + 1, rgt[x] = f;
return x;
} bool cmp(const int &x, const int &y) {return x > y;}
int main() {
init(); int W, n, m; scanf("%d%d", &W, &n);
for(int i=1;i<n;i++) vr[i].pb(i + 1);
for(int i=n;i>1;i--) vl[i].pb(i - 1);
for(int i=1;i<=n-3;i++) {
int x, y; scanf("%d%d", &x, &y);
vl[x].pb(y), vr[x].pb(y), vl[y].pb(x), vr[y].pb(x);
}
for(int i=1;i<=n;i++)
sort(all(vr[i])), sort(all(vl[i]), cmp);
build(1, n, true); int ans = 1, cnt = 0;
for(int i=1;i<=ncnt;i++)
if( !rgt[i] ) cnt++, ans = mul(ans, inv[siz[i]]);
if( W == 1 ) printf("%d %d\n", cnt, mul(ans, fct[cnt]));
else printf("%d\n", cnt); scanf("%d", &m);
for(int i=1;i<=m;i++) {
int l, r; scanf("%d%d", &l, &r);
int x = id(l, r), cnt1, ans1;
if( rgt[fa[x]] )
cnt1 = cnt - 1, ans1 = mul(mul(ans, fct[cnt - 1]), siz[x]);
else {
int p = ch[1][fa[x]], q = ch[1][x];
cnt1 = cnt, ans1 = mul(mul(ans, fct[cnt]), mul(siz[x], inv[siz[p] + siz[q] + 1]));
}
if( W == 1 ) printf("%d %d\n", cnt1, ans1);
else printf("%d\n", cnt1);
}
}

details

当然也可以不用像这样显性地建树。

事实上这道题转化问题的方法比较多,这里只是采用了我使用的一种。

最后代码实现比较简单,对问题一环套一环的分析更有趣些。

【loj - 3056】 「HNOI2019」多边形的更多相关文章

  1. Loj #3056. 「HNOI2019」多边形

    Loj #3056. 「HNOI2019」多边形 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3, \ldots , n\).最开 ...

  2. LOJ 3056 「HNOI2019」多边形——模型转化+树形DP

    题目:https://loj.ac/problem/3056 只会写暴搜.用哈希记忆化之类的. #include<cstdio> #include<cstring> #incl ...

  3. Loj #3059. 「HNOI2019」序列

    Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...

  4. Loj #3055. 「HNOI2019」JOJO

    Loj #3055. 「HNOI2019」JOJO JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」. 为了防止字太多挡住漫画内容,现在打算在新的漫画中用 ...

  5. Loj 3058. 「HNOI2019」白兔之舞

    Loj 3058. 「HNOI2019」白兔之舞 题目描述 有一张顶点数为 \((L+1)\times n\) 的有向图.这张图的每个顶点由一个二元组 \((u,v)\) 表示 \((0\le u\l ...

  6. Loj #3057. 「HNOI2019」校园旅行

    Loj #3057. 「HNOI2019」校园旅行 某学校的每个建筑都有一个独特的编号.一天你在校园里无聊,决定在校园内随意地漫步. 你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你 ...

  7. LOJ 3059 「HNOI2019」序列——贪心与前后缀的思路+线段树上二分

    题目:https://loj.ac/problem/3059 一段 A 选一个 B 的话, B 是这段 A 的平均值.因为 \( \sum (A_i-B)^2 = \sum A_i^2 - 2*B \ ...

  8. LOJ 3057 「HNOI2019」校园旅行——BFS+图等价转化

    题目:https://loj.ac/problem/3057 想令 b[ i ][ j ] 表示两点是否可行,从可行的点对扩展.但不知道顺序,所以写了卡时间做数次 m2 迭代的算法,就是每次遍历所有不 ...

  9. LOJ 3055 「HNOI2019」JOJO—— kmp自动机+主席树

    题目:https://loj.ac/problem/3055 先写了暴力.本来想的是 n<=300 的那个在树上暴力维护好整个字符串, x=1 的那个用主席树维护好字符串和 nxt 数组.但 x ...

随机推荐

  1. java 精确加减

    /** * 提供精确的加法运算. * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */ public double add(double v1, dou ...

  2. Python实现批量处理文件的缩进和转码问题

    最近把很久前的C代码传到Github上的时候,发现全部上百个源文件的代码缩进都变成了8格,而且里面的中文都出现了乱码,所以决定写个程序来批量处理所有文件的格式.这段关于转码的代码可以适用于很多场合,比 ...

  3. HTML5移动端最新兼容问题解决方案

    1.安卓浏览器看背景图片,有些设备会模糊.用同等比例的图片在PC机上很清楚,但是手机上很模糊,原因是什么呢?经过研究,是devicePixelRatio作怪,因为手机分辨率太小,如果按照分辨率来显示网 ...

  4. Java——删除Map集合中key-value值

    通过迭代器删除Map集合中的key-value值 Iterator<String> iter = map.keySet().iterator(); while(iter.hasNext() ...

  5. Android中的多进程、多线程

    前面几篇总结了进程.线程相关的知识.这里总结下关于Android中的多进程.多线程及其使用. 这里总结的Android中的多进程.多线程也是一个基础,可扩展的很多. Android中多进程 常见的几种 ...

  6. R语言入门二

    一.R语言应知常用函数 1.getwd() 函数:获取工作目录(同eclipse设置workspace类似),直接在R软件中使用,如下图: 2.setwd(dir=”工作目录”) 函数:设置R软件RS ...

  7. python调用大漠插件教程03窗口绑定实例

    怎样利用注册好的大漠对象来绑定窗口? 直接上代码,根据代码分析 from win32com.client import Dispatch import os from win32gui import ...

  8. js 识别二维码

    本文引用analyticCode.js.llqrcode.js实现识别二维码功能 html代码: <div class="box" id="analytic&quo ...

  9. ERROR: ...hbase.PleaseHoldException: Master is initializing

    同学升级HBase后遇到这个问题,hbase shell,status就可以看到 ERROR: -hbase.PleaseHoldException: Master is initializing 解 ...

  10. 2003 can't connect to mysql server

    在电脑中打开 计算机管理 点击 服务与应用程序 点击 服务 右侧找到 mysql 右键启动