2024初三寒假年前集训测试3

2024初三年前集训测试3

ps:也不知道我为什么没写测试1,2的题解

T1 夕景昨日 \(100pts\)

题目描述

\(Shintaro\) 制作了 \(n\) 个开关,每个开关的状态可被设置为 \(+\)\(-\)

现在你有一个数列 $ A=(a_1,a_2, \dots,a_n ) $ ,和一个初始值为 \(0\) 的变量 \(v\) 。你可以自由地操纵开关,当第 \(i\) 个开关被设置为 \(+\) 状态时, 会加上 \(a_i\) ,被设置为 \(-\) 状态时, 会减去 \(a_i\)

请你判断是否有两种及以上不同的方式操纵开关,使得最后得到的 \(v\)值相等。

输入格式

第一行一个数 \(n\) ,表示开关的个数

第二行 \(n\) 个数,第 \(i\) 个数表示 \(a_i\)

输出格式

如果有请输出 \(Yes\),否则输出 \(No\)

$ 20 $ \(\%\) $: n \le 10 $

$ 100 $ \(\%\) $: 1 \le n \le 100000,0 \le a_i \le 500000 $

赛时思路

一看到题目,先懵了一会,看了数据范围,人都快傻了,想过难但没想到 \(T1\)都不会。然后想起了 \(2023 CSP-S\),我 \(T1\)做了2h,然后A了。上午两个小时又在推 \(T1\),还没想到可行解,做得挺难受的。最后打的暴搜+特判+面向数据点分治。

  1. 若 $ A $中含有 \(0\) ,则有解。

  2. 若 $ A $中含有相等的数,则有解。

  3. 当 $ n \le 30 $, 暴搜。

  4. 否则,直接输出 \(Yes\)

结果A了?挺难崩

正解

首先,当 $ n \ge 20 $ 时,一定有解

证明

我们假设数列 \(vis\) 内的元素无解,我们将一元素加入原数列时,若数列仍无解,当且权当 \(vis\)内每一个元素与 \(v\)之和,仍不重复,并将所有加和放入原数列,循环操作 \(k\) ,每次元素个数变为原来的 \(2\)倍。

观察数据范围,$ \sum_{i=1}^na_i \le 5e10 $,所以 $ 2^k \le 5e10 $。 \(k\) 不妨取 \(20\)

当 $ n \le 20 $,打暴力;否则,直接输出 \(Yes\)

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e7;
int n,a[N];
bool b[N<<2],flag;
void dfs(int x,int y)
{
    if(flag)return ;
    if(x==n){
        if(b[y+a[x]+N]){
            flag=1;
        }
        if(b[y-a[x]+N]){
            flag=1;
        }
        b[y+a[x]+N]=1;
        b[y-a[x]+N]=1;
        return;
    }
    dfs(x+1,y+a[x]);
    dfs(x+1,y-a[x]);
}
signed main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;i++){
        if(a[i]==0){
            printf("Yes");
            return 0;
        }
        if(b[a[i]]){
            printf("Yes");
            return 0;
        }
        b[a[i]+N]=1;
        b[-a[i]+N]=1;
    }
    if(n<=20){
        memset(b,0,sizeof(b));
        dfs(1,0);
        if(flag)printf("Yes");
        else printf("No");
    }
    else{
        printf("Yes");
    }
}

总结

