AtCoder Beginner Contest 406

AtCoder Beginner Contest 406

A - Not Acceptable

A点B分有个DDL,C点D分提交。问是否在DDL之前完成的提交。

先比较小时,如果小时一致再比较分钟。

#include<iostream>
#include<cstdio>
using namespace std;
int A,B,C,D;
int main()
{
    cin>>A>>B>>C>>D;
    if(C<A||(C==A&&D<B))
        puts("Yes");
    else puts("No");
    return 0;
}

B - Product Calculator

有一个计算器一开始显示1

接下来执行了N个操作,在第\(i\)个操作,他会给当前显示的数乘上\(A_i\)

然而计算器最多显示K位,一旦结果超过了K位,就会变成\(1\)

问最终显示的数是多少。

首先数据范围用longlong可以存,但是不能直接做乘法。我们用sk表示k位9

我们实际要判断的是\(a\times b>sk\),那么处理一下可以变成\(a>sk/b\),这样可以很轻松的判断是否会乘爆了。

#include<iostream>
#include<cstdio>
using namespace std;
int N,K;
long long disp, maxk, A;
int n,k;
int main()
{
    cin>>n>>k;
    disp=1;
    for(int i=1;i<=k;++i)maxk=maxk*10+9;
    for(int i=1;i<=n;++i)
    {
        cin>>A;
        if(maxk/disp>=A)disp*=A;
        else disp=1;
    }
    cout<<disp<<endl;
    return 0;
}

C - ~

称一个序列是波浪的,当且仅当:

  • 长度至少为4
  • \(A_1<A_2\)
  • 存在一个\(2\le i<|A|\),满足\(A_{i-1}<A_i>A_{i+1}\)
  • 存在一个\(2\le i<|A|\),满足\(A_{i-1}>A_i<A_{i+1}\)

给定一个\(1-n\)的排列,问有多少个连续的子段是波浪的。

因为只存在两个特殊的位置,意味着其他的都不符合大于两侧或者小于两侧,所以他们只能是单增或者单减

两个特殊位置则是两个特殊拐角,因此子段的形状只能是 单增-单减-单增 三段(其实题目名字已经告诉你了)

按照单增和单减合并段,枚举所有单减段然后考虑两侧的程度即可。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int MAX = 300300;
int n,P[MAX];
long long ans=0;
vector<int> pos1,pos2;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&P[i]);
    vector<pair<char,int> > v;
    for(int i=1;i<n;++i)
        if(P[i]<P[i+1])
        {
            if(v.empty() || v.back().first == '>')
                v.push_back(make_pair('<',1));
            else v.back().second++;
        }
        else
        {
            if(v.empty() || v.back().first == '<')
                v.push_back(make_pair('>',1));
            else v.back().second++;
        }
    int size=v.size();
    for(int i=1;i<size-1;++i)
        if(v[i].first=='>')
            ans+=1ll*v[i-1].second*v[i+1].second;
    printf("%lld\n",ans);
    return 0;
}

D - Garbage Removal

有一个H行W列的网格,网格图中有\(N\)个垃圾,第\(i\)个位于\((X_i,Y_i)\)

现在有\(Q\)个询问,需要依次处理:

  • Type1:给出1 x的形式,回答第\(x\)行的垃圾数目,然后将他们移除
  • Type2:给出2 y的形式,回答第\(y\)列的垃圾数目,然后将他们移除

我们只需要分行/列维护每个行/列中所有的垃圾以及他们的位置

每次删除的时候首先输出这一行/列的大小,然后在对应的行和对应的列把它们删除即可

我们需要支持一个如下操作的数据结构:

  • 插入元素
  • 获取元素个数
  • 支持快速删除特定元素

使用STL中的set即可实现。

#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
const int MAX = 200200;
int n,Q,H,W;
set<int> X[MAX],Y[MAX];
int main()
{
    scanf("%d%d%d",&H,&W,&n);
    for(int i=1;i<=n;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        X[x].insert(y);
        Y[y].insert(x);
    }
    scanf("%d",&Q);
    while(Q--)
    {
        int t,x;
        scanf("%d%d",&t,&x);
        if(t==1)
        {
            printf("%d\n",X[x].size());
            for(auto a:X[x])Y[a].erase(x);
            X[x].clear();
        }
        else
        {
            printf("%d\n",Y[x].size());
            for(auto a:Y[x])X[a].erase(x);
            Y[x].clear();
        }
    }
    return 0;
}

E - Popcount Sum 3

给定\(N\)\(K\),找到所有不超过\(N\)的数中,二进制下恰好有\(K\)\(1\)的所有数之和,输出模\(9982244353\)的结果

很明显的数位\(dp\)

\(f[i][j][0/1]\)表示当前考虑到了\(2^i\)位,已经有\(j\)位是\(1\),是否已经严格比\(N\)小,的数的个数

\(g[i][j][0/1]\)表示当前考虑到了\(2^i\)位,已经有\(j\)位是\(1\),是否已经严格比\(N\)小,的数的已确定的数位的和

每次枚举这一位填\(0\)还是填\(1\),进行转移即可。

