Atcoder ABC228题解

ABC228 A - On and Off

一个人每天在 $ S $ 时开灯, $ T $ 时关灯,求在 $ X $ 时过 $ 30 $ 分钟后等是否时开着的。

$ 0\le S,T,X\le 23 $

$ S\ne T $


分析:

本题主要难点在于 $ T $ 可能是小于 $ S $ 的。

那么当 $ T<S $ 时我们令 $ T\gets T+24 $ ,这样就可以保证 $ S<T $ 了。

但是这个时候就会遇到一个问题:如果原来有 $ X<T<S $ ,这种方案显然是合法的。

在操作完之后就会有 $ X<S<T $ ,我们的程序不能正确判断。

那么如果这个时候有 $ X<S $ ,我们再令 $ X\gets X+24 $ 。

最后只需要看是否有 $ S\le X<T $ 就好了。

注意题目中问的是 $ X $ 时过 $ 30 $ 分钟灯是否开启,所以 $ X=T $ 是不行的。

代码:

#include<iostream>
using namespace std;
int s,t,x;
signed main()
{
    cin>>s>>t>>x;
    if(t<s) t+=24;
    if(x<s) x+=24;
    if(s<=x&&x<t) printf("Yes\n");
    else printf("No\n");
    return 0;
}

ABC228 B - Takahashi's Secret

高桥有 $ N $ 个朋友,被称为 Friend $ 1 $ ,Friend $ 2\dots $ ,Firend $ N $。

有一天,高桥不小心人让 Friend $ X $ 知道了他的可耻的秘密。

如果第 $ i $ 个朋友知道了高桥的秘密,那么他就会把这个秘密告诉第 $ A_i $ 个朋友(如果第 $ A_i $ 个朋友还不知道这个秘密的话)。

求最后有多少人会知道高桥的秘密。

$ 2\le N\le10^5 $

$ 2\le X\le N $

$ 1\le A_i\le N $

$ A_i\ne i $


分析:

直接按照题意,模拟每个朋友知道这个秘密的顺序。

最后一遍模拟一遍计算答案即可。

因为每个朋友最多知道这个秘密一次,所以时间复杂度为 $ O(n) $ 。

代码:

#include<iostream>
using namespace std;
bool vis[100001];
int a[100001];
int n,k;
int ans;
signed main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=k;;i=a[i])
    {
        if(vis[i]) break;
        vis[i]=1;ans++;
    }
    printf("%d\n",ans);
    return 0;
}

ABC228 C - Final Day

现在有 $ N $ 名学生正在参加为期 $ 4 $ 天的考试。

每天考试的满分均为 $ 300 $ 分, $ 4 $ 天的总共的分数为 $ 1200 $分。

目前前三天的考试已经完成了,第 $ i(1\le i\le N) $ 个学生在第 $ j(1\le j\le 3) $ 的考试中拿到了 $ P_{i,j} $ 分。

对于每个学生,请确定他是否有可能在第四天考试后排名在前 $ K $ 。

注:第四天之后的学生排名定义为四天总成绩高于该学生的学生人数再加1。

$ 1\le K\le N\le 10^5 $

$ 0\le P_{i,j}\le 300(1\le i\le N,1\le j\le 3) $


显然,当其他学生第四天考试成绩一直的情况下,某个学生第四天成绩越好,排名就越靠前。

所以当某个学生第 $ 4 $ 天考了 $ 300 $ 分,其他所有学生都考了 $ 0 $ 分的时候,这个学生能取得自己最高的排名。

对于每一个学生,我们只需要检查这个排名与 $ K $ 之间的大小关系就可以了。

排序即可,时间复杂度为 $ O(n\log n) $ 。

代码:

