ZAFU_2021_1_28_2021寒假个人赛第三场题解

A题
原题链接:https://codeforces.com/problemset/problem/1409/A
相关tag:贪心,简单结论

算出x和y的差值。
我们必然是尽可能用最大的步子10去缩短这个差值。
最后一步可能少于10。

直接算(x+y)/10+(x+y)%10!=0即可,等价于(x+y+9)/10

#include
#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--)
    {
     
        ll a,b;
        cin>>a>>b;
        a=abs(a-b);
        cout<<(a+9)/10<<endl;
    }
}

B题
原题链接:https://codeforces.ml/problemset/problem/1201/A
相关tag:贪心

记录下每道题选每个选项的有几个人,每道题都把选的人最多的选项当做答案去算即可。

#include
#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;
int num[1007][5];//num[i][j]代表第i题选'A'+j的有几个人

int32_t main()
{
     
    IOS;
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
     
        string s;cin>>s;
        for(int j=0;j<m;j++)
            num[j][s[j]-'A']++;
    }
    int ans=0;
    for(int i=0;i<m;i++)
    {
     
        int x;cin>>x;
        int y=0;
        for(int j=0;j<5;j++) y=max(y,num[i][j]);
        ans+=x*y;
    }
    cout<<ans<<endl;
}

C题
原题链接:https://codeforces.com/problemset/problem/1175/A
相关tag:贪心

注意到除k必然是比-1更优的,因此能整除k的时候优先使用除k,不能的时候把余数全部减掉即可。

#include
#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--)
    {
     
        ll n,k;cin>>n>>k;
        ll ans=0;
        while(n)
        {
     
            if(n%k)
            {
     
                ans+=n%k;
                n-=n%k;
            }
            else {
     n/=k;ans++;}
        }
        cout<<ans<<endl;
    }
}

D题
原题链接:https://codeforces.com/problemset/problem/977/D
相关tag:dfs,排序

给出dfs和结论后排序两种做法。

注意到最多只有100个数字,并且每个数字可能的变换方法只有两种。
我们可以把每个数字看成一个点,每种变换方式都是一条单向边,那么每个点最多只有两条出边,也就是整个图最多只有200条有向边(当然这种情况是不存在的)。
我们可以暴力枚举起点是哪一个进行dfs搜索,单次搜索这张无环有向图的复杂度等于有向边的数量。
总复杂度最大为O(2 × \times ×n × \times ×n)

由于点的数量n非常小,我们可以通过一次n × \times ×n的循环暴力比对每两个数字是否可以由这两种操作得到这张无环有向图的所有边。

#include
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

vector<int>field[107];//保存图的边信息i点与field[i]中的元素存在有向边
ll num[107];
int n;
bool visit[107];//记录各个点被访问的情况
vector<int>road;//保存路径,用数组实现而不是使用vector的push和pop操作会更快,这里出于方便理解的考虑写成vector形式

void dfs(int now)
{
     
    visit[now]=1;//当前位置置为访问过
    road.push_back(now);//压入路径中

    if(road.size()==n)
    {
     
        for(int i=0;i<n;i++) cout<<num[road[i]]<<' ';
        exit(0);//找到满足的路径后直接结束程序
    }

    for(int i=0;i<field[now].size();i++)//遍历当前结点出发可以走到的其他点
        if(!visit[field[now][i]]) dfs(field[now][i]);//如果下个位置未被访问过,即未被走过的路径,继续递归搜索

    road.pop_back();//还原
    visit[now]=0;//还原
}

