USACO22DEC银组&金组(20230704模拟赛)题解

T1 玩游戏

洛谷题目链接

这是一道博弈论题,但是数据比较大,于是只能使用打表找规律大法。

把每个房间分开来看,先打出 a i ≤ 1000 a_i \leq 1000 ai1000左右的表,然后就发现 a i   m o d   4 = 0 a_i \bmod 4 =0 aimod4=0时后手必胜,其余情况先手必胜。

我们来简单证明一下。

a i   m o d   4 = 0 a_i \bmod 4 = 0 aimod4=0时,由于任何质数(包括 1 1 1)模 4 4 4的余数都不为 0 0 0,所以取走任意合法个数后,后手都有办法将这个数重新变为 4 4 4的倍数。

其余情况,先手可以取走一个数将当前数变为 4 4 4的倍数。

接下来把所有房间合起来看。

作为先手,需要让必胜态尽量快点被取完,所以就减去一个尽量大的质数使得这个数变为 4 4 4的倍数。轮数则为 ( x − p ) / 4 + 1 (x-p)/4+1 (xp)/4+1 p p p为那个尽量大的质数, x x x则为当前个数。

而必败态则拖得越久越好,先手每次取 2 2 2,而后手则只能取走 2 2 2让这个数变回 4 4 4的倍数。轮数则为 x / 4 + 1 x/4+1 x/4+1

具体实现看代码。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN=100005;
const LL MAXM=5000005;
LL t;
LL n,a[MAXN];
LL p[MAXM],cnt;
bool prime[MAXM];
LL ans[MAXM],pri[5];
int main(){
    scanf("%lld",&t);
    p[++cnt]=1;
    for(LL i=2;i<=5000000;i++){
        if(!prime[i]){
            p[++cnt]=i;
            for(LL j=i+i;j<=5000000;j+=i){
                prime[j]=true;
            }
        }
    }
    for(LL i=0;i<=5000000;i+=4){
        ans[i]=i/4+1;
    }
    for(LL i=1;i<=5000000;i++){
        if(i%4==0)continue;
        if(!prime[i]){
            pri[i%4]=i;
        }
        ans[i]=(i-pri[i%4])/4+1;
    }
    while(t--){
        scanf("%lld",&n);
        memset(a,0,sizeof(a));
        for(LL i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        LL minn=LONG_LONG_MAX,minx;
        for(LL i=1;i<=n;i++){
            if(minn>ans[a[i]]){
                minn=ans[a[i]];
                minx=a[i];
            }
        }
        if(minx%4==0)printf("Farmer Nhoj\n");
        else printf("Farmer John\n");
    }
}

T2 看电影

洛谷题目链接

这道题看起来很像背包,但是仔细一分析发现正常做法无法 A C AC AC于是我们就需要一个不正常的做法。

可以发现我们需要让冰激凌尽量被花费在 x i x_i xi较小的人身上,因为在他身上冰激凌更值钱。

所以就先按照 x i x_i xi的大小排序,从前往后做一次背包,不考虑钱,只考虑冰激凌的花费。

而钱主要是被花费在冰激凌不值钱的人身上,所以从后往前做一次背包,只考虑金钱。

这时候会出现两种情况,第一种是有一个人,既用冰激凌又用钱,第二种是前面的一部分用冰激凌,后面的用钱。

如何确定这个分界线呢?答案是枚举,同时找到最大的欢迎程度。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN=2005;
LL n,a,b;
struct node{
    LL p,c,x;
    bool operator <(const node &T)const{
        return x<T.x;
    }
}t[MAXN];
LL f[MAXN][MAXN];
LL g[MAXN][MAXN];
int main(){
    scanf("%lld%lld%lld",&n,&a,&b);
    for(LL i=1;i<=n;i++){
        scanf("%lld%lld%lld",&t[i].p,&t[i].c,&t[i].x);
    }
    sort(t+1,t+n+1);
    for(LL i=1;i<=n;i++){
        for(LL j=0;j<=b;j++)f[i][j]=f[i-1][j];
        for(LL j=b;j>=t[i].x*t[i].c;j--)f[i][j]=max(f[i][j],f[i-1][j-t[i].x*t[i].c]+t[i].p);
    }
    for(LL i=n;i>=1;i--){
        for(LL j=0;j<=a;j++)g[i][j]=g[i+1][j];
        for(LL j=a;j>=t[i].c;j--)g[i][j]=max(g[i][j],g[i+1][j-t[i].c]+t[i].p);
    }
    LL ans=0;
    for(LL i=1;i<=n;i++){
        ans=max(ans,f[i-1][b]+g[i][a]);
        ans=max(ans,f[i][b]+g[i+1][a]);
        for(LL j=1;j*t[i].x<=min(b,t[i].c*t[i].x);j++){
            if(a-t[i].c+j<0)continue;
            ans=max(ans,f[i-1][b-t[i].x*j]+g[i+1][a-t[i].c+j]+t[i].p);
        }
    }
    printf("%lld",ans);
}

T3 山峰

洛谷题目链接

这道题考虑暴力用 n n n s e t set set维护每个山能看见的右边的山。

初始时直接暴力维护。

每次修改时,可以直接重构该点的 s e t set set,以达到更新右侧的效果。而左侧也是枚举。

由于最终每个 s e t set set中的值都是上升的,所以我们在判断它能否插入时,只需要与左边的第一个比较就行了。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pi pair<LL,double>
const LL MAXN=2005;
LL n,a[MAXN];
LL q;
set<pi>b[MAXN];
int main(){
    scanf("%lld",&n);
    for(LL i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(LL i=1;i<=n;i++){
        for(LL j=i+1;j<=n;j++){
            double t=double(a[j]-a[i])/double(j-i);
            if(b[i].empty()||(*prev(b[i].end())).second<=t){
                b[i].insert({j,t});
            }
        }
    }
    scanf("%lld",&q);
    while(q--){
        LL x,y;
        scanf("%lld%lld",&x,&y);
        a[x]+=y;
        b[x].clear();
        for(LL i=x+1;i<=n;i++){
            double t=double(a[i]-a[x])/double(i-x);
            if(b[x].empty()||(*prev(b[x].end())).second<=t){
                b[x].insert({i,t});
            }
        }
        for(LL i=1;i<x;i++){
            double t=double(a[x]-a[i])/double(x-i);
            auto it=b[i].lower_bound({x,t});
            if(it!=b[i].begin()){
                pi temp=(*(--it));
                if(temp.second>t){
                    continue;
                }
            }
            it=b[i].lower_bound({x,-1000000000});
            if((*it).first==x){
                b[i].erase(*it);
            }
            b[i].insert({x,t});
            it=b[i].upper_bound({x,t});
            while(it!=b[i].end()){
                if(t<=(*it).second)break;
                LL fi=(*it).first,se=(*it).second;
                b[i].erase((*it));
                it=b[i].upper_bound({fi,se});
            }
        }
        LL ans=0;
        for(LL i=1;i<=n;i++){
            ans+=b[i].size();
        }
        printf("%lld\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值