P9730 [CEOI2023] Grading Server

这是什么神仙题啊。

本题主要思路:优化转移决策,减少 dp 状态。

我们发现减一层盾其实就是给自己加攻击,所以我们将初始生命值(攻击力) \(C_H\)\(C_G\) 重新表示为 \(A_1 = C_H - f_G S\)\(A_2 = C_G - f_H S\),让 \(F_1 = f_G\)\(F_2 = f_H\)

现在的点对就是 \((A_1,F_1,A_2,F_2)\),容易发现每一维都是存在单调性的,若 \((A_1,F_1,A_2,F_2)\) 先手胜,\((A_1 + 1,F_1,A_2,F_2)\) 一定也是先手胜。

然后我们考虑减少 dp 状态。

为方便叙述,我们称破一层盾为蓄力,设 \(M = \max(A_1,F_1,A_2,F_2)\)


首先我们能够粗略发现有些情况下决策是固定的。

  • \(A_1 \ge S \wedge A_2 \le S\) 时,先手必胜,因为先手只要攻击就一定能使后手攻击力小于 \(0\)

  • \(A_1 \ge 0 \wedge A_2 \le 0 \wedge F_1 > 0\) 时,蓄力就能得到第一种情况。

  • \(A_1 \le 0\) 时,一定选择蓄力。

  • \(A_2 \ge S\) 时,先手一定选择攻击,因为若蓄力,后手攻击就一定会让你的蓄力次数和攻击力都减少,显然不优。

而这些就让 \(A_1,A_2 \in [0,S]\)\(F_1,F_2 \in [0,M/S]\) 了,大大减少了 dp 状态。


然后我们还能发现,当 \(A_1,A_2 \in [0,S]\),若先手总是蓄力,那后手一定得攻击先手,所以又有了这么一个限制 (\(d_1 = S - A_2\)\(d_2 = S - A_1\)):

  • \(A_1 + d_1 F_1 \ge S\) 时,先手必胜。

对应的,若我们要使 \((A_1,F_1,A_2,F_2)\) 这个点对是有用的,我们还要让后手 \(A_2\) 满足:

  • \(A_2 - A_1 + d_2 F_2 \le S\)

所以若 \(A_2 - (A_1 + F_1 d_1) + (d_2 - F_1 d_1) F_2 \ge S\),后手必胜,不然先手就要将后手操作到 \(A_2 - A_1 + d_2 F_2 \le S\) 为止。

这时候 dp 状态又多了限制: \(d_1 F_1 \le S - A_1 = d_2\)\(d_2 F_2 \le S + A_1 - A_2\),大致状态数就是 \(S^2\) 再多个对数。


那如果不蓄力,而是攻击呢。具体来说,就是先手先蓄力 \(x\)\(\to\) 先手攻击后手,后手蓄力 \(\to\) 后手攻击先手,先手蓄力。

那用式子写出来?(\(A_{1/2}^{'}\) 表示先手/后手最后的攻击力,\(d_{1}^{'}\) 同理)

\(max(A_{2}^{'})\)

$= A_2 - (A_1 + x d_1) + (d_2 - x d_1) F_2 $

\(= (A_2 - A_1 + d_2 F_2) - x d_1 (F_2 + 1) \le S - x d_1 (F_2 + 1)\)

所以 \(d_{1}^{'} \ge x d_1 (F_2 + 1)\)

那再蓄力就能使攻击力至少加 \((F_2 +1) d_1 x(F_1 - x)\),若令 \(x = \frac{F_1}{2}\),那就至少加 \(\frac{1}{4}(F_2 +1) d_1 F_1^2\)

由于后手攻击了先手一次,但 \(A_2^{'} \le S\),所以只要 \(\frac{1}{4}(F_2 +1) d_1 F_1^2 \ge 2S\),先手必胜。

所以又有了 \((F_2 +1) d_1 F_1^2 \le 8S\) 的限制,这下状态数就只剩 \(S\) 多个对数了,时间复杂度就可以接受了。

点击查看代码
#include<bits/stdc++.h>
#define fir first
#define sec second
#define int long long
#define mkp make_pair
#define lowbit(x) x&(-x)
using namespace std;
typedef pair<int,int> pir;
inline int read(){
    int x=0,f=1; char c=getchar();
    while(!isdigit(c)){if(c=='-') f=-1; c=getchar();}
    while(isdigit(c)){x=x*10+(c^48); c=getchar();}
    return x*f;
}
map<tuple<int,int,int>,int> mp;
int S;
inline bool dfs(int A1,int F1,int A2,int F2){
    
    int fl=0;
    while(1){
        if(F1*S+A1<=0) return fl;
        if(F2*S+A2<=0) return !fl;
        if(A1>=S&&A2<=S) return !fl;
        if(F1&&(A1>=0&&A2<=0)) return !fl;
        if(A2>=S||!F1){
            A2-=A1; fl=!fl;
            swap(A1,A2),swap(F1,F2);
            continue;
        }
        if(A1<=0){
            if(!F2&&A2<S){
                int nd=(-A1)/(S-A2)+1;
                if(nd>F1) return fl;
                A1+=(S-A2)*nd,F1-=nd;
                continue;
            }
            int nd1=(-A1+S-1)/S;
            int nd2=0; if(A2<=0) nd2=(-A2+S-1)/S;
            int stp=min(nd1,nd2);
            stp=max(stp-1,0ll);
            A1+=(stp+1)*S,F1-=(stp+1);
            A2+=stp*S,F2-=stp;
            fl=!fl;swap(A1,A2),swap(F1,F2);
            continue;
        }
        
        int d1=S-A2,d2=S-A1;
        if(A1+d1*F1>=S) return !fl;
        if(d2*F2>=S-A2+A1){
            int t=(d2*F2+A2-A1-S)/(F2+1)/d1+1;
            if(t>F1) return fl;
            A1+=t*d1,F1-=t;
            continue;
        }
        if(F1!=1&&(max({F2+1,d1,F1})>=8*S||(F2+1)*d1*F1*F1>=8*S)) return !fl;
        if(mp.find({F1,A2,F2})!=mp.end()) return fl^(A1>=mp[{F1,A2,F2}]);
        
        int l=0,r=S,res=r;
        while(l<=r){
            int mid=(l+r)>>1;
            if(dfs(A2,F2,mid+S,F1-1)&&dfs(A2-mid,F2,mid,F1)) l=mid+1;
            else r=mid-1,res=mid;
        }
        mp[{F1,A2,F2}]=res;
        return fl^(A1>=res);
    }
    
    
}
int c1,c2,f1,f2;
inline void solve(){
    c1=read(),f1=read(),c2=read(),f2=read();
    c1-=f2*S,c2-=f1*S;
    puts(dfs(c1,f2,c2,f1)?"YES":"NO");
}
signed main(){
    S=read();
    int T=read();
    while(T--) solve();
}
/*
1 1
1000000000000 0 1 1000000000000
*/
posted @ 2025-01-18 16:17  ~Cyan~  阅读(24)  评论(0)    收藏  举报