P7529 [USACO21OPEN] Permutation G

P7529 [USACO21OPEN] Permutation G题解

[USACO21OPEN] Permutation G

题目描述

Bessie 在二维平面上有 N N N 个最爱的不同的点,其中任意三点均不共线。对于每一个 1 ≤ i ≤ N 1\le i\le N 1iN,第 i i i 个点可以用两个整数 x i x_i xi y i y_i yi 表示。

Bessie 按如下方式在这些点之间画一些线段:

    1. 她选择这 N N N 个点的某个排列 p 1 , p 2 , … , p N p_1,p_2,\dots,p_N p1,p2,,pN
    1. 她在点 p 1 p_1 p1 p 2 p_2 p2 p 2 p_2 p2 p 3 p_3 p3 p 3 p_3 p3 p 1 p_1 p1 之间画上线段。
    1. 然后依次对于从 4 4 4 N N N 的每个整数 i i i,对于所有 j < i j<i j<i,她从 p i p_i pi p j p_j pj 画上一条线段,只要这条线段不与任何已经画上的线段相交(端点位置相交除外)。

Bessie 注意到对于每一个 i i i ,她都画上了恰好三条新的线段。计算 Bessie 在第 1 1 1 步可以选择的满足上述性质的排列的数量,结果对 1 0 9 + 7 10^9+7 109+7 取模。

输入格式

输入的第一行包含 N N N

以下 N N N 行,每行包含两个空格分隔的整数 x i x_i xi y i y_i yi

输出格式

输出一行一个整数表示方案数对 1 0 9 + 7 10^9+7 109+7 取模后的结果。

样例 #1

样例输入 #1

4
0 0
0 4
1 1
1 2

样例输出 #1

0

样例 #2

样例输入 #2

4
0 0
0 4
4 0
1 1

样例输出 #2

24

样例 #3

样例输入 #3

5
0 0
0 4
4 0
1 1
1 2

样例输出 #3

96

提示

样例一解释

没有排列满足该性质

样例二解释

所有排列均满足该性质

样例解释三

一个满足该性质的排列为 ( 0 , 0 ) , ( 0 , 4 ) , ( 4 , 0 ) , ( 1 , 2 ) , ( 1 , 1 ) (0,0),(0,4),(4,0),(1,2),(1,1) (0,0),(0,4),(4,0),(1,2),(1,1) 。对于这个排列,

  • 首先,她在 ( 0 , 0 ) , ( 0 , 4 ) (0,0),(0,4) (0,0),(0,4) ( 4 , 0 ) (4,0) (4,0) 两两之间画上线段。
  • 然后她从 ( 1 , 2 ) (1,2) (1,2) ( 0 , 0 ) (0,0) (0,0) ( 0 , 4 ) (0,4) (0,4) ( 4 , 0 ) (4,0) (4,0) 画上线段。
  • 最后,她从 ( 1 , 1 ) (1,1) (1,1) ( 1 , 2 ) (1,2) (1,2) ( 4 , 0 ) (4,0) (4,0) ( 0 , 0 ) (0,0) (0,0) 画上线段。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据范围与约定

3 ≤ N ≤ 40 3\le N \le 40 3N40 0 ≤ x i , y i ≤ 1 0 4 0\le x_i,y_i \le 10^4 0xi,yi104

题解

