Codeforces Round #677 (Div. 3)A-G题解

Codeforces Round #677 (Div. 3)A-G题解
比赛链接:https://codeforces.ml/contest/1433/

A题
水,施行

题意为给定一个1到9999的每一位上数字相同的整数x,现在按照一定规则报数,问报到x的时候,总共报了多少位的数字。
报数规则为从1,11,111,1111到2,22,222,2222…到9,99,999,9999。

注意到我们需要关注的只是x是由哪个数字构成的,以及x有几位。
假设x是由若干个y构成的,对于任意的数字比如1,喊完1,11,111,1111总共要报10位数字。因此报到y之前总共要报10(y-1)位,之后再看当前的x要报几次,1,2,3,4依次加上去就是了。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int x;
        cin>>x;
        int ans=10*(x%10-1);
        int cas=0;
        while(x)
        {
    
    
            cas++;
            ans+=cas;
            x/=10;
        }
        printf("%d\n",ans);
    }
}

B题
贪心,思维

题意为给定一个01数列a[],每次操作我们可以选择一个连续的1区间,把他们整体左移或者整体右移一个位置,询问最少要多少次操作可以使得所有的1都在一个连续的区间。

注意到对于所有1的初始位置中,最左侧和最右侧的下标位置构成的区间,对于这区间内的0,我们每次操作最多并且必然可以(操作最左侧或者最右侧的连续1区间即可)使得它们减少1。执行此贪心过程的话我们的最少操作次数就是这段区间内0的个数了。
统计即可。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

bool flag[60];

int32_t main()
{
    
    
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        int pre=-1,ans=0;//pre标记上一段连续1区间的右侧位置
        for(int i=1;i<=n;i++)
        {
    
    
            cin>>flag[i];
            if(flag[i]&&!flag[i-1]&&pre!=-1)//记录连续1区间之间的0的个数,pre=-1的时候代表是最左侧的0区间不需要计算进去
            {
    
    //flag[i]=1,flag[i-1]为0,正好是连续0区间到连续1区间的分界处
                ans+=i-pre;//两个位置下标相减,即为这一段连续0区间的长度
                pre=-1;
            }
            if(!flag[i]&&flag[i-1]) pre=i;
        }
        cout<<ans<<endl;
    }
}

C题
贪心,构造

题意为有n条鱼,排成一排,每条鱼有一个初始的力量值。我们需要选定一条鱼,他每次可以吃掉自己左边或者右边的一条鱼,并且每吃掉一条鱼后,自己的力量值+1。询问是否存在这样一条鱼,可以把所有的其他鱼吃掉,输出位置下标。

首先想一下不存在这样的鱼的情况。那就是所有鱼的初始力量都相同时,没有鱼可以吃掉其他鱼。
那么当不是所有鱼的初始力量值都相同的时候,我们必定可以找到一个初始的最大力量值,并且拥有这个最大力量值的所有鱼当中,必定存在一条鱼的周围有比他力量值小的鱼(否则就是所有鱼初始力量值都相等的情况了)。我们选定这样一条初始为最大力量值,周围有比他力量值小的鱼,吃掉旁边这条鱼后力量值+1,便成为了所有鱼中严格拥有最大力量值的唯一一条鱼,任意吃掉其他鱼即可。

因此我们只需要判断是否有这样一条初始为最大力量值,周围有比他力量值小的鱼即可。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=3e5+7;

int num[maxn];

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        int M=0;//所有初始值中的最大值
        for(int i=1;i<=n;i++)
        {
    
    
            cin>>num[i];
            M=max(M,num[i]);
        }
        int tar=-1;//寻找答案位置
        for(int i=1;i<=n;i++)
        {
    
    
            if(num[i]==M)//值要与初始最大值相同
            {
    
    
                if(i>1&&num[i-1]!=M) tar=i;//与左侧或者右侧的值不同的位置,均满足要求,输出任意一个
                if(i+1<=n&&num[i+1]!=M) tar=i;
            }
        }
        cout<<tar<<endl;
    }
}

D题
构造

题意为给定n个点,你需要构造n-1条边,把这n个点连接成一个连通图。但是每个点都有一个标记值,标记值相同的点不能用某条边直接连接,只能间接连通。询问是否有满足要求的构造方法。

