ABC334 F&G

F:
我们可以更好的利用一操作——当且仅当钱不够用时,加上经过的所有点中最大的 P,也就是在那个点插入一次一操作。
dpi,j,x,y=(step,money) 表示到达点 (i,j),经过的最大的 P 在点 (x,y),最少需要 val 次一操作(移动操作最后算上就行了),此前提下手头的钱最多为 money(方便比较)。
转移时,先枚举 (i,j),再枚举 (x,y),向 (i,j+1),(i+1,j) 刷表即可。
正确性显然。因为我们是在钱不够用时才操作,所以对于任意 dpi,j,x,y,即使其他情况下手头的钱更多,也一定小于 Px,y。我们只要一次操作,钱就可以超过它们,且操作数不劣。
具体 dp 过程见代码。

#include<algorithm>
#include<cstdio>
#include<cstring>
#define pii pair<int,int>
#define mk make_pair
#define ft first
#define se second
#define pb push_back
#define db double
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define inf 1e18
using namespace std;
namespace IO{
    #ifndef LOCAL
        #define SIZE 30000
        char in[SIZE],out[SIZE],*p1=in,*p2=in,*p3=out;
        #define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin),p1==p2)?EOF:*p1++)
        #define flush() (fwrite(p3=out,1,SIZE,stdout))
        #define putchar(ch) (p3==out+SIZE&&flush(),*p3++=(ch))
        class Flush{public:~Flush(){fwrite(out,1,p3-out,stdout);}}_;
    #endif
    template<typename type>
    inline void read(type &x){
        x=0;bool flag=0;char ch=getchar();
        while(ch<'0'||ch>'9') flag^=ch=='-',ch=getchar();
        while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        flag?x=-x:0;
    }
    template<typename type>
    inline void write(type x,char ch=0){
        x<0?x=-x,putchar('-'):0;static short Stack[50],top=0;
        do Stack[++top]=x%10,x/=10;while(x);
        while(top) putchar(Stack[top--]|48);
        if(ch) putchar(ch);
    }
}
using namespace IO;
#define M 85
int n,p[M][M],r[M][M],d[M][M];
pair<ll,ll> dp[M][M][M][M]; 
int main(){
    read(n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            read(p[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<n;j++){
            read(r[i][j]);
        }
    }
    for(int i=1;i<n;i++){
        for(int j=1;j<=n;j++){
            read(d[i][j]);
        }
    }
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
        for(int x=1;x<=n;x++) for(int y=1;y<=n;y++){
            dp[i][j][x][y]=mk(inf,inf);
        }
    }
    dp[1][1][1][1]=mk(0,0);
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
        for(int x=1;x<=i;x++) for(int y=1;y<=j;y++){
            if(dp[i][j][x][y].ft>1e14) continue;
            if(i+1<=n){
                ll need=dp[i][j][x][y].se+d[i][j],opr=0;
                if(need>0){
                    opr=(need+p[x][y]-1)/p[x][y];
                    need-=opr*p[x][y];
                }
                int nx=x,ny=y;
                if(p[i+1][j]>p[nx][ny]) nx=i+1,ny=j;
                dp[i+1][j][nx][ny]=min(dp[i+1][j][nx][ny],mk(dp[i][j][x][y].ft+opr,need));
            }
            if(j+1<=n){
                ll need=dp[i][j][x][y].se+r[i][j],opr=0;
                if(need>0){
                    opr=(need+p[x][y]-1)/p[x][y];
                    need-=opr*p[x][y];
                }
                int nx=x,ny=y;
                if(p[i][j+1]>p[nx][ny]) nx=i,ny=j+1;
                dp[i][j+1][nx][ny]=min(dp[i][j+1][nx][ny],mk(dp[i][j][x][y].ft+opr,need));
            }
        }
    }
    ll ans=inf;
    for(int x=1;x<=n;x++){
        for(int y=1;y<=n;y++){
            ans=min(ans,dp[n][n][x][y].ft);
        }
    }
    write(ans+2*(n-1),'\n');
    return 0;
}

G:
官方题解给出了一种巧妙的做法。下面再提一提。
AX+B<=YAX+Y>=B,想到若能使所有点按 AX+Y 的值从小到大排序,则可以用二分解决询问。
不妨将询问按 A 从小到大排序。
初始情况可以视作 A=,此时将 (X,Y) 按字典序从小到大排序即可。
(X,Y) 考虑成 f(A)=(X)A+Y,不难发现随着 A 的增大,任意相邻两点间最多交换一次顺序,交换点即两直线交点的横坐标.
具体地,对于 (X1,Y1),(X2,Y2),交换点即为 Y2Y1X2X1
接下来,只要考虑每次回答询问前完成为数不多(O(n2))的交换即可。
下面是具体实现:
开始时,把所有交换点放进优先队列,堆顶为最小的交换点。
每次询问前,若堆顶值小于当前的 A,则进行交换,并将左右两边新的可能加进去,将堆顶弹出。
特别注意,堆顶值对应的两条直线此时可能已经不相邻,若如此应忽略这个值并将其弹出。
一个简单二分回答询问即可。

posted @   studentDL  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示