【loj - 3056】 「HNOI2019」多边形
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。
考虑方案数,每次可以任选最右链上某一点进行旋转。通过简单的组合推导可得方案数为:
\]
一次右旋只会影响 \(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」多边形的更多相关文章
- Loj #3056. 「HNOI2019」多边形
Loj #3056. 「HNOI2019」多边形 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3, \ldots , n\).最开 ...
- LOJ 3056 「HNOI2019」多边形——模型转化+树形DP
题目:https://loj.ac/problem/3056 只会写暴搜.用哈希记忆化之类的. #include<cstdio> #include<cstring> #incl ...
- Loj #3059. 「HNOI2019」序列
Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...
- Loj #3055. 「HNOI2019」JOJO
Loj #3055. 「HNOI2019」JOJO JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」. 为了防止字太多挡住漫画内容,现在打算在新的漫画中用 ...
- Loj 3058. 「HNOI2019」白兔之舞
Loj 3058. 「HNOI2019」白兔之舞 题目描述 有一张顶点数为 \((L+1)\times n\) 的有向图.这张图的每个顶点由一个二元组 \((u,v)\) 表示 \((0\le u\l ...
- Loj #3057. 「HNOI2019」校园旅行
Loj #3057. 「HNOI2019」校园旅行 某学校的每个建筑都有一个独特的编号.一天你在校园里无聊,决定在校园内随意地漫步. 你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你 ...
- LOJ 3059 「HNOI2019」序列——贪心与前后缀的思路+线段树上二分
题目:https://loj.ac/problem/3059 一段 A 选一个 B 的话, B 是这段 A 的平均值.因为 \( \sum (A_i-B)^2 = \sum A_i^2 - 2*B \ ...
- LOJ 3057 「HNOI2019」校园旅行——BFS+图等价转化
题目:https://loj.ac/problem/3057 想令 b[ i ][ j ] 表示两点是否可行,从可行的点对扩展.但不知道顺序,所以写了卡时间做数次 m2 迭代的算法,就是每次遍历所有不 ...
- LOJ 3055 「HNOI2019」JOJO—— kmp自动机+主席树
题目:https://loj.ac/problem/3055 先写了暴力.本来想的是 n<=300 的那个在树上暴力维护好整个字符串, x=1 的那个用主席树维护好字符串和 nxt 数组.但 x ...
随机推荐
- jQuery中效果animate方法解决width是百分比出现的问题
jQuery中效果animate方法解决width是百分比出现的问题 http://www.mafutian.net/131.html 问题描述: 效果如图,初始化,每个层宽20%,采用animate ...
- 基于Netty包中的Recycler实现的对象池技术详解
一.业务背景 当项目中涉及到频繁的对象的创建和回收的时候,就会出现频繁GC的情况,这时就出现了池化的技术来实现对象的循环使用从而避免对象的频繁回收,Netty包下的Recycler就实现了这一功能.当 ...
- CF #459 D. MADMAX
D. MADMAX time limit per test 1 second memory limit per test 256 megabytes input standard input outp ...
- 【MySQL】覆盖索引和回表
先来了解一下两大类索引 聚簇索引(也称聚集索引,主键索引等) 普通索引(也成非聚簇索引,二级索引等) 聚簇索引 如果表设置了主键,则主键就是聚簇索引 如果表没有主键,则会默认第一个NOT NULL,且 ...
- redis的安装和简单操作
安装gcc 目地是编译软件 yum install gcc-c++ 1.拷贝并解压 2.编译文件 到解压目录下 执行 make 进行编译依赖项 cd /deps make hiredis lua j ...
- springmvc拦截器和概念,配置!!!
用于拦截请求,过滤后再拦截 实现HandlerInterceptor接口 配置拦截器 package cn.zys.lanjieqi; import javax.servlet.http.HttpSe ...
- Robot Framework(13)- RF 循环的详细使用
如果你还想从头学起Robot Framework,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1770899.html 前言 RF用 ...
- 创建多线程的方式&Thread类的常用方法
创建多线程的第一种方式:继承java.lang.Thread类 注意:1.一个线程只能执行一次start() 2.不能通过Thread实现类对象的 run()去启动一个线程 3.增加加一个线程,需要新 ...
- 分布式项目开发-springmvc.xmll基础配置
基础步骤: 1 包扫描 2 驱动开发 3 视图解析器 4 文件上传解析器 5 拦截器 6 静态资源 <beans xmlns="http://www.springframework.o ...
- 设计带构造函数的Dog类 代码参考
#include <iostream> #include <string> using namespace std; class Dog { private: string n ...