转移参考代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
const int MOD=998244353;
ll N,K; 
int f[100][100][2];
int g[100][100][2];
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        cin>>N>>K;
        f[60][0][0]=1;
        for(int i=59;i>=0;--i)
        {
            if(N&(1ll<<i))
                for(int j=0;j<=K;++j)
                {
                    add(f[i][j][1],f[i+1][j][0]);
                    add(g[i][j][1],g[i+1][j][0]);

                    add(f[i][j][1],f[i+1][j][1]);
                    add(g[i][j][1],g[i+1][j][1]);

                    add(f[i][j+1][0],f[i+1][j][0]);
                    add(g[i][j+1][0],g[i+1][j][0]);
                    add(g[i][j+1][0],(1ll<<i)*f[i+1][j][0]%MOD);

                    add(f[i][j+1][1],f[i+1][j][1]);
                    add(g[i][j+1][1],g[i+1][j][1]);
                    add(g[i][j+1][1],(1ll<<i)*f[i+1][j][1]%MOD);
                }
            else
                for(int j=0;j<=K;++j)
                {
                    add(f[i][j][0],f[i+1][j][0]);
                    add(g[i][j][0],g[i+1][j][0]);
                    
                    add(f[i][j][1],f[i+1][j][1]);
                    add(g[i][j][1],g[i+1][j][1]);  

                    add(f[i][j+1][1],f[i+1][j][1]);
                    add(g[i][j+1][1],g[i+1][j][1]);
                    add(g[i][j+1][1],(1ll<<i)*f[i+1][j][1]%MOD);
                }
        }
        int ans=(g[0][K][0]+g[0][K][1])%MOD;
        cout<<ans<<endl;
    }
    return 0;
}

F - Compare Tree Weights

有一棵树,有\(N\)个节点,每个节点有一个重量,初始每个节点的重量都是\(1\)

给出\(Q\)个询问,依次处理:

  • 1 x w:给节点\(x\)的重量加上\(w\)
  • 2 y:把第\(y\)条边断开,树会变成两部分,回答这两部分的重量和的差

以任意一个点作为根节点,断开一条边,其中一部分一定是一棵完整的子树,而完整子树在dfs序上是连续的

那么我们只需要知道所有节点的重量和,以及快速求一段dfs序上的重量和即可。

所有节点的重量和全局维护一下就行了。

快速求一段区间和用树状数组可以很容易的解决。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
const int MAX=300300;
vector<int> E[MAX];
int x[MAX],y[MAX];
int dfn[MAX],low[MAX],f[MAX],cnt;
int n;
ll t[MAX],totw;
int lowbit(int x){return x&(-x);}
void add(int x,int y){while(x<=n)t[x]+=y,x+=lowbit(x);}
ll sum(int x){ll s=0;while(x>0)s+=t[x],x-=lowbit(x);return s;}
void dfs(int u,int fa)
{
    f[u]=fa;dfn[u]=low[u]=++cnt;
    for(auto v:E[u])
    {
        if(v==fa)continue;
        dfs(v,u);
        low[u]=max(low[u],low[v]);
    }
}
int main()
{
    n=read();
    for(int i=1;i<n;++i)
    {
        x[i]=read();
        y[i]=read();
        E[x[i]].push_back(y[i]);
        E[y[i]].push_back(x[i]);
    }
    dfs(1,0);
    for(int i=1;i<=n;++i)add(dfn[i],1);
    totw=n;
    int Q=read();
    while(Q--)
    {
        int op=read();
        if(op==1)
        {
            int x=read(),w=read();
            totw+=w;
            add(dfn[x],w);
        }
        else
        {
            int i=read();
            if(f[x[i]]!=y[i])swap(x[i],y[i]);
            int l=dfn[x[i]],r=low[x[i]];
            ll sub=sum(r)-sum(l-1);
            ll ans=abs(totw-sub-sub);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

G - Travelling Salesman Problem

\(N\)个商人在坐标轴上,第\(i\)个商人位于\(X_i\),销售物品\(i\)

一开始你在位置\(0\)上,需要依次购买物品\(1,2,...,N\)

有三种操作:

  • 你自己移动一个单位,花费\(C\)
  • 让一个商人移动一个单位,花费\(D\)
  • 如果你和一个商人位于坐标轴上同一个位置,那么可以从他手中购买物品,花费\(0\)

回答最小花费,并输出一个位置序列,第\(i\)个数表示你购买物品\(i\)时所在的位置。

考虑一个暴力\(dp\),设\(f[i][j]\)表示当前已经购买了\(i\)个物品,购买完之后你在的位置为\(j\)

显而易见的,在购买第\(i-1\)个物品之前,你不需要让第\(i\)个商人移动,所以每次你在考虑购买第\(i\)个物品时,只需要考虑你自己和第\(i\)个商人的位置。

因此初值\(f[0][0]=0\)

\(f[i][j]=\min_k\{f[i-1][k]+abs(k-j)*C+abs(X_i-j)*D\}\)

假设函数\(f_i(j)=f[i][j]\),设\(g_i(j)=\min\{f_{i}(k)+C|k-j|\}\)

于是\(f_{i+1}(j)=g_i(j)+D|X_i-j|\)

注意到\(f\)\(g\)的形式都是类似于每次增加上一个线段。

而线段的端点数量是\(O(n)\)级别的,因此我们可以用map暴力维护这些线段变化的位置。当然,也可以使用线段树来进行维护。

假设我们已经求出了\(f_i\),那么\(f_i\)一定可以写成左端点值+若干条线段斜率的形式。

考虑\(g_i\)

对于靠数轴负半轴的部分:

如果\(f_i(x)\)\(f_i(x-1)\)之间的斜率的绝对值超过了\(C\),那么显然他们不如向中心移动一位(因为向中心移动一位的斜率只有\(C\),相比之下是更优的)

直到某个位置斜率绝对值小于\(C\),此时之前的位置显然都用当前的位置去进行更新,而之后的位置由于\(f\)的斜率都小于\(C\),因此想其他方向移动不如由自己的\(f\)值更新过来。

对于靠数轴正半轴的部分是类似的。

这样子我们可以通过\(f\)直接更新出\(g\)的值。而在此基础上加上两条线段即可得到新一轮\(f\)的值。

重复此过程,我们就可以得到最终的\(dp\)值。

posted @ 2025-05-23 17:51  小蒟蒻yyb  阅读(107)  评论(0)    收藏  举报