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. poj3177 无向连通图加多少条边变成边双连通图

    Redundant Paths Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 15752   Accepted: 6609 ...

  2. Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)

    好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名<Angular单页面应用基于Ocelot API网关与IdentityServer4+ASP.NET Iden ...

  3. ATX插件机制-学习学习

    添加插件:记录一下 https://testerhome.com/topics/16074 webview操作: https://testerhome.com/topics/12599

  4. 解决 appium 当中 uiautomator2 无法启动的问题

    在启动 appium 程序中,有时候会出现 uiautomator2 服务无法启动的错误,appium 的日志截图如下: image.png 错误信息: ActivityManager: Unable ...

  5. 《Head First 设计模式》:策略模式

    正文 一.定义 策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户. 要点: 策略模式把系统中会变化的部分抽出来封装. 二.实现步骤 1.创建策略接口 ...

  6. Redis学习笔记(2)

    一.配置文件(部分) 1. UNITS(单位) 数据单位 2. INCLUDES(包含) 可以包含其他配置文件,而redis.conf作为总的配置文件 3. NETWORK(网络配置) -网络端口的绑 ...

  7. 阿里云服务器centOS安装Docker

    环境准备 1.需要有Linux的基础 2.centOS 7 环境查看 # 系统内核是 3.10 以上的 [root@iz2zeaet7s13lfkc8r3e2kz ~]# uname -r -.el7 ...

  8. volatile关键字与内存可见性&原子变量与CAS算法

    1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...

  9. 值得注意的Java基础知识

    1)Java语言中默认(即缺省没写出)的访问权限,不同包中的子类不能访问. 中有4中访问修饰符:friendly(默认).private.public和protected. public :能被所有的 ...

  10. C#线程 入门

    Threading in C#   第一部分: 入门 介绍和概念 C#支持通过多线程并行执行代码.线程是一个独立的执行路径,能够与其他线程同时运行.C#客户端程序(控制台,WPF或Windows窗体) ...