1 条题解

  • 0
    @ 2025-10-18 18:29:22

    这里是人肉爬虫114514号awa,这是我爬出的第一个题目,以下是由生物计算机(已迭代20次)生成的内容:

    知识点:

    这是一个计算几何 + 动态规划的题目(怎么一上来就给我来个这么难的题目)

    动态规划(DP)学习网址(我都没学完):http://www.oj7.cn/discuss/67a85d917cd0119d5eaaefeb

    凸多边形的定义:一个边不相交并且任意两个顶点之间的线段都完全包含在多边形内部或边界上的多边形。eg:三角形,正方形。

    极角:在平面直角坐标系中,从正x轴逆时针旋转到某个向量所需的角度。 极角排序:以某个点O为原点,将其他点按照它们相对于O的极角从小到大排序。

    问题分析 这是一个凸多边形的三角划分计数问题,但增加了额外的约束条件:每个三角形内的羊数必须为偶数。

    题解(点击复制代码,然后复制到编译器里去看)

    
    
    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <functional>
    using namespace std;
    /*
    题解:
    这是一个凸多边形三角剖分计数问题,要求每个三角形内包含偶数只羊。
    
    算法思路:
    1. 预处理阶段:对于每条边(i,j),判断它是否可以作为剖分边
       - 以点i为原点,将所有羊按照相对于i的极角排序
       - 对于每个点j,统计在向量(i,j)左侧的羊的数量
       - 如果左侧羊的数量为偶数且没有羊恰好在边界上,则边(i,j)是合法的
    
    2. 动态规划阶段:使用区间DP计算剖分方案数
       - dp[i][j]表示从顶点i到j的凸多边形子区域的剖分方案数
       - 初始条件:相邻两点dp[i][i+1]=1(表示一条边)
       - 状态转移:枚举中间点k,如果边(i,k)和(k,j)都合法,则
            dp[i][j] += dp[i][k] * dp[k][j]
    
    关键点:
    - 利用极角排序和叉积判断点的相对位置
    - 通过统计半平面内点的奇偶性来判断边的合法性
    - 动态规划时保证三角形内羊的数量为偶数
    
    时间复杂度:O(n2k + n3),其中排序O(nklogk),DP为O(n的3次方)
    空间复杂度:O(n2)
    */
    int n, k, mod;//牧场的顶点数,羊的个数,以及模数(mod好看) 
    int x[20005], y[20005], p[20005], q[20005];
    int f[605][605], dp[605][605];
    
    struct Point {
        int x, y;
        Point() {}
        Point(int _x, int _y) : x(_x), y(_y) {}
    } a[605], b[20005], O;
    
    Point operator-(Point a, Point b) {
        return Point(a.x - b.x, a.y - b.y);
    }//这个函数让我们可以用 point1 - point2 的语法来计算两个点之间的向量。
    
    int cross(Point a, Point b) {
        return a.x * b.y - a.y * b.x;//数学知识:叉积
    }
    
    int main() {
        cin >> n >> k >> mod;
    
        for (int i = 1; i <= n; i++) {
            cin >> x[i] >> y[i];
            a[i] = Point(x[i], y[i]);//a是牧场的顶点
        }
    
        for (int i = 1; i <= k; i++) {
            cin >> p[i] >> q[i];
            b[i] = Point(p[i], q[i]);//b是羊的坐标
        }
    
        // 预处理f数组:f[i][j]表示边(i,j)是否合法(合法要求:当三角形(i,j,k)内羊数为偶数时,此三角形合法。)
        // i,j,k表示某点,k是由i,j派生出的点,它可以是多边形中除了i和j之外的任何一个顶点。
        //如果边(i,j)左侧有奇数只羊,那么无论k选在哪里,都会形成至少一个三角形包含奇数只羊
        //如果边(i, j)左侧有偶数只羊,那么可以找到合适的k使得所有三角形都包含偶数只羊
        for (int i = 1; i <= n; i++) {
            O = a[i];// 以点i(O)为原点,按极角排序(见知识点)所有羊
    
            sort(b + 1, b + k + 1, [](Point x, Point y) 
                {
                return cross(x - O, y - O) < 0;
                });
    
            // 对于每个点j,统计在边(i,j)左侧的羊的数量
            for (int j = i + 1, p = 0; j <= n; j++) {
                // 使用双指针统计:当羊在边(i,j)左侧时计数
                while (p < k && cross(a[j] - a[i], b[p + 1] - a[i]) > 0)
                    ++p;
    
                //a[j] - a[i]:从i指向j的向量
                //b[p + 1] - a[i]:从i指向第(p + 1)只羊的向量
                //cross(a[j] - a[i], b[p + 1] - a[i]) > 0:羊在边(i, j)的左侧
    
                // 边(i,j)合法的条件:
                // 1. 左侧羊的数量为偶数 (p&1为1时,p为奇数)
                // 2. 没有羊恰好在边(i,j)上(cross != 0)
                f[i][j] = !((p & 1) || (p < k && cross(a[j] - a[i], b[p + 1] - a[i]) == 0));
                //等价于 ((p % 2) == 0) && ( p == k || cross(a[j] - a[i], b[p + 1] - a[i]) != 0);
                //p==k时,说明所有羊恰好在边(i,j)左边,此时如果p%2==0,f[i][j]为1
            }
    
        }//通过统计半平面内的羊数奇偶性,来预判所有可能三角形的合法性,从而在DP阶段快速判断边是否可用。
    
        // 动态规划:计算三角剖分方案数
        for (int i = 1; i < n; i++)
            dp[i][i + 1] = 1;  // 相邻两点形成一条边。相邻两个顶点形成的"多边形"实际上是一条边,只有1种方案(就是这条边本身),想想正方形
    
        //dp[i][j]:表示从顶点i到顶点j的凸多边形子区域的合法三角剖分方案数。
        
        // 区间DP:按长度从小到大处理
        for (int l = 3; l <= n; l++) {
            //               ↓j
            for (int i = 1; i + l - 1 <= n; i++) {
                int j = i + l - 1;//离点i距离为l的点
    
                //多边形[i, j]:从i顺时针到j所形成的多边形
                
                //核心思想:
                    //将多边形[i, j]通过中间顶点k分割为两个子多边形[i, k]和[k, j]:
                    //子多边形[i, k]的方案数:dp[i][k]
                    //子多边形[k, j]的方案数:dp[k][j]
                    //总方案数 = 两个子方案数的乘积
                
                // 枚举中间点k,将多边形[i,j]分割为[i,k]和[k,j]
    
                for (int k = i + 1; k < j; k++) {
                    // 如果两条边都合法,则累加方案数
                    if (f[i][k] && f[k][j]) {
                        dp[i][j] = (dp[i][j] + 1ll * dp[i][k] * dp[k][j]) % mod;
                        //                     ↑long long
                    }
                }
                //想想五边形12345  
                //在计算 dp[1][5](整个五边形)时:
                    //枚举k = 2, 3, 4:
                    //k = 2时:dp[1][5] += dp[1][2] * dp[2][5]//[2,5]在i=2,l=3时算过
                    // 含义:边(1,2) + 多边形[2,5]的剖分
                    // 实际上就是整个多边形被对角线(2,5)分割
    
                    //k = 3时:dp[1][5] += dp[1][3] * dp[3][5]
                    // 含义:多边形[1,3]的剖分 + 多边形[3,5]的剖分  
                    // 实际上就是被对角线(1,3)和(3,5)分割
    
                    //k = 4时:dp[1][5] += dp[1][4] * dp[4][5]
                    // 含义:多边形[1,4]的剖分 + 边(4,5)
                    // 实际上就是被对角线(1,4)分割
            }
        }
    
        //算法工程师就是一个会写代码的数学家,也就是说,他要一个职业干两个职业的活。
        //现在AI来了,你说会不会出现一个职业干一个团队的活?
        //管他的,这道题理解的我肚子饿,吃饭去了。
        
        cout << dp[1][n] << endl;
    
        return 0;
    }
    
    • 1

    信息

    ID
    3287
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    5
    已通过
    1
    上传者