Codeforces Round #587 (Div. 3) 题解

算法竞赛 比赛-Codeforces
编辑文章

小号rating比大号高惨案

wzxtxdy

wzx真是太强辣,除了我还有人膜它,而且还AK了。

wzxakioi

A.Prefixes

题意

给出一个长度 $n(n\le 2\times 10^5)$ 的 $a/b$ 串,要求对于所有的 $k\le \dfrac{n}{2}$ ,$[1,2k]$ 中 $a$ 和 $b$ 个数相等。问至少要修改几个数。

题解

水题,判断下每相邻两个数是否相等即可。

#include<bits/stdc++.h>

using namespace std;

inline int read()
{
    char ch=getchar(); int f=1,x=0;
    while (ch<'0' || ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0' && ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return f*x;
}

int n;
char s[200005];

signed main()
{
    n=read();
    int ans=0;
    scanf("%s",s+1);
    for (int i=2;i<=n;i+=2)
    {
        if (s[i]==s[i-1] && s[i]=='a') s[i]='b',ans++;
        else if (s[i]==s[i-1]) s[i]='a',ans++;
    }
    return !printf("%d\n%s",ans,s+1);
}

B.Shooting

题意

要求射击所有 $n(n\le 1000)$ 个罐子,第 $x$ 次射击第 $i$ 个罐子的代价为 $a_i\times (x-1)+1$ ,问最小代价。

题解

贪心,先射 $a_i$ 大的即可。

#include<bits/stdc++.h>

using namespace std;

inline int read()
{
    char ch=getchar(); int f=1,x=0;
    while (ch<'0' || ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0' && ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return f*x;
}

int n;
struct node {
    int s,id;
} s[1005];

signed main()
{
    n=read();
    for (int i=1;i<=n;i++) s[i].s=read(),s[i].id=i;
    sort(s+1,s+n+1,[](node x,node y){ return x.s>y.s; });
    int ans=0,cnt=0;
    for (int i=1;i<=n;i++) ans+=s[i].s*(cnt++)+1;
    printf("%d\n",ans);
    for (int i=1;i<=n;i++) printf("%d ",s[i].id);
    return 0;
}

C.White Sheet

题意

给出三个矩形的左下角和右上角坐标,问后两个矩形(黑)能否完全覆盖第一个矩形(白)。

题解

可以发现要么是一个黑色矩形直接覆盖完,要么是上下、左右覆盖。依次判断即可。

#include<bits/stdc++.h>

using namespace std;

int x1,x2,x3,x4,x5,x6,yy1,y2,y3,y4,y5,y6;

signed main()
{
    cin>>x1>>yy1>>x2>>y2;
    cin>>x3>>y3>>x4>>y4;
    cin>>x5>>y5>>x6>>y6;
    if (x3<=x1 && x4>=x2 && y3<=yy1 && y4>=y2) return 0&puts("NO"); //一个覆盖完
    if (x5<=x1 && x6>=x2 && y5<=yy1 && y6>=y2) return 0&puts("NO");
    if (x3<=x1 && x4>=x2 && x5<=x1 && x6>=x2) //上下覆盖
    {
        if (y4>=y2 && y5<=yy1 && y6>=y3) return 0&puts("NO");
        else if (y6>=y2 && y3<=yy1 && y4>=y5) return 0&puts("NO");
    }
    if (y4>=y2 && y3<=yy1 && y5<=yy1 && y6>=y2) //左右覆盖
    {
        if (x4>=x2 && x5<=x1 && x6>=x3) return 0&puts("NO");
        else if (x6>=x2 && x3<=x1 && x4>=x5) return 0&puts("NO");
    }
    return 0&puts("YES");
}

D.Swords

题意

有 $n(2\le n\le 2\times 10^5)$ 种剑,最开始每种都有 $x$ 把。有 $y$ 个偷剑,每个人偷一种剑的 $z$ 把。

已知最后剩的剑每种有 $a_i$,求 $y$ 的最小值。

题解

要求 $y$ 最小,即要求 $z$ 尽可能大。可以发现 $z$ 由 $a_i$ 之间的差值限定,即所有差值都是 $z$ 的整数倍。

所以将 $a_i$ 从小到大排序,$z$ 即是 $\gcd_{i=2}^n (a_i-a_{i-1})$ ,然后就可以得到 $y$ 了。

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read()
{
    char ch=getchar(); ll f=1,x=0;
    while (ch<'0' || ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0' && ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return f*x;
}

ll n,s[200005];

ll gcd(ll x,ll y) { return y ? gcd(y,x%y) : x; }

signed main()
{
    n=read();
    for (ll i=1;i<=n;i++) s[i]=read();
    sort(s+1,s+n+1);
    if (n==2) return !printf("1 %lld",s[2]-s[1]);
    ll z=gcd(s[2]-s[1],s[3]-s[2]);
    for (ll i=3;i<n;i++) z=gcd(z,s[i+1]-s[i]);
    ll ans=0;
    for (ll i=1;i<n;i++) ans+=(s[n]-s[i])/z;
    return !printf("%lld %lld",ans,z);
}

E.Numerical Sequence

题意

小学奥数题

将 $\forall n\in N \ , \ 1..n$ 的数排列,得到一个形如

$$112123123412345...$$

的无限长数列。

有 $q(q\le 500)$ 次询问,每次询问数列的第 $k(E1: k\le 10^9 \ , \ E2: k\le 10^{18})$ 个数是多少。

题解

对于 $E1$ ,可以发现 $n$ 并不会太大(实验可以发现最多 $20000$ 多),所以可以预处理出 $s[i]$ ,表示当 $n\le i$ 时总共有多少个数。

对于每组询问,可以确定 $k$ 所在的组的 $n$ ,然后不断减数字的位数可以得到 $k$ 所在的数,就能得到答案了。

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read()
{
    char ch=getchar(); ll f=1,x=0;
    while (ch<'0' || ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0' && ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return f*x;
}

ll n,a,s[100005];

inline int f(int x) //x 有几位
{
    int j=0;
    for (int i=1;i<=x;i*=10,j++);
    return j;
}

inline int get(int x,int y) //得到 x 的倒数第 y 位
{
    y--;
    for (;y;y--) x/=10;
    return x%10;
}

signed main()
{
    n=read();
    for (int i=1,cnt=0;cnt<=1e9;i++) //预处理 s[i]
    {
        int cur;
        if (i<=9) cur=i;
        else if (i<=99) cur=9+(i-9)*2;
        else if (i<=999) cur=189+(i-99)*3;
        else if (i<=9999) cur=2889+(i-999)*4;
        else cur=38889+(i-9999)*5;
        cnt+=cur;
        s[i]=cnt;
    }
    while (n--)
    {
        a=read();
        int i=1; for (;s[i]<a;i++); //确定 n
        int now=1,j=a-s[i-1]; //j 是还剩的数的个数
        for (;j-f(now)>0;j-=f(now),now++); //不断减数字的位数可以得到所在的数字 now
        printf("%d\n",get(now,f(now)-j+1)); //第 j 位即倒数第 f(now)-j+1 位
    }
    return 0;
}

对于 $E2$ ,可以预处理 位数为 $i$ 的数字的总长度 $s[i]$ ,如 $s[2]=len(10111213...99)=180$ ,然后二分 $n$ 解决。

代码不想写了,$E1$ 都够恶心了。

F.Wi-Fi

题意

有 $n(n\le 2\times 10^5)$ 个房间,要求每个房间都覆盖网络,覆盖 $i$ 号房间的花费为 $i$。部分房间可以安装路由器,可以覆盖 $[i-k,i+k]$ 的所有房间,花费也为 $i$ 。问最小花费。

题解

可以转化为图论解决。将每个房间看成点,直接覆盖即为从 $i\rightarrow (i+1)$ ,边长为 $i$ ;路由器可以从 $(i-k)\rightarrow (i+k+1)$ ,边长也为 $i$ 。因为可以多次覆盖,所以增加回溯边 $(i+1)\rightarrow i$ ,边长为 $0$ 。

为了统计答案,新建个点 $(n+1)$ 。然后从 $1$ 开始跑最短路即可。

#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define pii pair<ll,ll>
 
using namespace std;
 
inline ll read()
{
    char ch=getchar(); ll f=1,x=0;
    while (ch<'0' || ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0' && ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
    return f*x;
}

const ll N=200005;
struct Edge {
    ll next,to,w;
} edge[N<<2];
bool vis[N];
ll cnt,head[N],n,k,dis[N];
char s[N];

inline void add(ll u,ll v,ll w)
{
    edge[++cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt;
}

inline ll dijkstra()
{
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;
    priority_queue <pii,vector<pii>,greater<pii> > q;
    q.emplace(mp(0,1));
    while (!q.empty())
    {
        ll x=q.top().second; q.pop();
        if (vis[x]) continue;
        vis[x]=1;
        for (ll i=head[x];i;i=edge[i].next)
        {
            ll y=edge[i].to,w=edge[i].w;
            if (dis[x]+w<dis[y])
            {
                dis[y]=dis[x]+w;
                q.emplace(mp(dis[y],y));
            }
        }
    }
    return dis[n+1];
}

signed main()
{
    n=read(); k=read();
    scanf("%s",s+1);
    for (ll i=1;i<=n;i++)
    {
        add(i,i+1,i);
        add(i+1,i,0);
        if (s[i]=='1') add(max(1ll,i-k),min(n+1,i+k+1),i);
    }
    return !printf("%lld",dijkstra());
}

新评论

称呼不能为空
邮箱格式不合法
网站格式不合法
内容不能为空
    lukelmouse
    lukelmouse
    2019-09-28 10:49

    请问F题的 多次覆盖是什么意思,为什么要 加反向边 i+1 -> i ,0

      Llf0703
      2019-09-28 11:50

      就是一个房间可以被多个路由器同时覆盖网络,如果不加反向边的话就无法统计这种情况。比如两个路由器的区间为 [1,5] 和 [4,10] ,答案应该是 1->5->4->10 。

        lukelmouse
        lukelmouse
        2019-09-28 12:54

        我懂了,
        1->6->5->4->10,加反向边可以让他回退到更小的距离去尝试。
        谢谢大佬~Orz.

          lukelmouse
          lukelmouse
          2019-09-28 13:16

          可以加qq吗

            Llf0703
            2019-09-28 14:31

            可以qwq。1667334662

            lukelmouse
            lukelmouse
            2019-09-28 14:33

            我已经发送好友申请了啊~~~