应该注意值域范围,好像数据很大,但其中有效的很少。(感觉像数论Longge的问题

数据有点,只输出 \(Yes\)\(90 pts\)

透明答案 \(30pts\)

题目描述

\(Ayano\)\(Bob\) 在玩简单的取石子游戏。最初有 \(n\)堆石子,每堆有 \(2\) 块。

$ Ayano $ 和 $ Bob $ 轮流取石子(从 $ Ayano $ 开始)。每一次取石子时当前玩家可以任选一堆,如果还剩下 \(k\) 块石子,则可以从这堆中取出 \(1\)\(k\) 之间的任意数量的石子。

此外,每 \(3\) 回合他们会添加一堆石子(含 \(2\) 块石子)。换句话说,在第 \(t\) 次操作(两个人操作的总次数)之后,如果 \(t\) 可以被 \(3\) 整除,则添加一堆 \(2\) 块石子的石子堆。

(即使在第 \(t\) 次操作中取完了所有石子,如果 \(t\) 可被 \(3\) 整除,也会添加新石子堆并继续游戏。)

双方都采取最优策略,无法操作的玩家输掉游戏。请你判断哪个玩家获胜。

数据范围

$ 30 $ \(\%\) $: n\le 10 $

$ 100 $ \(\%\) $ n \le 1000 $

赛时思路

一眼博弈论,然后......读完题觉得不可做,就先跳了,\(T4\)做完再回来看看,结果只有两种情况,那我随意输出一个一定有分。那......输出什么呢?许多游戏都有先手必胜的性质,但并不保证多得分,写 \(rand\) 也比较悬。从地上捡了个小塑料片,随机吧,正面A,反面B。最后输出 \(Bob\) (期末考试一道历史题不会做就抛橡皮,正面B,反面D,结果它立起来了?那题答案是C

正解

博弈论,推了半个下午还是不会,学校题解给的很抽象:

爆搜找规律,发现 $ n \equiv 2 \ (\mod \ 3) $ 时, $ Bob $赢,否则 $ Ayano $ 赢。由于是普及题,所以放 \(dfs\) 过。

还是不会,先放下了。

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
    cin>>n;
    if(n%3==2){
        printf("Bob");
    }
    else {
        printf("Ayano");
    }
}

界外科学 \(100pts\)

题目描述

\(ENE\) 是一位电脑少女,这天她在帮 \(Shintaro\) 网上购物。网店一共有 \(n\) 件物品,第 \(i\) 件物品有 \(a_i\) 的价格,并且购买这件物品会给 \(Shintaro\) 带来 \(b_i\) 的满足度,不同的物品获得的满足度会累加。

\(Shintaro\) 最多只能支付 \(m\) 元。由于他资金有限, \(ENE\) 黑入了网店的支付系统。在她操作之后,总价格的计算方式是将所有物品的价格给 \(xor\) (异或运算)起来。

\(Shintaro\) 现在买了价格为 \(1\)\(2\)\(4\)\(7\) 的四件物品,总价格为 $ 1 \bigoplus 2 \bigoplus 4 \bigoplus 7=0 $ 。

\(Shintaro\) 现在想知道在足够支付所买的物品的前提下,他最多能获得多少满足度。

数据范围

$ 30 $ \(\%\) $ :n \le 5 $

$ 50 $ \(\%\) $ :n \le 20 $

另外 $ 20 $ \(\%\) $ :1 \le m,a_i \le 100 $

$ 100 $ \(\%\) $ :1 \le n \le 36 , 1 \le m,a_i, \left| b_i \right| \le 10^9 $

赛时思路

$ n \le 36 $ ,为暴搜准备的嘛。但复杂度 $ O(2^n) $ ,最坏情况下会 \(TLE\) ,所以写了一个剪枝:

用sum[i] 记录以i为起点,数组b的后缀和,当当前价值加后续所有价值仍小于现有的 ans ,那就不需要再搜下去了。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=50;
struct node{
    int a,b;
}s[N];
int n,m,ans,sum[N];
inline int Max(int x,int y)
{
    if(x>=y)return x;
    else return y;
}
void dfs(int x,int y,int z)
{
    if(x==n){
        if((y^s[x].a)<=m){
            ans=Max(ans,z+s[x].b);
        }
        if(y<=m){
            ans=Max(ans,z);
        }
        return ;
    }
    if(z+sum[x]<=ans)return;
    dfs(x+1,(y^s[x].a),z+s[x].b);
    dfs(x+1,y,z);
}
bool cmp(node x,node y)
{
    return x.b>y.b;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",&s[i].a);
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&s[i].b);
    }
    sort(s+1,s+n+1,cmp);
    for(int i=n;i>=1;i--){
        if(s[i].b>=0)
        sum[i]=sum[i+1]+s[i].b;
        else sum[i]=sum[i+1];
    }
    dfs(1,0,0);
    printf("%lld",ans);
}

闲话:数据过水,虽然赛时AC,但后来被 Vsinger_LuoTianYi $ hack $了,机房每份代码都被 $ hack $了一遍。另外把价值都加起来好像就可以AC。

原代码的剪枝不能处理价值为负数的情况,调整一下sum的定义,只使其记录正数。

注意一个点,位运算一定要加括号。

整个集训最难崩的一道题

正解