#include<algorithm>
#include<iostream>
using namespace std;
int num[100001];
int a[100001];
int n,k;
signed main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        a[i]=num[i]=x+y+z;
    }
    sort(num+1,num+n+1);
    for(int i=1;i<=n;i++)
    {
        if(a[i]+300>=num[n-k+1]) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

ABC228 D - Linear Probing

有一个序列 $ A=(A_0,A_1,\dots,A_{N-1}),N=2^{20} $ ,并且所有数字一开始都为 $ -1 $ 。

现在有 $ Q $ 个查询,每个查询由两个数字 $ t_i,x_i $ 组成。

当 $ t_i=1 $ 时,找到 $ \bmod N $ 意义下大于等于 $ x_i $ 的第一个不等于 $ -1 $ 的数字,并将这个数字改成 $ x_i $。

当 $ t_i=2 $ 时,查询 $ A_{x_i\bmod N} $ 的值。

$ 1\le Q\le 2\times 10^5 $

\(t_i\in\{1,2\}(1\le i\le N)\)

$ 1\le x_i\le 10^{18}(1\le i\le Q) $

存在至少一个 $ i $ 使得 $ t_i=2 $ 。


由于每一次只会更改一个地方的值,我们考虑维护这个序列。

现在的目标是快速找到某一个位置以后第一个为 $ -1 $ 的地方。

我们可以使用一个并查集,在初始的时候并查集每一个元素都指向自己。

在我们更改一个位置的值以后,将这个位置向它下一个位置合并过去。

这样子每一次从一个地方开始find()到的元素就是题目中要找的位置了。

查询的时候直接输出就可以了。

时间复杂度 $ O(n+\alpha(n)Q) $ 。

代码:

#include<iostream>
using namespace std;
using ll=long long;
int fa[1048576];
ll a[1048576];
int n=1048576;
int q;
int find(int num)
{
    if(fa[num]==num) return num;
    return fa[num]=find(fa[num]);
}
signed main()
{
    for(int i=0;i<n;i++) fa[i]=i,a[i]=-1;
    cin>>q;
    for(int i=1;i<=q;i++)
    {
        int op;ll x;
        cin>>op>>x;
        if(op==1)
        {
            int val=find(x%n);
            a[val]=x;
            fa[val]=fa[val+1];
        }
        else
            printf("%lld\n",a[x%n]);
    }
    return 0;
}

ABC228 E - Integer Sequence Fair

现在有许多个长度为 $ N $ 的整数序列,每个序列中的数字都在 $ [1,K] $ 内。

你现在要给每一个整数序列打分,分数是一个属于 $ [1,M] $ 的整数。

请问共有多少种不同的打分方案。(当有任何一个序列在两种方案中得到的分数不同时,这两个方案不同)

$ 1\le N,K,M\le 10^{18} $


分析:显然,不同的序列共有 $ K^N $ 种,而分数也有 $ M $ 种取值。

所以答案为 $ M^ {K^N} $ ,使用欧拉定理直接算就可以 。

注:指数向 $ 998244352 $ 取模,每一次快速幂前记得先将底数取模。

时间复杂度 $ O(\log mod) $ 。

代码:

#include<iostream>
using namespace std;
using ll=long long;
ll qow(ll a,ll b,int mod)
{
    if(a==0) return 0;
    ll ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
const int mod=998244353;
ll n,k,m;
signed main()
{
    cin>>n>>k>>m;k%=(mod-1),m%=mod;
    ll num=qow(k,n,mod-1);
    printf("%lld\n",qow(m,num,mod));
    return 0;
}

ABC228 F - Stamp Game

现在有一个 $ H\times W $ 的方格,每个方格上都有一个数字。

从上面数第 $ i $ 行从左数第 $ j $ 列的格子上面的数字为 $ A_{i,j} $ 。

一开始的时候这些格子都是白色的。

现在又两个人,他们要进行一下操作:

  1. 第一个人选一个高为 $ h_1 $ ,宽为 $ w_1 $ 的矩阵,并将这里的所有数字都涂黑。

  2. 第二个人在第一个人涂完后,选择一个高为 $ h_2 $ ,宽为 $ w_2 $ 的矩阵,并将这里的所有数字涂白。

如果一个格子先被涂黑,再被涂白,那么这个格子最终是白色的。(意思就是每个格子最后的颜色取决于最后一次涂上了什么色,如果没有涂色那它就是白的)

第一个人想要让黑色的方格里数字之和最大化,第二个人想要让黑色的方格里数字之和最小化。

求最后这个黑色方格里面的数字之和是多少。

$ 1\le H,W\le 1000 $

$ 1\le h_1,h_2 \le H $

$ 1\le w_1,w_2 \le W $

$ 1\le A_{i,j}\le 10^9 $


分析:首先我们可以令 $ h_2\gets \min(h_1,h_2),w_2\gets \min(w_1,w_2) $ 。

因为第二个人知道第一个人将哪些格子涂黑了,所以当 $ h_2>h_1 $ 或 $ w_2>w_1 $ 时,多出来的这部分实际上是没有意义的。

接下来我们不妨来枚举一下第一个人选的方格是哪一个。

这一步显然是 $ HW $ 的。

我们可以提前预处理出前缀和,这样我们就可以快速求得第一个人选择的方块中的数字之和。

那么如果第一个人已经确定了要选这个矩阵,第二个人会选哪一个矩阵呢?

用脑子稍微想一下你会发现,他选的一定是和最大的那一个。

这不就好办了。

我们提前预处理出第二个人选每一个矩阵能够减少的贡献是多少。

然后枚举第一个人选的是哪一个矩阵,然后利用某些东西求出第二个人选的是哪一个矩阵,最后答案取最大值,不就可以了吗?

那我们用什么东西来维护第二个人选择哪个矩阵呢?

我们其实需要一个可以快速得到某一个二位区间(就是一个矩阵)中数字的最小值的东西。

直接二维线段树/二维st表搞定。

时间复杂度为 $ O(HW\log H\log W) $ 。

代码里用的是二维线段树。

代码:

#include<algorithm>
#include<iostream>
using namespace std;
using ll=long long;
ll num[4001][4001];
ll pre[1001][1001];
int a[1001][1001];
ll b[1001][1001];
int x1,y1,x2,y2;
int n,m;
ll ans;
void build(int p,int pl,int pr,int v,int vl,int vr)
{
    if(vl==vr)
    {
        if(pl==pr) num[p][v]=b[pl][vl];
        else num[p][v]=max(num[p*2][v],num[p*2|1][v]);
        return ;
    }
    int mid=(vl+vr)>>1;
    build(p,pl,pr,v*2,vl,mid);
    build(p,pl,pr,v*2|1,mid+1,vr);
    num[p][v]=max(num[p][v*2],num[p][v*2|1]);
}
void build(int p,int pl,int pr)
{
    if(pl!=pr)
    {
        int mid=(pl+pr)>>1;
        build(p*2,pl,mid);
        build(p*2|1,mid+1,pr);
    }
    build(p,pl,pr,1,1,m);
}
ll get(int p,int v,int vl,int vr,int l,int r)
{
    if(l<=vl&&vr<=r) return num[p][v];
    int mid=(vl+vr)>>1;ll ans=0;
    if(l<=mid) ans=max(ans,get(p,v*2,vl,mid,l,r));
    if(mid<r) ans=max(ans,get(p,v*2|1,mid+1,vr,l,r));
    return ans;
}
ll get(int p,int pl,int pr,int u,int d,int l,int r)
{
    if(u<=pl&&pr<=d) return get(p,1,1,m,l,r);
    int mid=(pl+pr)>>1;ll ans=0;
    if(u<=mid) ans=max(ans,get(p*2,pl,mid,u,d,l,r));
    if(mid<d) ans=max(ans,get(p*2|1,mid+1,pr,u,d,l,r));
    return ans;
}
int main()
{
    cin>>n>>m>>x1>>y1>>x2>>y2;
    x2=min(x2,x1),y2=min(y2,y1);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
    {
        cin>>a[i][j];
        pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j];
    }
    for(int i=x2;i<=n;i++) for(int j=y2;j<=m;j++)
        b[i][j]=pre[i][j]-pre[i-x2][j]-pre[i][j-y2]+pre[i-x2][j-y2];
    build(1,1,n);
    for(int i=x1;i<=n;i++) for(int j=y1;j<=m;j++)
    {
        ll num=pre[i][j]-pre[i-x1][j]-pre[i][j-y1]+pre[i-x1][j-y1];
        num-=get(1,1,n,i-x1+x2,i,j-y1+y2,j);
        ans=max(ans,num);
    }
    printf("%lld\n",ans);
    return 0;
}

ABC228 G - Digits on Grid

现在有一个 $ H\times W $ 的方格,每一个方格上都有一个在 $ 1 $ 到 $ 9 $ 之间的整数。从上数第 $ i $ 行左数第 $ j $ 列的方格上写的数字为 $ c_{i,j} $ 。

一开始的时候有一个棋子在这个网格上,并且还有一个数字 $ X $ 。$ X $ 初始为 $ 0 $ 。

你现在要做以下操作 $ N $ 次。

  1. 将这个棋子移到同行的任何一个格子上(可以不移)。

  2. 将现在棋子所在方格上面的数字添加在 $ X $ 的末尾。

  3. 将这个棋子移到同列的任何一个格子上(可以不移)。

  4. 将现在棋子所在方格上面的数字添加在 $ X $ 的末尾。

这个棋子一开始可以在任何一个位置,求最后 $ X $ 可以变成多少个不同的数字,对 $ 998244353 $ 取模。

注:如果有多种方法使得 $ X $ 变为一个数字,只算一次。

$ 1\le H,W \le 10 $

$ 1\le N\le 300 $

$ 1\le c_{i,j}\le 9 $


分析:

不得不说,这是一道神仙题。

首先我们将题意进行转化:

现在有一个二分图,左边有 $ H $ 个点,右边有 $ W $ 个点。

左边的每一个点与右边的每一个点都有连边,其中左边的第 $ i $ 个点与右边的第 $ j $ 个点之间的边有边权 $ c_{i,j} $ 。

现在有一个棋子在左边的任何一个点上,现在棋子要在这个图上沿着走 $ 2N $ 步,每走一步就要将走过边上的边权加到 $ X $ 的末尾,求 $ X $ 最后有多少可能的取值。

不难发现,这个问题和刚刚问题的答案是一样的。

再联系到 $ 10 $ 的数据范围,我们不难想到状压dp。

我们不妨先令 $ dp_{sta,i} $ 表示走完 $ i $ 步以后,可以由 $ sta $ 中的点结尾走出的字符串的个数。

然后你就会发现这种玩意会算重。

那么我们能不能去一下重呢?

于是乎,就有一个神奇的状压dp出现了:

令 $ dp_{sta,i} $ 表示当前已经走了 $ i $ 步,能且仅能在以 $ sta $ 表示的节点作为当前节点表示出的字符串有多少。

举个例子,假如现在已经走过了 $ 3 $ 步,现在有 $ 34,54,64 $ 一共 $ 3 $ 个 $ X $ 可以且仅可以以第 $ 1 $ 个点为最后一个点走出,那么就有 $ dp_{sta_1,3}=3 $ 。

最后对 $ i $ 的奇偶性讨论进行转移即可。

时间复杂度 $ O(N(2^ H+2^W)) $ 。

代码:

#include<iostream>
using namespace std;
using ll=long long;
const int mod=998244353;
bool num[20][20][10];
ll dp[601][1024];
int val[20][10];
char in[11][11];
int m,n,k;
ll ans;
int main()
{
    cin>>m>>n>>k;
    for(int i=0;i<m;i++)
    {
        cin>>in[i];
        for(int j=0;j<n;j++)
        {
            val[i][in[i][j]-'0']|=(1<<j);
            val[j+m][in[i][j]-'0']|=(1<<i);
        }
    }
    dp[0][(1<<m)-1]=1;
    for(int i=0;i<k*2;i++)
    {
        if(i&1)
        {
            for(int fr=1;fr<(1<<n);fr++)
                for(int w=1;w<=9;w++)
                {
                    int can=0;
                    for(int j=0;j<n;j++) if(fr&(1<<j)) can|=val[j+m][w];
                    dp[i+1][can]=(dp[i+1][can]+dp[i][fr])%mod;
                }
        }
        else
        {
            for(int fr=1;fr<(1<<m);fr++)
                for(int w=1;w<=9;w++)
                {
                    int can=0;
                    for(int j=0;j<m;j++) if(fr&(1<<j)) can|=val[j][w];
                    dp[i+1][can]=(dp[i+1][can]+dp[i][fr])%mod;
                }
        }
    }
    for(int i=1;i<(1<<max(m,n));i++) ans=(ans+dp[k*2][i])%mod;
    printf("%lld\n",ans);
    return 0;
}

ABC228 H - Histogram

现在有两个长度为 $ N $ 的序列 $ A=(A_1,A_2,\dots,A_N) $ 和 $ C=(C_1,C_2,\dots,C_N) $ 。

你现在可以进行以下操作任意多次(不进行也可以):

  1. 选择一个 $ i $ 使得 $ 1\le i\le N $ 。

  2. 花费 $ C_i $ 的代价使得 $ A_i\gets A_i+1 $ 。

最终的序列也有一个成本为 $ XK $ 。

其中 $ X $ 为输入中给定的数据, $ K $ 为最后 $ A $ 中不同的数字个数。

问最小的代价是多少。

代价为进行操作的代价加上最后序列的成本。

$ 1\le N\le 2\times 10^5 $

$ 1\le X\le 10^6 $

$ 1\le A_i,C_i\le 10^6(1\le i\le N) $


分析:

不得不说,这还是一道神仙题。

首先我们将这两个序列进行排序,使得 $ A $ 序列单调不降。

为了方便,我们令 $ A' $ 表示所有操作结束后 $ A $ 的值。

因为我们已经将 $ A $ 从小到大进行排序,所以会有:

A'单调不降

为什么呢?

假设存在两个下标 $ i,j $ 满足 $ i<j,A'_i>A'_j $ 。

因为我们之前排了序,所以还会有 $ A_i<A_j $

那么这种情况显然不是最优的。

如果我们令 $ A'_j\gets A'_i $ ,那么操作的代价比原来小,最后序列的成本不比原来大,那么这种情况一定更优。

所以这是正确的。

那么一定就会存在两个序列 $ P,Q $ 使得:

$ 0=P_0<P_1<P_2<\dots<P_K=N $

$ 1\le Q_1<Q_2<\dots <Q_K $

$ \forall P_{k-1}<i\le P_k:A'_i=Q_k $

这个也很好理解,因为 $ A' $ 不降,所以就会分成若干段,使得每一段内值都相同。

然后呢?

然后dp呀。

我们设 $ dp_i $ 为前 $ i $ 个元素的答案。

那么 $ A' $ 中最后的一段的值肯定都为 $ A_i $ ,我们枚举上一段的末尾位置 $ j $ 。

就会有 $ dp_i=\min\limits_{j=0}^ {i-1}(dp_j+X+\sum\limits_{k=j+1}^i(A_i-A_k)C_k) $ 。

然后你就可以快乐的 $ O(n^2) $ 计算。

然后 $ 1 $ 分都拿不上。

考虑优化转移。

令 $ prec_i=\sum\limits_{j=1}^ iC_j,preac_i=\sum\limits_{j=1}^iA_jC_j $ 。

假设 $ dp_i $ 由 $ dp_j $ 转移而来,那么就会有:

\[\begin{aligned} dp_i&=dp_j+X+\sum_{k=j+1}^i(A_i-A_k)C_k\\ dp_i&=dp_j+X+A_i(prec_i-prec_j)-(preac_i-preac_j)\\ dp_i&=dp_j+X+A_iprec_i-A_iprec_j-preac_i+preac_j\\ dp_i-X-A_iprec_i+preac_i&=dp_j+preac_j-A_iprec_j \end{aligned} \]

有没有发现哪里不对?

这个玩意可以斜率优化!

令 $ b_i=dp_i-X-A_iprec_i+preac_i,y_i=dp_i+preac_i,k_i=A_i,x_i=prec_i $

那么就有 $ b_i=y_j-k_ix_j $ 。

直接一个二分+斜优就可以了。

时间复杂度 $ O(n\log n) $ 。

注:事实上那个 $ A_iC_i $ 每一项都有,可以提到外面算。

代码:

#include<algorithm>
#include<iostream>
using namespace std;
using ll=long long;
struct node
{
    int a,c;
}in[200001];
int que[200001];
ll pre[200001];
ll dp[200001];
int head,tail;
int n,x;
int run(int k)
{
    int ans=head;
    for(int i=131072;i;i>>=1)
        if(ans+i<=tail)
        {
            int x=que[ans+i-1],y=que[ans+i];
            if((dp[y]-dp[x])<k*(pre[y]-pre[x])) ans+=i;
        }
    return que[ans];
}
int main()
{
    cin>>n>>x;head=tail=1;
    for(int i=1;i<=n;i++) cin>>in[i].a>>in[i].c;
    sort(in+1,in+n+1,[](node a,node b){ return a.a<b.a;});
    for(int i=1;i<=n;i++) pre[i]=in[i].c+pre[i-1];
    for(int i=1;i<=n;i++)
    {
        int j=run(in[i].a);
        dp[i]=dp[j]+(pre[i]-pre[j])*in[i].a+x;
        while(tail>head)
        {
            int x=que[tail-1],y=que[tail];
            if((__int128)(dp[y]-dp[x])*(pre[i]-pre[y])<(__int128)(dp[i]-dp[y])*(pre[y]-pre[x])) break;
            tail--;
        }
        que[++tail]=i;
    }
    for(int i=1;i<=n;i++) dp[n]-=(ll)in[i].a*in[i].c;
    printf("%lld\n",dp[n]);
    return 0;
}

完结撒花!

posted @ 2022-06-27 21:09  Wuyanru  阅读(190)  评论(1编辑  收藏  举报