思考过程如上题,首先无法构造的情况,所有点的标记值都相同,我们无法构造任何边,故此情况下不存在满足要求的构造方法。
那么当并不是所有点的标记值都相同的情况下,必然存在两个下标的点x和y,他们的标记值是不同的。那么我们把所有与x点标记值不同的点(包括y)都与x连接起来,那么就只剩下标记值与x相同的点没有连接进来了,我们将这些点与y点连接起来即可。便能构造出满足要求的连通图。

按照以上思路构造即可。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        vector<ll>num(n);
        for(auto &x:num) cin>>x;
        int tar=-1;
        for(int i=1;i<n;i++)
            if(num[i]!=num[i-1])//寻找是否有标记值不同的两个点
            {
    
    
                tar=i;
                break;
            }
        if(tar==-1) cout<<"NO"<<endl;//所有点标记值相同,无法构造任何边
        else
        {
    
    
            cout<<"YES"<<endl;
            for(int i=0;i<n;i++)
            {
    
    
                if(i!=tar-1)
                {
    
    
                    if(num[i]!=num[tar-1]) cout<<tar<<' '<<i+1<<endl;//如果标记值与tar-1下标点的值不同,则连接到tar-1点上
                    else cout<<tar+1<<' '<<i+1<<endl;//如果与tar-1点的标记值相同,则连接到tar点上,tar点与tar-1点标记值不同固必定符合要求
                }
            }
        }
    }
}

E题
组合数学

题意为给定n个人,标记为1-n,n为偶数。现在需要你将这n个人分为人数相同的两组,每组都给定一个排列顺序,询问总共有多少种不同的分法。其中排列[1,2,3,4]和[2,3,4,1]和[3,4,1,2]和[4,1,2,3]这样循环相同的排列被认为是相同的排列方式。

首先很容易想到的,分为两组必定要计算组合数C(n/2,n),然后A,B和B,A两种方式重复,再除以2。接着需要考虑的就是两个分组各自有多少种不同的排列了。不考虑循环相同的情况下,n/2长度的排列数为(n/2)!,而考虑循环相同,每n/2种排列构成相同的一种排列方式,因此总方案数应该是(n/2-1)!。两个分组的排列情况相乘,因此最后的总排列数应该为C(n/2,n)/2 × \times ×(n/2-1)! × \times ×(n/2-1)!。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int n;
    cin>>n;
    ll ans=1;
    for(int i=1;i<=n/2;i++) ans=ans*(n-i+1)/i;//计算C(n/2,n),划分成两组
    for(int i=2;i<n/2;i++) ans*=i*i;//乘上两边n!,并且除掉两个n,化简后便是该循环。此处为两组当中排列方式不同的情况,除掉n是因为循环相同的排列也属于排列方式相同
    cout<<ans/2<<endl;//A,B和B,A的分法重复,除以2
}

F题
dp

题意为给定一个n × \times ×m的矩阵,每行最多取m/2个数字,要找到一个最大的总和值,满足能被k整除。

注意到数据范围都很小,都只有70。
对于每行来说,我们可以按照取了几个数字,对k取模等于多少,分为70/2 × \times × 70种状态。使用dp去计算每行取不超过m/2个数的情况下,对k取模为j时,能得到的最大总和值,再将它用同样的dp方式,计算到最终答案上即可。
具体实现和转移方程见代码。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int n,m,k;

int dp[50][100],ans[2][100];//dp[i][j]讨论的是某一行,代表选择了i个数的情况下,得到的sum对k取模等于j的情况下,sum的最大值
//ans数组则用滚动数组来记录最终答案,ans[i][j],代表当前已经dp计算得的所有行累加情况下,得到的sum对k取模等于j的情况下,sum的最大值

int32_t main()
{
    
    
    IOS
    bool flag=0;
    cin>>n>>m>>k;
    int lim=m/2;//每行最多取lim个数
    for(int i=0;i<n;i++)
    {
    
    
        memset(dp,0,sizeof(dp));
        for(int j=0;j<m;j++)
        {
    
    
            int x;cin>>x;
            for(int q=lim-1;q>=0;q--)//注意这里循环是从大到小
            {
    
    
                for(int p=0;p<k;p++)
                {
    
    
                    int temp=dp[q][p]+x;
                    if(temp>dp[q+1][temp%k]) dp[q+1][temp%k]=temp;
                }
            }
        }
        for(int q=0;q<k;q++)
        {
    
    
            for(int p=0;p<k;p++)
            {
    
    
                int temp=ans[flag][p]+dp[lim][q];
                if(ans[!flag][temp%k]<temp) ans[!flag][temp%k]=temp;
            }
        }
        //for(int i=0;i<k;i++) cout<<ans[!flag][i]<<endl;
        flag=!flag;//滚动数组翻滚
    }
    cout<<ans[flag][0]<<endl;
}

