【题解】SCOI2009围豆豆

本文介绍了一种判断点是否位于多边形内的射线法,并结合SPFA算法解决了一个涉及状态转移的问题。通过引入射线法的概念,讨论了如何有效判断豆豆的位置状态,同时利用SPFA算法进行状态转移。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  很久之前就很想做的一道题,一直思考到今天才下定决心看题解。这道题中,很关键的一点就在于:如何判断一个点是否在一个多边形内?其实如果计算几何基本功扎实的话,应该是可以很快给出答案的(可惜我完全不行):由一个点向一边引一条射线,判断与多边形相交的边数。若边数是奇数,说明在多边形的内部。在这里贴一篇博文:大佬的博客

  以下想图解一下射线法(与代码判图方式一致)。

  1.图中五角星为一个豆豆。

  2.在转移的时候,我们由\(f[i][j][k] -> f[i][j + 1][k']\) 的转移即为在这两个格子之间链接一条边界线。

 

  3.再转移一下,我们会注意到此时这个豆豆向下引的射线与两条边界线均有交点,但它暂时是被包围的状态,与我们所设的状态矛盾。所以我们规定:只有与箭头头部相交才算做一次。

  4.如果再次出现,次数变成偶数,就不在边界内了:

  通过这几幅图,我们会发现竖直方向的移动不会改变豆豆是否被包围的判定,可以不必重复判断。了解了这一点之后,我们每一次的转移都是在画轮廓线。我们规定状态 \(f[i][j][k]\) 表示当前边界线画到第 \(\left ( i,j \right )\)时,被包围的豆豆状况为 \(k\) 时的最大得分。因为 \(\left ( i,j \right )\) 可以转移到 \(\left ( i,j + 1 \right )\),反之亦然,所以这个转移是不满足拓扑序的。但由于它满足三角形不等式,所以我们使用spfa来转移状态。由于题目规定移动会减少分数,所以不用担心存在正环的问题。

#include <bits/stdc++.h>
using namespace std;
#define maxn 20
#define maxk 6000
#define INF 99999999
int n, m, S, Map[maxn][maxn];
int ans, CNST, val[maxn];
int f[maxn][maxn][maxk];
const int dx[4]={0, 0, -1, 1};
const int dy[4]={-1, 1, 0, 0};
bool vis[maxn][maxn][maxk];

struct node 
{
    int x, y, num;
    node(int xx = 0, int yy = 0, int zz = 0) { x = xx, y = yy, num = zz; }
}; queue <node> q;
node p[maxn];

int read()
{
    int x = 0, k = 1;
    char c;
    c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

void spfa(int sx, int sy)
{
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            for(int k = 0; k < CNST; k ++)
                f[i][j][k] = -INF;
    q.push(node(sx, sy, 0)), f[sx][sy][0] = 0, vis[sx][sy][0] = 1;
    while(!q.empty())
    {
        node now = q.front(); q.pop();
        vis[now.x][now.y][now.num] = 0;
        if(now.x == sx && now.y == sy)
            ans = max(ans, f[now.x][now.y][now.num]);
        for(int k = 0; k < 4; k ++)
        {
            int xx = now.x + dx[k], yy = now.y + dy[k];
            if(xx <= 0 || xx > n || yy <= 0 || yy > m || Map[now.x][now.y]) continue;
            int num = now.num, Y = max(yy, now.y), del = 0;
            if(k <= 1)
            {
                for(int i = 1; i <= S; i ++)
                    if(p[i].y == Y && p[i].x < xx) 
                    {
                        num ^= 1 << (i - 1);
                        if((num >> i - 1) & 1) del += val[i];
                        else del -= val[i];
                    }
            }
            if(f[xx][yy][num] < f[now.x][now.y][now.num] + del - 1)
            {
                f[xx][yy][num] = f[now.x][now.y][now.num] + del - 1;
                if(!vis[xx][yy][num]) vis[xx][yy][num] = 1, q.push(node(xx, yy, num));
            }
        }
    }
}

int main()
{
    n = read(), m = read(), S = read();
    CNST = (1 << S) - 1;
    for(int i = 1; i <= S; i ++) val[i] = read();
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
        {
            char c; cin >> c;
            if(c == '#') Map[i][j] = -1;
            else Map[i][j] = c - '0';
            if(Map[i][j] >= 1) p[Map[i][j]] = node(i, j);
        }
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            if(!Map[i][j]) spfa(i, j);
    printf("%d\n", ans);
    return 0; 
} 

 

 

转载于:https://www.cnblogs.com/twilight-sx/p/9122212.html

### 关于 SCOI2009 WINDY 数的解法 #### 定义与问题描述 WINDY数是指对于任意两个相邻位置上的数字,它们之间的差至少为\(2\)。给定正整数区间\([L, R]\),计算该范内有多少个WINDY数。 #### 动态规划方法解析 为了高效解决这个问题,可以采用动态规划的方法来处理。定义状态`dp[i][j]`表示长度为`i`且最高位是`j`的WINDY数的数量[^3]。 - **初始化** 对于单个数字的情况(即只有一位),显然每一位都可以单独构成一个合法的WINDY数,因此有: ```cpp dp[1][d] = 1; // d ∈ {0, 1,...,9} ``` - **状态转移方程** 当考虑多位数时,如果当前位选择了某个特定数值,则下一位的选择会受到限制——它必须满足与前一位相差不小于2的要求。具体来说就是当上一高位为`pre`时,当前位置可选范取决于`pre`的具体取值: - 如果`pre >= 2`, 则可以选择`{0... pre-2}` - 否则只能从剩余的有效集合中选取 这样就可以通过遍历所有可能的状态来进行状态间的转换并累加结果。 - **边界条件处理** 特殊情况下需要注意的是,在实际应用过程中还需要考虑到给出区间的上下限约束。可以通过逐位比较的方式判断是否越界,并据此调整有效状态空间大小。 ```cpp // 计算不超过num的最大windy数数量 int calc(int num){ int f[15], g[15]; memset(f, 0, sizeof(f)); string s = to_string(num); n = s.size(); for (char c : s) { a[++len] = c - '0'; } // 初始化f数组 for (int i=0;i<=9;++i)f[1][i]=1; // DP过程省略... return sum; } long long solve(long long L,long long R){ return calc(R)-calc(L-1); } ``` 此代码片段展示了如何利用预处理好的`dp`表快速查询指定范内的WINDY数总量。其中`solve()`函数用于返回闭区间\[L,R\]内符合条件的总数;而辅助函数`calc()`负责根据传入参数构建相应的状态序列并最终得出答案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值