洛谷P3800 Power收集

题目

https://www.luogu.com.cn/problem/P3800

思路

显然这是一道DP题,朴素算法很容易想:

\[dp[i][j]=v[i][j]+max(dp[i-1][k])(j-t\leq k\leq j+t) \]

然而这样的转移是\(O(nm^2)\)的,不合要求,还要继续优化。

注意到\(k\leq 4000\),棋盘的数据是相当稀疏的,那么我们考虑将有P点的格子当成一个结点,建图。根据题意(必须一直向下走)该图是一个DAG。

边的构造:将结点按x坐标从小到大排序,可以算出从i结点的行到j结点的行经过的时间\(x_j-x_i\),那么如果这段时间内能从i结点到j结点,那么建一条从i到j的有向边。

跑一遍记忆化dfs就行了。

易错点:不要以为答案就是dfs(1),因为最优解完全可以从空格子开始,起点是不固定的。

代码

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
struct node{
    int x,y,val;
    bool operator <(node t){
        return x<t.x;
    }
} a[4001];
int book[4001][4001],dp[4001],k,vis[4001];
int dfs(int x){
    int i;
    vis[x]=1;
    if(dp[x]) return dp[x];
    dp[x]=a[x].val;
    for(i=x+1;i<=k;i++){
        if(book[x][i]) dp[x]=max(dp[x],a[x].val+dfs(i));
    }
    return dp[x];
}
int main(){
    int i,j,l,n,m,t,x,y,z;
    int ans=0;
    scanf("%d%d%d%d",&n,&m,&k,&t);
    for(i=1;i<=k;i++)
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].val);
    sort(a+1,a+k+1);
    for(i=1;i<=k;++i){
        for(j=i+1;j<=k;++j){
            int l=a[i].y-(a[j].x-a[i].x)*t;
            int r=a[i].y+(a[j].x-a[i].x)*t;
            if(a[j].y>=l&&a[j].y<=r) book[i][j]=1;
        }
    }
    for(i=1;i<=k;++i){
        if(!vis[i]) dfs(i);
    }
    for(i=1;i<=k;i++)
        ans=max(ans,dp[i]);
    printf("%d",ans);
    return 0;
}
posted @ 2020-12-08 12:55  文艺平衡树  阅读(59)  评论(0编辑  收藏  举报