G题
最短路,暴力,复杂度分析

题意为给定一个n个点,m条双向边的连通图,给定k条路径的起终点。询问在将最多一条边的权值修改为0的情况下,这k条路径的消耗总和的最小值。

注意到数据范围都很小,最大1000。因此可以采取暴力一些的做法。
如果我们暴力枚举删除每条边,每次删除后暴力去跑k次Dijskra的话,总复杂度mk(n+m)logn,1e9带常数的级别,必定会tle。
这代表我们的方法过分暴力了,注意到上述表述中有两个暴力过程,我们尝试思考是否可以把其中的某一部分暴力过程优化。
对于我们把i点到j点的路径置为0,对于从q点到p点的最短路径来说,存在两种大的情况。
1.i到j点路径置为0后,q点到p点的最短路径选择未受影响
2.i到j点路径置为0后,q点到p点的最短路径选择受到影响,并且i点到j点的路径必定在新的最短路径上。

对于第二种情况,可以是q->i->j->p也可以是q->j->i->p,这两种路径长度分别为q到i的最小距离加上j到p的最小距离,和q到j的最小距离加上i到p的最小距离。这两种方案与原本不把路径置零的情况下q到p的最小距离取最小值,即为把i到j点路径置为0后,q到p的最短距离了。

有了上述结论后,我们注意到,上面需要的三个距离,我们只需对k次询问的所有起终点,都跑一次Dijskra即可。总复杂度2k(n+m)logn。
离线计算k次询问的所有起终点,到其他所有点的最短距离,之后跑一次km复杂度的计算即可。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e3+7;

int n,m,k,tot;

struct Edge
{
    
    
    int to,next,dis;
}edge[maxn*2];

int head[maxn];

void init()
{
    
    
    for(int i=1;i<=n;i++) head[i]=-1;
    tot=0;
}

void add(int u,int v,int w)
{
    
    
    edge[tot].dis=w;
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}

ll dis[maxn][maxn];//dis[i][j]记录i点到j点的最短距离

struct Node
{
    
    
    int pos;ll val;
    Node(int pos,ll val):pos(pos),val(val){
    
    }
    friend bool operator < (Node a,Node b)
    {
    
    
        return a.val>b.val;
    }
};

void Dijskra(int st)
{
    
    
    for(int i=1;i<=n;i++) dis[st][i]=llINF;
    dis[st][st]=0;
    priority_queue<Node>Q;Q.push(Node(st,0));
    while(Q.size())
    {
    
    
        Node now=Q.top();
        Q.pop();
        if(now.val>dis[st][now.pos]) continue;
        for(int i=head[now.pos];i!=-1;i=edge[i].next)
        {
    
    
            int to=edge[i].to;
            if(dis[st][to]>edge[i].dis+now.val)
            {
    
    
                dis[st][to]=edge[i].dis+now.val;
                Q.push(Node(to,dis[st][to]));
            }
        }
    }
}

int u[maxn],v[maxn],w[maxn],st[maxn],ed[maxn];

int32_t main()
{
    
    
    IOS
    cin>>n>>m>>k;
    init();
    for(int i=0;i<m;i++)
    {
    
    
        cin>>u[i]>>v[i]>>w[i];
        add(u[i],v[i],w[i]);add(v[i],u[i],w[i]);
    }
    for(int i=0;i<k;i++)
    {
    
    
        cin>>st[i]>>ed[i];
        Dijskra(st[i]);Dijskra(ed[i]);//预处理出所有询问的起终点,到其他所有点的距离
        //单次Dijskra复杂度为(n+m)logn,要跑2k次,总复杂度2k(n+m)logn
    }
    ll ans=llINF;
    for(int i=0;i<m;i++)//此处O(1)计算,总复杂度mk
    {
    
    
        ll temp=0;
        for(int j=0;j<k;j++) temp+=min(dis[st[j]][ed[j]],min(dis[st[j]][u[i]]+dis[ed[j]][v[i]],dis[st[j]][v[i]]+dis[ed[j]][u[i]]));
        ans=min(ans,temp);
    }
    cout<<ans<<endl;
}

猜你喜欢

转载自blog.csdn.net/StandNotAlone/article/details/109538974