解释代码里有

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define resetIO(x) { freopen(x".in", "r", stdin); freopen(x".out", "w", stdout); }
template<class T> inline void read(T &x){
    T f = 1; x = 0; char ch = getchar();
    for( ; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -f;
    for( ; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    x *= f;
}
template<class T> inline void write(T x){
    if(x < 0) { putchar('-'); x = -x; }
    if(x > 9) write(x / 10); putchar((x % 10) ^ 48);
}
const int maxn = 41;
const int inf = 1LL << 60;
const int mod = 1e9+7;
// 向量模版
struct Vec{
    int x, y;
    Vec(){ }
    Vec(int _x, int _y) : x(_x), y(_y) { }
    Vec(pair<int, int> p) : x(p.first), y(p.second) { }
    // 向量加法
    Vec operator+(const Vec &o) const {
        return Vec(x + o.x, y + o.y);
    }
    // 向量减法
    Vec operator-(const Vec &o) const {
        return Vec(x - o.x, y - o.y);
    }
    // 向量数乘
    Vec operator*(const int k) const {
        return Vec(x * k, y * k);
    }
    // 向量点乘
    int operator*(const Vec &o) const {
        return x * o.x + y * o.y;
    }
    // 向量叉乘
    int operator^(const Vec &o) const {
        return x  * o.y - y * o.x;
    }
};
struct Line{
    Vec a, b;
    Line(){ }
    Line(Vec _a, Vec _b) : a(_a), b(_b) { }
    // 获取线段对应的向量
    Vec vector() const {
        return b - a;
    }
    // 判断两个点是否在线段的同侧
    bool side(Vec v1, Vec v2) const {
        Vec x = vector(), y = v1 - a, z = v2 - a;
        return (x ^ y) * (x ^ z) > 0;
    }
};
int n, dp[maxn][maxn][maxn][maxn];
pair<int, int> a[maxn];
// 判断点在三角形内
bool inner_triangle(int p1, int p2, int p3, int i){
    Vec v1(a[p1]), v2(a[p2]), v3(a[p3]), vi(a[i]);
    Line l1(v2, v3), l2(v1, v3), l3(v1, v2);
    // 判断点vi和v1在l1的同侧,和v2在l2的同侧以及和v3在l3的同侧
    return l1.side(v1, vi) && l2.side(v2, vi) && l3.side(v3, vi);
}
// 判断点在红色区域中,如果是则更新成新的三角形三个端点
bool outer_triangle(int &p1, int &p2, int &p3, int i){
    Vec v1(a[p1]), v2(a[p2]), v3(a[p3]), vi(a[i]);
    Line l1(v2, v3), l2(v1, v3), l3(v1, v2);
    // 判断点vi和v1在l1的异侧并且vi和v2在l2的异侧,如果符合则更新v3,下同
    if((!l1.side(v1, vi)) && (!l2.side(v2, vi))) { p3 = i; return true; }
    if((!l1.side(v1, vi)) && (!l3.side(v3, vi))) { p2 = i; return true; }
    if((!l2.side(v2, vi)) && (!l3.side(v3, vi))) { p1 = i; return true; }
    return false;
}
// 记忆化搜索(dp),dp[p1][p2][p3][k]表示
// 以p1,p2,p3为三角形端点且已经放了k个点的方案数
int dfs(int p1, int p2, int p3, int k){
    // k=n时返回1
    if(k == n) return 1;
    // 排序三角形端点
    if(p1 > p2) swap(p1, p2);
    if(p1 > p3) swap(p1, p3);
    if(p2 > p3) swap(p2, p3);
    if(dp[p1][p2][p3][k] != -1) return dp[p1][p2][p3][k];
    // 用&引用来写记忆化会比较舒服
    int &ret = dp[p1][p2][p3][k]; ret = 0;
    // 计算三角形内点的个数,最后一起计算(算是个优化)
    int cnt_in = 0;
    for(int i=1; i<=n; i++){
        // 新的点不能和三角形端点重复
        if(p1 == i || p2 == i || p3 == i) continue;
        // 分情况讨论
        if(inner_triangle(p1, p2, p3, i)){
            // 计算三角形内点的个数
            cnt_in ++;
        }
        else{
            // 为了防止原来的p1,p2,p3被更改,这里新开了3个变量
            int tp1 = p1, tp2 = p2, tp3 = p3;
            // 判断是否在红色区域
            if(outer_triangle(tp1, tp2, tp3, i))
                (ret += dfs(tp1, tp2, tp3, k + 1)) %= mod;
        }
    }
    // 判断已经放置的个数k是否超过三角形内的点个数+3个端点,如果超过,就不能再在三角形内放置了
    if(cnt_in + 3 > k)
        (ret += (dfs(p1, p2, p3, k + 1) * (cnt_in + 3 - k))) %= mod;
    return ret;
}
signed main(){
    // 读入+初始化
    read(n);
    for(int i=1; i<=n; i++) read(a[i].first), read(a[i].second);
    memset(dp, -1, sizeof(dp));
    int ans = 0;
    // 枚举前三个点的顺序
    for(int p1=1; p1<=n; p1++){
        for(int p2=p1+1; p2<=n; p2++){
            for(int p3=p2+1; p3<=n; p3++){
                (ans += dfs(p1, p2, p3, 3)) %= mod;
            }
        }
    }
    // 因为前三个点可以任意交换位置,所以答案要乘上6
    write((ans * 6) % mod); putchar('\n');
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值