折半搜索,可将复杂度降到 \(O(2^{\frac{n+4}{2}}+2^n)\) ,挺神奇的,可以学一下,但这题我没打出来,因为异或运算不像普通的加减法,有一些特殊的性质,处理出异或值和权值,再用 \(01trie\) 求最大值。

回忆补时 \(30pts\)

题目描述

\(Shintaro\)\(n\) 条直线,第 \(i\) 条直线 \(l_i\) 可以被描述为 \(k_ix+b_i\)

这天 \(Ayano\)\(Shintaro\) 在一起玩游戏。每局游戏 \(Ayano\) 会给出一个整数 \(x\) ,然后让 \(Shintaro\) 选两条不同的直线 \(l_i,l_j\) ,得到 $ y=k_j \times(k_i \times x +b_i)+b_j $ 作为他的得分。

作为游戏中级高手, \(Shintaro\) 觉得得分肯定是越大越好,然而他不知道自己的得分是否达到了最大。所以对于每局游戏里 \(Ayano\) 给出的整数 \(x\) ,请你告诉 \(Shintaro\) 可能得分的最大值。

数据范围

$ 30 $ \(\%\) $ :n,q \le 100 $

$ 60 $ \(\%\) $ :n,q \le 3000 $

$ 100 $ \(\%\) $ :2 \le n,q \le 100000, \left| x_i \right|,\left| k_i \right| \le 10^6, \left|b_i \right| \le 10^{12} $

赛时思路

能感觉到题目很难,但是得分比较容易,从前做过两道与直线解析式的题目,做得还算轻松。

先考虑 $ n \le 100 $ ,很容易想到 $ O(n^2q) $ 的做法,每次查询枚举直线 \(i\) ,再枚举直线 \(j\) ,求最大值即可。可得 \(30pts\)

$ O(n\ logn\ q) $的做法,我们可以知道直线是具有单调性的:

当 $ k \ge 0 $ 时, \(y\)\(x\) 增大而增大,我们只需要知道最大值

当 $ k<0 $ 时, \(y\)\(x\) 增大而减小,我们只需要知道最小值

注意特判 \(i=j\) 即所选同一条直线的情况(你猜为什么 $ n \ge 2 $ )

预估 \(60pts\) ,但赛时结果大小计算有误,开了 $ __int 128 $ ,由于速度比 \(long long\) 慢,挂了 \(30pts\) 。据 $ The\_Shadow\_Dragon $ 说,可以优化一下, $ O(n) $ 求出最大值,次大值,最小值,次小值。总复杂度 $ O(nq) $ 。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+100;
int n,q,x,k[N],b[N],z[N],ans;
struct node{
    int y,id;
}num[N];
inline int read()
{
    int f=1,w=0;
    char c=getchar();
    while(c>'9'||c<'0'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        w=w*10+(c-'0');
        c=getchar();
    }
    return w*f;
}
void write(int x)
{
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
void qwrite(int x)
{
    write(x);
    puts("");
}
bool cmp(node a,node b)
{
    return a.y<b.y;
}
inline int Max(int a,int b)
{
    if(a>b)return a;
    else return b;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++){
        k[i]=read();
        b[i]=read();
    }
    q=read();
    while(q--){
        x=read();
        ans=-1e13;
        for(int i=1;i<=n;i++){
            num[i].y=k[i]*x+b[i];
            num[i].id=i;
        }
        sort(num+1,num+n+1,cmp);
        for(int i=1;i<=n;i++){
            if(k[i]>0){
                if(num[n].id==i)z[i]=num[n-1].y*k[i]+b[i];
                else z[i]=num[n].y*k[i]+b[i];
            }
            else {
                if(num[1].id==i)z[i]=num[2].y*k[i]+b[i];
                else z[i]=num[1].y*k[i]+b[i];
            }
            ans=Max(ans,z[i]);
        }
        qwrite(ans);
    }
}

感觉寒假年前集训过得很快,这就要结束了,做的题目不是很多,主要是一些较基础的字符串,数论 \(exgcd 和 欧拉函数\) 写了写,有的题目是有难度的,还有题目没A,打算过年几天写。剩下的时间在打模拟赛,调题,前两场打得稀烂,挂分不少,但给人一些启发,也触碰到一些盲点。现在比较关心过年不会要补文化课吧。

posted @ 2024-02-05 21:40  Abnormal123  阅读(52)  评论(2编辑  收藏  举报