习题:守卫(DP)

题目

传送门

思路

这道题的题面十分具有迷惑性。。。
某位大犇上来直接用凸包
但是仔细分析之后发现,有点像区间DP
对于\(l_0r_0\)\(l_1 r_1\)两个区间,如果这两个区间是不相交
那么这两个区间实际上是不会影响对方的DP值的
有了这个结论之后,我们设\(dp_{i j}\)为区间为i和j的最小值
转移方程即为\(dp_{ij}=1+\sum _{}dp_{l_{k}r_k}\)

50pts

一个时间复杂度为\(O(n^3)\)算法就出现了
前两层循环枚举左端点与右端点,
最内层的循环就是枚举之间看不到的区间
对于每一个看不到的区间可能在它的右端点放一个守卫可能更

100pts

我们考虑对50pts的算法的改进,发现最内层的k是根本不需要的
因为可以先固定右端点,
在枚举左端点的时候顺便就可以将看不见的统计出来

代码

#include<iostream>
#include<climits>
using namespace std;
#define int long long
int n;
int h[5005];
int ans=1;
int dp[5005][5005];//表示区间i,j的最小值
bool f[5005][5005];//表示i号节点和j号节点之间能否互相看到
double solve_slope(int a,int b)
{
    return 1.0*(h[a]-h[b])/(a-b);
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>h[i];
    for(int i=2;i<=n;i++)
    {
        double now=-INT_MIN;
        for(int j=i-1;j>=1;j--)
        {
            double t=solve_slope(j,i);
            if(t>now)
            {
                f[j][i]=f[i][j]=1;
                now=t;
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            cout<<f[i][j]<<" ";
        cout<<endl;
    }
    for(int i=1;i<=n;i++)
    {
        int lastt=0;
        int s=1;
        for(int j=i;j>=1;j--)
        {
            if(f[j][i])
            {
                if(!f[j+1][i])
                {
                    s+=min(dp[j+1][lastt],dp[j+1][lastt+1]);
                }
                dp[j][i]=s;
            }
            else
            {
                if(f[j+1][i])
                {
                    lastt=j;
                }
                dp[j][i]=s+min(dp[j][lastt+1],dp[j][lastt]);
            }
            ans^=dp[j][i];
        }
    }
    cout<<ans;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/loney-s/p/11735005.html