Logo __vector__ 的博客

博客

标签
暂无

P7078 [CSP-S2020] 贪吃蛇 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-16 20:08:54

难度:$\color{black}\text{NOI/NOI+/CTSC}$

$\color{green}\text{AC算法:}$贪心,队列

题目分析:

题面不用解释,就是让回答有多少蛇将存活。

显然,蛇保命的优先级最高,在自身能保命的前提下尽可能多的弄死别的蛇 (让我想起了海盗分金问题)

设当前蛇为 $dq$

可以分两种情况讨论:
1、 $dq$ 吃了最弱的蛇之后不会变成最弱的蛇
2、 $dq$ 吃了最弱的蛇之后会变成最弱的蛇

对于情况一:
显然可以大胆吃

对于情况二:
设 $dq$ 进食后,新的老大为 $dq2$。此时 $dq2$也遇到了相同两种情况。如果 $dq2$ 进食之后不会变成最弱的蛇,那么 $dq2$ 一定会吃掉 $dq$,$dq$ 不应该进食。如果 $dq2$ 进食之后会变成最弱的,那么那就再看 $dq3$ 的情况........就这样递归下去。直到有一条蛇可以吃或者只剩下两条蛇。此时设第 $i$ 个蛇可以放心吃。那么第 $i-1$ 条蛇就不应该吃,否则将在下一轮被吃掉。而第 $i-2$ 条蛇可以吃,因为它知道第 $i-1$ 条蛇不敢吃它。
现在就有了一个结论:对于第 $i$ 轮递归的蛇可以大胆吃,如果 $i$ 为奇数,那么当前蛇就不应该吃;如果 $i$ 为偶数,那么当前蛇就可以大胆吃,然后转化为 $i$ 为奇数的稳定状态。(当然如果只有两条蛇那么只有一条蛇可以存活)

题目分析完毕,剩余的在注释里。

AC代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn=1e6+5;
#define reg register
int a[maxn];
int t;
int n;
int k;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
inline void write(int x)
{
    if(x<0)
    {
        putchar('-');
        x=-1;
    }
    if(x>9)write(x\/10);
    putchar(x%10+'0');
}
int main()
{
    t=read();
    int testcnt=0;
    while(t--)
    {
        testcnt++;
        int ans=0;
        if(testcnt==1)
        {
            n=read();
            for(reg int i=1;i<=n;i++)
            {
                a[i]=read();
            }
        }
        if(testcnt>1)
        {
            k=read();
            reg int x,y;
            for(reg int i=1;i<=k;i++)
            {
                x=read();
                y=read();
                a[x]=y;
            }
        }
        deque<pair<int,int> > que1;
        deque<pair<int,int> > que2;
        for(reg int i=1;i<=n;i++)
        {
            que1.emplace_back(make_pair(a[i],i));
        }
        while(1)
        {
            if(que1.size()+que2.size()==2)
            {
                ans=1;
                break;
            }
            \/\/取出最弱
            int y=que1.front().first;
            que1.pop_front();
            \/\/取出最强
            int x;
            int bianhao;\/\/最强者编号
            if(que2.empty()||(!que1.empty()&&que1.back()>que2.back()))
            {\/\/如果que1不空且它的最后一个元素是两个队列最大的才行
            \/\/但是如果que2没有就只能选que1
                x=que1.back().first;
                bianhao=que1.back().second;
                que1.pop_back();
            }
            else{
                x=que2.back().first;
                bianhao=que2.back().second;
                que2.pop_back();
            }
            pair<int,int> xianzai=make_pair(x-y,bianhao);
            if(que1.empty()||xianzai<=que1.front())\/\/pair自动比较第一个参数
            {\/\/最强蛇进食之后变成了最弱蛇
                ans=que1.size()+que2.size()+2;
                \/\/先假设不吃
                int count=0;
                \/\/第几轮递归
                while(1)
                {
                    count++;
                    if(que1.size()+que2.size()+1==2)
                    {
                        if(!(count&1))ans--;
                        \/\/可以发现,当轮数为偶数时,当前最强才能放心吃
                        break;
                    }
                    \/\/重新选择当前最强递归下去
                    if(que2.empty()||!que1.empty()&&que1.back()>que2.back())
                    {   \/\/如果que1不空且它的最后一个元素是两个队列最大的才行
                        \/\/但是如果que2没有就只能选que1
                        x=que1.back().first;
                        bianhao=que1.back().second;
                        que1.pop_back();
                    }
                    else{
                        x=que2.back().first;
                        bianhao=que2.back().second;
                        que2.pop_back();
                    }
                    xianzai=make_pair(x-xianzai.first,bianhao);\/\/之前的最强已经是最弱了,让现在的最强进食
                    if(!((que1.empty()||xianzai<que1.front())&&(que2.empty()||xianzai<que2.front())))
                    {\/\/如果不是最弱的,那么看看能不能吃
                        if(!(count&1))
                        {
                            ans--;
                        }
                        break;
                    }
                }
                break;
            }
            else
            {
                que2.emplace_front(xianzai);
                \/\/如果吃掉后不是最弱,那么大胆吃!
            }
        }
        write(ans);
        putchar('\n');
    }
    return 0;
}