int main()
{
     
    IOS;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>num[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(num[j]*3==num[i]||num[i]*2==num[j])//暴力for一遍建图
                field[i].push_back(j);
    for(int i=1;i<=n;i++) dfs(i);//以这n个数字分别作为第一个数字进行搜索
}

我们进行的操作只有 × \times × 2或者 ÷ \div ÷ 3
那么随着我们操作的进行,当前数字分解质因数后,2出现的次数是不断在上升的,而3出现的次数是不断在下降的。
依靠这一点直接sort一遍即可,第一排序关键字为分解质因数后3出现的个数,3个数相同的情况下直接比较数值大小即可,大的数值在后面。

#include
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

struct Node
{
     
    ll num;//保存数值
    int base;//num分解质因数后3出现了几次
};

Node node[107];
int n;

bool cmp(Node a,Node b)
{
     
    if(a.base==b.base) return a.num<b.num;
    else return a.base>b.base;//排序的第一关键字为3出现的次数
}

int32_t main()
{
     
    IOS;
    cin>>n;
    for(int i=0;i<n;i++)
    {
     
        cin>>node[i].num;
        ll temp=node[i].num;
        node[i].base=0;
        while(temp%3==0)
        {
     
            temp/=3;
            node[i].base++;
        }
    }
    sort(node,node+n,cmp);
    for(int i=0;i<n;i++) cout<<node[i].num<<' ';
}

E题
原题链接:https://atcoder.jp/contests/arc111/tasks/arc111_a
相关tag:数学,快速幂

注意到10n可以分解成k × \times ×m2+d。(k和d均为整数)
其中k × \times ×m2在经历/m%m的过程后必定为0,去除这部分对结果没有影响。
故我们直接用快速幂计算10n对m2取模的结果d,用d/m%m即为结果。

#include
#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

ll qpow(ll x,ll p,ll mod)//快速幂计算10^n%(m*m)的结果
{
     
    ll ret=1;
    while(p)
    {
     
        if(p&1) ret=ret*x%mod;
        x=x*x%mod;
        p>>=1;
    }
    return ret;
}

int main()
{
     
    IOS
    ll n,m;cin>>n>>m;
    n=qpow(10,n,m*m);
    cout<<n/m%m<<endl;
}

F题
原题链接:https://codeforces.com/problemset/problem/976/C
相关tag:排序,贪心

区间[l1,r1]完全包含区间[l2,r2]的充要条件是l1<=l2且r1>=r2。

对每个区间[l2,r2]来说,我们实际上只需要先找左边界l1 或者l1=l2时,对比所有右区间r1>=r2的区间。

我们可以对所有区间进行一个排序,先按照左区间边界从小到大的顺序进行排序。
这样的话我们就保证了,对于第i个区间来说,左区间边界小于第i个区间的所有区间都在它的左侧。
当左区间边界相同时,按照右区间边界从大到小排序。
这样我们就保证了,左区间边界相等的所有区间,右区间边界大的在左侧。

之后直接从左到右for一遍,记录之前出现的最大右边界,与当前的右边界比较即可。

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=3e5+7;

struct Node//区间结构体,tar标记这是第几个区间,用于输出
{
     
    int l,r;
    int tar;
};

bool cmp(Node a,Node b)//sort的比较函数,左边界越靠左的排序后越在前,左边界相同情况下,右边界越靠右排序后越在前
{
     
    if(a.l==b.l) return a.r>b.r;
    else return a.l<b.l;
}

Node node[maxn];
int n;

int main()
{
     
    IOS
    cin>>n;
    for(int i=0;i<n;i++)
    {
     
        cin>>node[i].l>>node[i].r;
        node[i].tar=i+1;
    }
    sort(node,node+n,cmp);
    int pre=-1,r=0;//pre记录前面右边界最大的区间是第几个,r标记当前出现的最大右边界
    bool flag=0;
    for(int i=0;i<n;i++)
    {
     
        if(node[i].r<=r)
        {
     
            cout<<node[i].tar<<' '<<pre<<endl;
            flag=1;
            break;
        }
        else {
     pre=node[i].tar;r=node[i].r;}//更新右边界最大的区间以及最大右边界的值
    }
    if(!flag) cout<<-1<<' '<<-1<<endl;
}

G题
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1059
相关tag:dp,进制优化

我们把所有任务总共需要的天数记为sum。
这个题目一看是一个比较明显的多重背包问题,我们可以dp去求sum/2总天数的方案是否存在。
但是问题在于这里的背包个数x最多为2e4,而sum最多也有6e4。
而多重背包的复杂度为O(x × \times ×sum/2),打到12e8,并且还有多组数据,因此直接多重背包dp是会tle的。

这里我们可以利用二进制分解任务的件数,将复杂度降低为O(logx × \times ×sum/2)。
例如需要2天的任务有x2件,由于这些任务都是需要2天,可以被视作完全相同的任务。我们需要关注的只是这x2件任务需要选择几件。
而我们把这个x2件写成二进制,按照二进制的每一位单独拿出来,实际上就是x2的二进制各位取1还是取0,也就是取还是不取。
我们将x2的各个二进制位独立出来,视作统一的一件任务,自此转化为一个01背包问题。
而这个01背包拥有的背包数量len满足2len=x,故len=logx。

#include
#include
int sum,a[7],b[107],len;//sum为所有任务的天数综合,a[i]代表需要i天的任务有几件
bool dp[60001];
//b[]保存二进制优化的01背包
//dp[i]代表花费i天的分配方案是否存在

void get()
{
     
    sum=0;
    for(int i=1;i<=6;i++)
    {
     
        scanf("%d",&a[i]);
        sum=sum+i*a[i];
    }
}

void change()//二进制优化
{
     
    len=0;
    for(int i=1;i<=6;i++)//i代表当前优化的是需要i天的任务
    {
     
        int x=1;//对任务数量进行二进制分解,x从2进制最低位的1开始
        while(x<=a[i])
        {
     
            b[++len]=i*x;//x件任务合并为一个统一的任务
            a[i]-=x;
            x*=2;//二进制更高一位
        }
        if(a[i]) b[++len]=i*a[i];//剩余的部分合并为一个统一的任务
    }
}

int main()
{
     
    int flag,t=0;
    dp[0]=1;
    while(get(),sum)//逗号表达式,返回值为sum,当sum=0时推出循环;get函数负责读入
    {
     
        t++;//t记录当前是第几组数据
        flag=1;//标记是否存在平均分配的方案
        if(sum%2) flag=0;
        else
        {
     
            sum/=2;
            change();//将多重背包利用二进制优化为01背包
            for(int i=1;i<=len;i++)
                for(int j=sum;j>=b[i];j--)
                    if(dp[j-b[i]]) dp[j]=1;//01背包dp过程
            if(dp[sum]==0) flag=0;
            for(int i=1;i<=sum;i++) dp[i]=0;
        }
        printf("Collection #%d:\n",t);
        if(flag) printf("Can be divided.\n\n");
        else printf("Can't be divided.\n\n");
    }
}

H题
原题链接:
相关tag:树的直径,dfs

思路见注释

#include
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+10;

//题意为给你一个有n个节点n-1条边的树(没有闭环的图)
//要求你在这棵树里找到三个节点,使得它们之间的三条路径走过的边数最多(多次经过同一条边只算一次)
//首先要知道树的直径这个概念.树的直径是整棵树中两点距离最长的那一条路径

//先用反证的方法证明三条路径必定至少有一条会是树的直径
//如果三条路径中没有任何一条是树的直径
//我们可以将其中的两个点更换为树的直径两端的端点,会发现长度变得更大了
//因此三条路径中没有任何一条是直径的情况必然不是总长度最长的

//因而首先我们可以确定这三条路径中必定至少存在一条直径,这样就确定了两个点,再就是第三个点如何确定
//第三个点和第一还有第二个点的路径合起来的新路径,必定包含了这条直径
//那么在原来已经找到的这条直径基础上加上这个第三个点,规定第三和第一个点的距离为d1,第三和第二个点的距离为d2,树的直径为x
//那么加入第三个点后增加的边数就应该是(d1+d2-x)/2,而x是我们计算出的一个定值
//所以实际上第三个点需要满足的条件是和第一个点还有第二个点的距离和最大

//到此思路已经全部完成,首先是求取树的直径的两个端点,这个方法和证明过程自己见算法书或者博客
//dfs第一次从1点开始,dst[i][0]记录i点到1点的距离,距离最大的点为直径的第一个端点a
//dfs第二次从a点开始,dst[i][1]记录i点到a点的距离,距离最大的点为直径的第二个端点b
//dfs第三次从b点开始,dst[i][2]记录i点到b点的距离,由此我们便计算出了整棵树各个点到点a还有点b的距离,由此去寻找c点

vector<int>road[maxn];//记录路径
int dis[maxn][3];

void dfs(int now,int before,int sum,int cas)//now为当前所在节点位置,before为上一个节点位置,sum为当前到起点的距离,cas为dst第二个下标
{
     
    for(int i=0;i<road[now].size();i++)
    {
     
        if(road[now][i]!=before)         //这是个无闭环的图,因此我们任意的搜索路径也都是无闭环的,只要满足不往回走就可以了
        {
     
            dis[road[now][i]][cas]=sum+1;
            dfs(road[now][i],now,sum+1,cas);
        }
    }
}

int main()
{
     
    IOS
    memset(dis,0x7f,sizeof(dis));
    int n;
    cin>>n;
    for(int i=1;i<n;i++)
    {
     
        int x,y;
        cin>>x>>y;
        road[x].push_back(y);
        road[y].push_back(x);
    }
    dis[1][0]=0;
    dfs(1,1,0,0);
    int a,b,c,Max=-INF;
    for(int i=2;i<=n;i++)
        if(dis[i][0]>Max)
        {
     
            Max=dis[i][0];
            a=i;
        }
    dis[a][1]=0;
    dfs(a,a,0,1);
    Max=-INF;
    for(int i=1;i<=n;i++)   //这个循环结束后Max记录的便是直径的长度
    {
     
        if(dis[i][1]>Max)
        {
     
            Max=dis[i][1];
            b=i;
        }
    }
    dis[b][2]=0;
    dfs(b,b,0,2);
    int ans=-INF;
    for(int i=1;i<=n;i++)       //ans记录的是c点到a点和b点的距离和
    {
     
        int x=dis[i][1]+dis[i][2];
        if(x>ans&&i!=a&&i!=b)  //注意这里如果整棵树只有一条路径的话寻找c点的时候可能会和a或者b点重复
        {
     
            ans=x;
            c=i;
        }
    }
    ans=(ans+Max)/2;            //原本应该是(ans-Max)/2+Max,化简了一下
    cout<<ans<<endl;
    if(a>b) swap(a,b);
    if(b>c) swap(b,c);
    if(a>b) swap(a,b);
    cout<<a<<' '<<b<<' '<<c<<endl;
}

你可能感兴趣的:(2021寒假个人赛题解,算法)