P1526 [NOI2003] 智破连环阵题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-17 21:06:48

难度:$\color{black}\text{NOI/NOI+/CTSC}$

题面:

不需要解释

题目分析:

注意,第 $i$ 号武器被摧毁后第 $i+1$ 号武器就会立即进入攻击状态。炸弹的那个持续引爆5分钟实际上就是说炸弹能摧毁的武器都会立即摧毁,那个5分钟实际上没有用,例如第$1,2,3,4,6$号武器在攻击范围内,那么炸弹将会瞬间摧毁$1,2,3,4$号武器。
可以开个数组 attack[maxn][maxn],记录第 $j$ 个武器是否在第 $i$ 个炸弹的攻击范围内。这么处理:

for(reg int i=1;i<=n;i++)
{
    for(reg int j=1;j<=m;j++)
    {
        if(k*k>=((zd[i].x-wq[j].x)*(zd[i].x-wq[j].x)+(zd[i].y-wq[j].y)*(zd[i].y-wq[j].y)))\/\/k*k省去开根运算
        {
            attack[i][j]=1;
        }
    }
}

然后跑个dp求解第$i$个武器攻击的情况下从第 $j$ 个武器炸起最大能炸到的武器编号:

for(reg int i=1;i<=n;i++)
{
    for(reg int j=m;j>0;j--)
    {
        if(!attack[i][j])continue;
        max_attack[i][j]=max(j,max_attack[i][j+1]);
    }
}

再跑个dp求解从第 $i$ 个武器炸起炸毁最后一个武器最少需要多少武器

for(reg int i=m;i>0;i--)
{\/\/dp
    for(reg int j=1;j<=n;j++)
    {
        if(!attack[j][i])continue;
        min_zd[i]=min(min_zd[i],min_zd[max_attack[j][i]+1]+1);
    }\/\/当前炸起最远能炸到max_attack[j][i],加上1就到了下一个炸弹所对应的武器区间。
    \/\/所以min_zd[max_attack[j][i]+1]就是下一个炸弹对应的武器区间炸起所需要的最少炸弹。
    \/\/min_zd[max_attack[j][i]+1]还要加上1是因为要考虑到min_zd[max_attack[j][i]+1]是后面的武器区间,
    \/\/还需要算上当前炸弹。
}

然后定义一个数组wcpd[maxn][maxn],代表第 $i$ 个武器是否对应第 $j$个 武器区间 再定义一个数组vis[maxn][maxn],用于匈牙利算法

然后就是dfs了,定义:

void dfs(int x,int qj);\/\/到了第x个武器,分了qj个区间

然后定义一个数组huisu,用于暂时存储

然后从 $x \rightarrow m$枚举武器($i$),从 $1 \rightarrow n$ 枚举炸弹($j$),如果武器 $x$ 在当前炸弹的攻击范围内($attack[j][x]==true$)并且当前炸弹从 $x$ 炸起最大能炸到的编号($max_attack[j][x]$) $\ge$ 当前枚举武器编号($i$) ($max_attack[j][x]$ $\ge$ $i$),那么标记上第 $j$ 个武器对应第 $i$个 武器区间($ wcpd[j][qj+1]=1$)。然后用匈牙利算法看一下当前炸弹是否能匹配上,如果能,那么递归下去即可。最后要注意回溯。这一部分的代码:

bool xylsf(int x)\/\/匈牙利算法
{
    for(int i=1;i<=n;i++)
    {
        if(wcpd[i][x]&&!vis[i])
        {
            vis[i]=1;
            if(!dy[i]||xylsf(dy[i]))
            {
                dy[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
void dfs(int x,int qj)\/\/到了第x个武器,分了qj个区间
{
    if(qj+min_zd[x]>=ans)return;\/\/剪枝
    if(x>m)
    {
        ans=qj;
        return;
    }
    int huisu[maxn];\/\/用于回溯
    for(reg int i=x;i<=m;i++)
    {
        for(reg int j=1;j<=n;j++)
        {
            huisu[j]=dy[j];
            if(attack[j][x]&&max_attack[j][x]>=i)
            {
                wcpd[j][qj+1]=1;\/\/j控制了qj++的武器区间
            }
        }
        memset(vis,0,sizeof(vis));
        if(xylsf(qj+1))dfs(i+1,qj+1);
        \/\/接下来是回溯
        for(reg int j=1;j<=n;j++)
        {
            dy[j]=huisu[j];
            if(attack[j][x]&&max_attack[j][x]>=i)
            {
                wcpd[j][qj+1]=0;\/\/j控制了qj++的武器区间
            }
        }
    }
}

题目分析完毕

$\color{green}\text{AC代码:}$

#include <bits\/stdc++.h>
using namespace std;
#define reg register
int n,m,k;
const int maxn=105;
bool attack[maxn][maxn];\/\/第i个炸弹是否能炸掉第j个武器
int max_attack[maxn][maxn];\/\/第i个炸弹进行攻击,从第j个武器开始炸能攻击到的最大编号武器
int min_zd[maxn];\/\/从第i个开始炸起,最少要用多少炸弹才能炸掉最后的武器
int ans;
int dy[maxn];\/\/一个炸弹配对了那些
bool wcpd[maxn][maxn];
bool vis[maxn];
struct Wq\/\/武器
{
    int x,y;
}wq[maxn];
struct Zd\/\/炸弹
{
    int x,y;
}zd[maxn];
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
bool xylsf(int x)\/\/匈牙利算法
{
    for(int i=1;i<=n;i++)
    {
        if(wcpd[i][x]&&!vis[i])
        {
            vis[i]=1;
            if(!dy[i]||xylsf(dy[i]))
            {
                dy[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
void dfs(int x,int qj)\/\/到了第x个武器,分了qj个区间
{
    if(qj+min_zd[x]>=ans)return;\/\/剪枝
    if(x>m)
    {
        ans=qj;
        return;
    }
    int huisu[maxn];\/\/用于回溯
    for(reg int i=x;i<=m;i++)
    {
        for(reg int j=1;j<=n;j++)
        {
            huisu[j]=dy[j];
            if(attack[j][x]&&max_attack[j][x]>=i)
            {
                wcpd[j][qj+1]=1;\/\/j控制了qj++的武器区间
            }
        }
        memset(vis,0,sizeof(vis));
        if(xylsf(qj+1))dfs(i+1,qj+1);
        \/\/接下来是回溯
        for(reg int j=1;j<=n;j++)
        {
            dy[j]=huisu[j];
            if(attack[j][x]&&max_attack[j][x]>=i)
            {
                wcpd[j][qj+1]=0;\/\/j控制了qj++的武器区间
            }
        }
    }
}
inline void indata()
{
    m=read();
    n=read();
    k=read();
    for(reg int i=1;i<=m;i++)
    {
        wq[i].x=read();
        wq[i].y=read();
    }
    for(reg int i=1;i<=n;i++)
    {
        zd[i].x=read();
        zd[i].y=read();
    }
}
inline void init()
{
    for(reg int i=1;i<=n;i++)
    {
        for(reg int j=1;j<=m;j++)
        {
            if(k*k>=((zd[i].x-wq[j].x)*(zd[i].x-wq[j].x)+(zd[i].y-wq[j].y)*(zd[i].y-wq[j].y)))\/\/k*k省去开根运算
            {
                attack[i][j]=1;
            }
        }
    }
    for(reg int i=1;i<=n;i++)
    {
        for(reg int j=m;j>0;j--)
        {
            if(!attack[i][j])continue;
            max_attack[i][j]=max(j,max_attack[i][j+1]);
        }
    }
    for(reg int i=1;i<=m;i++)min_zd[i]=0x3f3f3f3f;
    for(reg int i=m;i>0;i--)
    {\/\/dp
        for(reg int j=1;j<=n;j++)
        {
            if(!attack[j][i])continue;
            min_zd[i]=min(min_zd[i],min_zd[max_attack[j][i]+1]+1);
        }\/\/当前炸起最远能炸到max_attack[j][i],加上1就到了下一个炸弹所对应的武器区间。
        \/\/所以min_zd[max_attack[j][i]+1]就是下一个炸弹对应的武器区间炸起所需要的最少炸弹。
        \/\/min_zd[max_attack[j][i]+1]还要加上1是因为要考虑到min_zd[max_attack[j][i]+1]是后面的武器区间,
        \/\/还需要算上当前炸弹。
    }
    ans=n;
}
int main()
{
    indata();
    init();
    dfs(1,0);
    printf("%d",ans);
    return 0;
}

P1082 [NOIP2012 提高组] 同余方程题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-19 10:41:29

置顶消息:

zgc大佬认为本题解的 $x = (x \mod b + b) \mod b$ 是错的,看来本题解应该是出错了,但是我太弱了不会改,这篇题解很垃圾

难度:$\color{green}\text{普及+/提高}$

题目分析:

题目要求关于 $x$ 的方程 $ax \equiv 1(\mod b)$ ,也就是 $ax \mod b = 1$ 的最小整数解。
可以发现,$x$ 就是 $a$的逆元,记作 $inv(a)$, 然后方程转换为:
$a \times inv(a) + b \times y = 1$
这么转化的原因是 $a \times inv(a)$ 与 $1$ 之间的差肯定是 $b$ 的倍数。($y$ 代表 $b$ 的多少倍,可以为负数)
转化完之后就可以直接用扩欧求解了。
最后答案可能不是最小,也可能是负数。
先上一个式子:
若 $ax \mod b = 1$,
那么 $a(x+bn) \mod b = 1$
$n$ 是一个变量。
所以最后如果 $x$ 是负数,就大量加上 $b$ 变为正数,如果太大就大量减去 $b$ 即可。
最后处理的式子: $x = (x \mod b + b) \mod b$
注解:$x \mod b + b$处理负数,最后括号外面的 $\mod b$ 处理正数。

代码:

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
ll a,b;
ll x,y;
void exgcd(ll a,ll b)
{
    if(b==0)
    {
        x=1;
        y=0;
        return;
    }
    exgcd(b,a%b);
    ll tmp=x;
    x=y;
    y=tmp-a\/b*y;
}
int main()
{
    scanf("%lld%lld",&a,&b);
    exgcd(a,b);
    printf("%lld",(x%b+b)%b);
    return 0;
}

P1516 青蛙的约会 推式子过程

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-20 16:15:41

懒得写题解,就把推算过程放上来

$x+mt \equiv y+nt (\mod L)$
因此
$(x+mt)-(y+nt)$ 一定为 $L$ 的倍数
$(x+mt)-(y+nt) = aL$
$(x+mt)-(y+nt)-aL = 0$
$(x-y)+t(m-n)-aL=0$
$t(n-m)+aL=(x-y)$
设 $w = n-m$,$b = x-y$。
$ tw + aL=b$
$w,L,b$ 为常数
设 $c = GCD(w,L)$
若 $b \mod c \neq 0$,无解。
对这个方程:$tw+aL = c$ 求一组解 $t0,a0$
然后两边同时乘$(b/c)$ 变为:
$t0 \cdot w \cdot (b/c) + a0 \cdot L \cdot (b/c) = d$
因为要求的是 $t$,那么本题的解就是:
$t0 * (b/c)$
然后因为要求最小解,那么处理一下:
$ans = (t0 *(b/c) \mod (L \div c) + (L \div c)) \mod (L \div c)$

中国剩余定理(crt)学习记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-20 16:50:36

对于这样一个方程组:
$\begin{cases} x \equiv a1 (\mod mod1)\ x \equiv a2 (\mod mod2)\\
x \equiv a3 (\mod mod3)\ \cdots\cdots\ x \equiv an (\mod modn) \end{cases}$
设一个数 $MUL = \Pi_{i=1}^{n} modi$
设 $MODi = \frac{a}{modi}$
设 $Ci$ 为 $MODi$ 在模 $modi$ 意义下的逆元
解: $x = \Sigma_{i=1}^{n} ai \cdot MODi \cdot Ci$
注解:因为 $Ci$ 为 $MODi$ 的逆元 ,$MODi \cdot Ci \equiv 1 (\mod modi)$,乘上$ai$,就变为 $ai \cdot MODi \cdot Ci \equiv ai (\mod modi)$ 了
最后 $ans = x \mod MUL $

P2158 [SDOI2008] 仪仗队题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-21 11:28:49

难度:$\color{blue}\text{提高+/省选-}$

前言:

这题是蓝题但是很水

题目分析:

瞄一眼就能发现这个方阵可以被左下---右上对角线分成对称的两部分(中间的对角线是单独的一部分,单独计算)。就只看下面那半部分好了,可以发现,对于第 $i$ 列($i \ge 2$,从左数),能看到的人数就是 $\phi(i-1)$。所以这半部分总共能看到的人数就是 $\Sigma_{i=2}^{n}\phi(i-1)$。
另一部分和这一部分的答案一样。至于剩下的那个对角线,可以发现对角线上肯定只有第一个人能被看见。
所以上下两部分加上对角线总共能看到的人数就是:
$2\cdot \Sigma_{i=2}^{n}\phi(i-1)+1$
复杂度:$\Theta(n\sqrt{n})$

特殊情况:

如果 $n = 1$,那么答案应该为 $0$,要特判一下。

欧拉函数:

设一个数为 $x$,欧拉函数就是求有多少个数 $i$ $(1 \le i \le x)$,与 $x$ 互质 $(gcd(i,x)=1)$ 。 记作 $\phi(x)$。
放上个求 $\phi(x)$ 的板子:

int eular(int n)
{
    int ret=1;
    for(reg int i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            n\/=i;
            ret*=(i-1);
            while(n%i==0)
            {
                n\/=i;
                ret*=i;
            }
        }
    }
    if(n>1)ret*=n-1;
    return ret;
}

$\color{green}\text{AC代码:}$

#include <bits\/stdc++.h>
using namespace std;
#define reg register
int n;
int eular(int n)
{
    int ret=1;
    for(reg int i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            n\/=i;
            ret*=(i-1);
            while(n%i==0)
            {
                n\/=i;
                ret*=i;
            }
        }
    }
    if(n>1)ret*=n-1;
    return ret;
}
signed main()
{
    scanf("%lld",&n);
    if(n==1)
    {
        printf("0");
        return 0;
    }
    int ans=0;
    for(reg int i=2;i<=n;i++)
    {
        ans+=eular(i-1);
    }
    ans=(ans<<1)+1;
    if(ans<0)return -1;
    printf("%d",ans);
    return 0;
}

CF1633B Minority题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-02-02 21:06:29

题目大意:

给定一个数组。可以随便选一个子区间执行这样的操作:
一、如果这个子区间 $0$ 和 $1$ 的数量相同,不执行操作
二、如果这个子区间 $0$ 和 $1$ 的数量不同,若 $0$ 的数量严格小于 $1$ ,那么删除子区间所有的 $0$ ,反之删除子区间所有的 $1$ 。
求最多能删除的元素个数

题目解法:

第一眼看到,只能想到 $O(n^2)$的暴力(我太菜了)
再想想,可以发现可以贪心的选择整个数组执行操作。这样就能保证删除元素最多了。
如果 $0$ 和 $1$ 数量相等呢?只需要从区间头部或尾部随便删去一个元素就行了。

AC代码:

#include <bits\/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int t;
int n;
const int maxn=2e5+5;
char a[maxn];
int zero;
int one;
int ans;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        zero=one=0;
        scanf("%s",a+1);
        n=strlen(a+1);
        for(int i=1;i<=n;i++)
        {
            if(a[i]=='0')zero++;
            if(a[i]=='1')one++;
        }
        ans=min(zero,one);
        if(zero==one)ans--;
        printf("%d\n",ans);
    }
 \/\/   system("pause");
    return 0;
}

CF1633C题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-02-04 20:10:22

题目意思:

一个角色与怪物决斗。
角色有以下两个属性:
1.$hc$:生命值
2.$dc$:攻击力
怪物有以下两个属性:
1.$hm$:生命值
2.$dm$:攻击力

决斗方式:
1.角色向怪物发动攻击,怪物生命值掉了 $dc$。
2.怪物向角色发动攻击,角色生命值掉了 $dm$。
如此循环,直到其中一方的生命值小于等于 $0$,另一方获胜。

决斗开始之前,角色可以购买装备,角色有 $k$ 枚硬币,一个硬币可以使生命值增加 $a$,也可以使攻击力增加 $w$。
求如果以最优的方式购买装备,角色是否能打败怪物。

有 $t$ 组数据,$1 \le t \le 5 \cdot 10^4$。

题目分析:

看到这道题,可以发现测试数据组数很多,可能需要 $O(1)$ 回答每组询问,但是,既然是要求最优方案,难以做到 $O(1)$ 回答询问。
此时可以发现,题目里面说了,所有测试数据的 $k$ 之和不超过 $2 \cdot 10^5$。所以直接枚举分配给生命值和角色的硬币数量,对于枚举到的每组方案显然可以 $O(1)$ 判断角色是否可以胜利,如果可以胜利输出 YES,如果所有方案都不可行输出 NO(我到比赛结束前十几分钟才发现这个最后没时间写了) 这道题就做出来了。

注意事项:

如果就这样写,将会在第 $13$ 个测试点 WA 掉,原因是神仙 hacker 造的数据会爆掉 long long,所以在 C++20 下开 __int128 即可。

AC代码:

#include <bits\/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int t;
signed main()
{
    scanf("%lld",&t);
    int hc,dc,hm,dm,k,w,a;
    while(t--)
    {
        scanf("%lld%lld",&hc,&dc);\/\/角色的生命和攻击力
        scanf("%lld%lld",&hm,&dm);\/\/怪物的生命和攻击力
        scanf("%lld%lld%lld",&k,&w,&a);
        bool flag=0;
        for(int i=0;i<=k;i++)
        {
            int gjl=(k-i)*w+dc;\/\/新的攻击力
            int sd=hm\/gjl-1;\/\/杀死怪物时受到伤害的次数
            if(hm%gjl>0)sd++;
            if((__int128)hc+(__int128)i*(__int128)a>(__int128)sd*(__int128)dm)
            {
                printf("YES\n");
                flag=1;
                break;
            }
        }
        if(!flag)printf("NO\n");
    }
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

How to AK Educational Codeforces Round 122 (1633)

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-02-06 20:00:52

A题 Div. 7

签到题,$\color{green}\text{AC代码:}$

#include <bits\/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int t;
int n;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        if(n%7==0)
        {
            cout<<n<<endl;
            continue;
        }
        int zh=n%10;\/\/最后一位
        int m=n%7;
        if(zh-m>=0)
        {
            cout<<n-m<<endl;
            continue;
        }
        if(zh-m<0)
        {
            cout<<n-m+7<<endl;
        }
    }
 \/\/   system("pause");
    return 0;
}

B题 Minority

题目大意:

给定一个数组。可以随便选一个子区间执行这样的操作:
一、如果这个子区间 $0$ 和 $1$ 的数量相同,不执行操作
二、如果这个子区间 $0$ 和 $1$ 的数量不同,若 $0$ 的数量严格小于 $1$ ,那么删除子区间所有的 $0$ ,反之删除子区间所有的 $1$ 。
求最多能删除的元素个数

题目解法:

第一眼看到,只能想到 $O(n^2)$的暴力(我太菜了)
再想想,可以发现可以贪心的选择整个数组执行操作。这样就能保证删除元素最多了。
如果 $0$ 和 $1$ 数量相等呢?只需要从区间头部或尾部随便删去一个元素就行了。

AC代码:

#include <bits\/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int t;
int n;
const int maxn=2e5+5;
char a[maxn];
int zero;
int one;
int ans;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        zero=one=0;
        scanf("%s",a+1);
        n=strlen(a+1);
        for(int i=1;i<=n;i++)
        {
            if(a[i]=='0')zero++;
            if(a[i]=='1')one++;
        }
        ans=min(zero,one);
        if(zero==one)ans--;
        printf("%d\n",ans);
    }
 \/\/   system("pause");
    return 0;
}

C题 Kill the Monster

题目意思:

一个角色与怪物决斗。
角色有以下两个属性:
1.$hc$:生命值
2.$dc$:攻击力
怪物有以下两个属性:
1.$hm$:生命值
2.$dm$:攻击力

决斗方式:
1.角色向怪物发动攻击,怪物生命值掉了 $dc$。
2.怪物向角色发动攻击,角色生命值掉了 $dm$。
如此循环,直到其中一方的生命值小于等于 $0$,另一方获胜。

决斗开始之前,角色可以购买装备,角色有 $k$ 枚硬币,一个硬币可以使生命值增加 $a$,也可以使攻击力增加 $w$。
求如果以最优的方式购买装备,角色是否能打败怪物。

有 $t$ 组数据,$1 \le t \le 5 \cdot 10^4$。

题目分析:

看到这道题,可以发现测试数据组数很多,可能需要 $O(1)$ 回答每组询问,但是,既然是要求最优方案,难以做到 $O(1)$ 回答询问。
此时可以发现,题目里面说了,所有测试数据的 $k$ 之和不超过 $2 \cdot 10^5$。所以直接枚举分配给生命值和角色的硬币数量,对于枚举到的每组方案显然可以 $O(1)$ 判断角色是否可以胜利,如果可以胜利输出 YES,如果所有方案都不可行输出 NO(我到比赛结束前十几分钟才发现这个最后没时间写了) 这道题就做出来了。

注意事项:

如果就这样写,将会在第 $13$ 个测试点 WA 掉,原因是神仙 hacker 造的数据会爆掉 long long,所以在 C++20 下开 __int128 即可。

AC代码:

#include <bits\/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int t;
signed main()
{
    scanf("%lld",&t);
    int hc,dc,hm,dm,k,w,a;
    while(t--)
    {
        scanf("%lld%lld",&hc,&dc);\/\/角色的生命和攻击力
        scanf("%lld%lld",&hm,&dm);\/\/怪物的生命和攻击力
        scanf("%lld%lld%lld",&k,&w,&a);
        bool flag=0;
        for(int i=0;i<=k;i++)
        {
            int gjl=(k-i)*w+dc;\/\/新的攻击力
            int sd=hm\/gjl-1;\/\/杀死怪物时受到伤害的次数
            if(hm%gjl>0)sd++;
            if((__int128)hc+(__int128)i*(__int128)a>(__int128)sd*(__int128)dm)
            {
                printf("YES\n");
                flag=1;
                break;
            }
        }
        if(!flag)printf("NO\n");
    }
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

D题 Make Them Equal

题目意思:

有 $3$ 个大小为 $n$ 的数组,$a$,$b$ 和 $c$
给定 $a$ 和 $b$。$a$ 数组初始所有值都为 $0$。

有 $k$ 次操作,每次操作可以过程如下:

  1. 选择两个整数 $i$ 和 $x$ $(1 \le i \le n,x \ge 1)$
  2. $a_i$ 变为 $a_i + \frac{a_i}{x}$

若 $a_i = b_i $, 则获得 $c_i$ 的分数。
求经过 $k$ 此操作最多能获得的分数。

数据范围:

有 $t$ 组数据 $(1 \le t \le 100)$。
$1 \le n \le 10^3$。
$1 \le b_i \le 10^3$。
$1 \le c_i \le 10^6$。

题目分析:

定义一个数组 $dis$,$dis_i$ 代表$1 \rightarrow i$,最小转换次数,由于 $n$ 特别小,可以 $O(n^2)$ 预处理。
定义一个数组 $f$ ,$f_i$ 代表操作次数为 $i$ 时最多能获得多少分。
可以发现 $1$ 到 $10^3$ 次方的范围内,最多转换 $12$ 次。总共操作数就是 $min(12 \cdot n,k)$。超过了 $12 \cdot n$ 就无意义了。
对于每组询问跑 dp 处理 $f$ 数组。
设 $p = min(12 \cdot n,k)$
最终答案就是 $f_p$。

AC代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn=1e3+5;
int t;
int n,k;
int b[maxn];
int c[maxn];
int dis[maxn];\/\/从1到i最少多少步
int f[maxn*12];\/\/操作数量为i时最大收益
int main()
{
    memset(dis,0x3f3f3f3f,sizeof(dis));
    dis[1]=0;
    for(int i=1;i<=maxn;i++)
    {
        for(int j=1;j<=i;j++)
        {
            dis[i+i\/j]=min(dis[i+i\/j],dis[i]+1);
        }
    }
    scanf("%d",&t);
    while(t--)
    {
        memset(f,0,sizeof(f));
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&b[i]);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&c[i]);
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=min(k,12*n);j>=dis[b[i]];j--)
            {
                f[j]=max(f[j],f[j-dis[b[i]]]+c[i]);
            }
        }
        printf("%d\n",f[min(k,12*n)]);
    }
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

CSP-J2021游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-02-19 18:37:46

Day0:

第一次正式比赛。

比赛地点是日照。停了一天课,坐大巴去。车上没网,所以闲的没事,也没啥好说的,中午就到了日照。
车就停在酒店门口,酒店还挺大的。我和 lhx 神仙(@cancan12345) 被教练分在同一个房间。房间里有很多以前都没见过设施,感觉还很豪华,瞎逛了逛。
然后........整个下午,闲的没事,我就看 lhx 神仙打游戏,就这么度过了整个下午............
吃完晚饭(不得不说这个酒店的晚饭特别丰盛)之后,去试机。我们全都集合到楼下,还是坐大巴车去,和 lsj 神仙(@lsj123)坐在一起,和这位神仙聊了一会,就到了。
教练先说了明天考完之后在哪里集合,然后就进了楼里。在门口还拍照留念了一下。进去之后找了半天自己对应的机房,总算是找到了,结果发现还要过一会才能进去,就和在一起的大佬闲聊了一会。进去之后,敲了敲板子,感觉键盘打字似乎不太方便,内存很豪华,16GB。
试机完之后回来,复习了一些算法(复习的这些算法一个都没考到),lhx 神仙还教了 lsj 一波 dijkstra。
考前想着,按照之前的经验,全暴力即可 pj 1=,所以我采用了保守策略

Day1:

酒店门口集合,出发。 开考前打了一会板子,然而没用上。
先开 T1。
推了 10 分钟左右,并没有发现什么规律。
决定弃疗写暴力,看 $10^9$ 的数据范围,看着或许能放暴力过就写了。
看了一会 T2,看要排序,我本来打算直接快排,但是脑子抽了,不知道为什么感觉快排可能不对。
然后我就干脆小数据 $O(n^2)$,大数据快排。
然后写完一次过了大样例。
看了会 T3,一看应该就是之前打过的正睿模拟赛原题。
当时我拿了 50pts,这次却挂了。
狂写了 200行+,代码变得面目全非,彻底调不出来了。
看考试即将结束,我一下子慌了。
这是 T3,T4 都是 0 分。
我决定立刻扔掉 T3,以最快速度刚 T4。
先是想了个链表做法,但是时间不太够用了,而暴力做法在随机数据下几乎是 $O(n)$ 的,就迅速写了个暴力。
为了存储方便就用了 vector 套 vector
写完之后改了一些 sb 错,就过了大样例。
我认为 CCF 数据应该不是很强,就估计 70+50+0+100=220pts。
过完大样例,又检查了会文件名,结束。
出场的时候 very exciting,还和一个非同校且我不认识的聊了一会。
然后就是去集合,发现除了我几乎所有人都过了 T1。
然后我说了我的 T4,然后别人告诉我,你这是 30 分的,这时我忽然发现,我这个垃圾暴力,非常好卡,非常好卡。
然后我还听 zgc 说 T3 水题,我人傻了。
回酒店,又听 lhx 说他估分 100+70+100+30=300 分。
我越来越慌,因为周围人都比我高。
而我的估分目前只有 70+50+0+30=150。
到了车上,教练:“今年 J 组非常简单,分数线不会低于 200,当然 300 就有点夸张了”
听到这个真心态崩了。
只能寄希望于 T4。
晚上凭记忆复原考场代码,出乎意料地得了 100+50+0+70=220 分。

第二天发了现场代码。

我非常紧张地搜我的考号,紧张地打开。

提交 T1,通过民间数据。

然后看赛时 T2,发现判断数据范围的地方写错了。

不管了提交,居然还有 50 分。

提交 T3,果然是 0。

最后一题了,也是最紧张的,提交 T4。

看着 waiting 状态,心跳不停地加速。

接下来的一幕,让我瘫在了椅子上:

编译信息:  
编译失败  
\/tmp\/compiler_eubcvv9u\/src:11:1: 错误:‘vector’ does not name a type; did you mean ‘perror’?
 vector< vector<int> > hruvri;
 ^~~~~~
 perror
\/tmp\/compiler_eubcvv9u\/src: 在函数‘int main()’中:
\/tmp\/compiler_eubcvv9u\/src:29:3: 错误:‘vector’在此作用域中尚未声明
   vector<int> tmp;
   ^~~~~~
\/tmp\/compiler_eubcvv9u\/src:29:3: 附注:‘std::vector’ is defined in header ‘<vector>’; did you forget to ‘#include <vector>’?
\/tmp\/compiler_eubcvv9u\/src:7:1:
+#include <vector>
 using namespace std;
\/tmp\/compiler_eubcvv9u\/src:29:3:  
...  

我一时以为是交错代码了,检查了下考号,确实是我的。

我又以为是提交的时候不小心多打入了字符。
我一时不相信我的赛时 T4 CE 了,重新提交了很多遍。

然后我修好了 CE,看能多少分,结果 AC 了。
因为我从来没有比赛 CE 的历史,我甚至以为有人赛后改我代码,我就去看了一下我的代码修改时间,一看,是在比赛结束 0.5h 之后,但我看了别人的,也是差不多的时间。
我又不甘心的在 NOI Linux 虚拟机上编译了一遍,-std=c++14 CE 了,而不加 -std=c++14 能过编译。而今年是首次加上 -std=c++14。
我又下载了与考场编辑器同款的 dev5.1.0,发现这个版本的 dev 自带头文件补全。

打电话问了教练能不能申诉,教练说没法申诉。

自认倒霉了。

我现场把 luogu id 改成了 __vector__,并打算下一年如果 CSP-S/NOIP 拿了省一,就改回原名 liumonong

等来了官方成绩,90 + 36 + 0 + 0 = 126。

T1 最终没有放过,T2 数据范围判错,T4 最终还是没分。

挂了一车分,没挂分就有 1=。

后话:

第二年提高组没考,但拿了 Noip 省一,但是用户名不打算改回去了。
后来我的所有 CF 账号也全改成了 vector 系列。
某些网站个人介绍干脆自称 vector 了。

共 320 篇博客