Codeforces Round #774 (Div. 2) 简训

本文介绍了Codeforces Round #774 (Div.2) 中涉及的几个题目,包括树形动态规划在ASquareCounting中的应用,背包问题解决技巧在BQualityvsQuantity中的策略,以及Factorials and Powers of Two中的阶乘和幂运算计数。通过实例解析和代码演示,展示了如何利用数学思维和技术解决这些信息技术竞赛中的实际问题。
摘要由CSDN通过智能技术生成

导语

日常训练,C题很可惜,因为DP不是很擅长

涉及的知识点

树形DP,背包DP,思维,数学

链接: Codeforces Round #774 (Div. 2)

题目

A Square Counting

题目大意:有n+1个整数序列 a a a,对于每个下标保证 0 ≤ a i < n 0\le a_i\lt n 0ai<n或者 a i = n 2 a_i=n^2 ai=n2 s s s为序列和,现在知道了 s s s n n n,判断最多能有几个 n 2 n^2 n2

思路:直接除就行

代码

#include <bits/stdc++.h>
#define int long long
const int inf=0x3f3f3f3f;
using namespace std;
const int maxn=1e6+5;
int t,n,s,a[maxn];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n>>s;
        cout <<s/(n*n)<<endl;//获得个数
    }
    return 0;
}

B Quality vs Quantity

题目大意:略

思路:录入数据后直接排序,大值取1,小值取2个,累和,如果不符合条件大值个数+1,小值个数+1,直到满足条件

代码

#include <bits/stdc++.h>
#define int long long
const int inf=0x3f3f3f3f;
using namespace std;
const int maxn=2e5+5;
int t,n,s,a[maxn];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        for(int i=1; i<=n; i++)cin >>a[i];
        sort(a+1,a+1+n);
        if(n<=2) {
            cout <<"NO\n";
            continue;
        }
        int h=a[n],l=a[1]+a[2];
        if(h>l) {
            cout <<"YES\n";
            continue;
        }
        bool flag=0;
        for(int i=3,j=n-1; i<j;) {
            h+=a[j--],l+=a[i++];
            if(h>l) {
                flag=1;
                break;
            }
        }
        flag?cout <<"YES\n":cout <<"NO\n";
    }
    return 0;
}

C Factorials and Powers of Two

题目大意:定义完美的数字 x x x x x x要么为2的整数次幂,要么为阶乘,现在给出一个数n,找出k个不同的完美数字的和正好为n,输出k,如果存在多解,输出最小的k

思路:两种思路,一种暴搜,一种是DP
暴搜:因为存在2的次幂,所以一直有解,加入阶乘相当于让多个二进制和起来,而总体元素不多,所以可以直接暴搜尝试
DP:因为范围内满足条件的阶乘很少,只有15个,所以可以直接尝试这15个数的所有有效组合,而任意一个数可以转换为二进制,所以一个数二进制里1的个数对应需要的数字个数,那么可以尝试所有的阶乘+二进制1的组合方式取最值即可

代码(暴搜)

#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
const int maxn=121;
int t,n,cnt,pre[maxn],ans;
int a[maxn]= {0,1,2,4,6,8,16,24,32,64,120,128,256,512,720,1024,2048,
              4096,5040,8192,16384,32768,40320,65536,131072,262144,362880,524288,
              1048576,2097152,3628800,4194304,8388608,16777216,33554432,39916800,
              67108864,134217728,268435456,479001600,536870912,1073741824,2147483648,
              4294967296,6227020800,8589934592,17179869184,34359738368,68719476736,
              87178291200,137438953472,274877906944,549755813888,1099511627776,1394852659200
             };//预处理1e12内的所有特殊数字
void DFS(int res,int u,int num) {//暴搜
    if(res==0) {
        ans=min(ans,num);
        return;
    }
    if(u==0)return;
    if(res>pre[u])return;
    //剪枝,如果前缀和都小于当前剩余的值,代表不能从前缀和中抽出值得到
    if(res>=a[u])
        DFS(res-a[u],u-1,num+1);
    DFS(res,u-1,num);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
//    freopen("in.txt","w",stdout);
    cin >>t;
    //reverse(a+1,a+50);
    for(int i=1; i<=54; i++)//前缀和
        pre[i]=pre[i-1]+a[i];
    while(t--) {
        cin >>n;
        ans=inf;
        DFS(n,54,0);
        ans==inf?cout <<-1<<endl:cout <<ans<<endl;
    }
    return 0;
}

代码(DP)

#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
int c[20]= {1},t,n;
vector<int>v;
map<int,int>dp;//记录阶乘组合的结果以及对应需要的元素个数
int div(int x) {
    bitset<65>tmp=x;
    return tmp.count();
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    for(int i=1; i<=15; i++)c[i]=c[i-1]*i;
    v.push_back(0);
    for(int i=3; i<=15; i++) {//预处理所有的阶乘的处理情况
        int len=v.size();
        for(int j=0; j<len; j++) {
            if(dp[v[j]+c[i]]==0)dp[v[j]+c[i]]=dp[v[j]]+1;
            else dp[v[j]+c[i]]=min(dp[v[j]+c[i]],dp[v[j]]+1);//取个数最少
            v.push_back(v[j]+c[i]);
        }
    }
    sort(v.begin(),v.end());
    cin >>t;
    while(t--) {
        cin >>n;
        int res=div(n),len=v.size();//初始化为二进制里1个数
        for(int i=0; i<=len; i++) {
            if(n-v[i]<0)break;
            res=min(res,div(n-v[i])+dp[v[i]]);
            //一部分用阶乘组成,另一部分用二进制
        }
        cout <<res<<endl;
    }
    return 0;
}

D Weight the Tree

题目大意:给出有n个节点的树,编号1到n,定义好节点:邻接点点权和等于该点点权,现在要为树节点赋值,求好节点数量最大的赋值方案,如果多解,输出点权和最小的方案

思路:由于权值和要最小化,那么不为好点直接设置为1即可,除了只有两个点的特殊情况外,好点和坏点必然是交替的,那么好点周围必然全是坏点,点权值也就是多个1相加,考虑dp
d p [ i ] [ 0 ] dp[i][0] dp[i][0] i i i不为好节点, d p [ i ] [ 1 ] dp[i][1] dp[i][1]为好节点, a n s ans ans为该点及其子树中好点个数, s u m sum sum表示自己以及子树点权和,可得转移方程

  1. d p [ u ] [ 1 ] . a n s + = d p [ v ] [ 0 ] . a n s dp[u][1].ans+=dp[v][0].ans dp[u][1].ans+=dp[v][0].ans
  2. d p [ u ] [ 1 ] . s u m + = d p [ v ] [ 0 ] . s u m dp[u][1].sum+=dp[v][0].sum dp[u][1].sum+=dp[v][0].sum
  3. d p [ u ] [ 0 ] . a n s + = m a x . a n s dp[u][0].ans+=max.ans dp[u][0].ans+=max.ans
  4. d p [ u ] [ 0 ] . s u m + = m a x . s u m dp[u][0].sum+=max.sum dp[u][0].sum+=max.sum
    m a x max max为好点个数最大且点权和最小的子节点

代码

#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
const int maxn=2e5+5;
int t,n,cnt,head[maxn],degree[maxn],w[maxn];
struct node {
    int next,to;
} e[maxn<<1];
void Add(int from,int to) {
    e[++cnt].next=head[from];
    e[cnt].to=to;
    head[from]=cnt;
}
struct x {
    int ans,sum;//子节点内(包括自己)好点个数,子树和
    bool operator==(const x& a)const {
        return a.ans==ans&&a.sum==sum;
    }
} dp[maxn][2];
x getmax(x a,x b) {
    if(a.ans>b.ans)return a;
    if(a.ans==b.ans&&a.sum<b.sum)return a;
    return b;
}
void DFS(int u,int f) {
    dp[u][0]=(x) {//不是好点
        0,1ll
    };
    dp[u][1]=(x) {//是好点,那么周围的所有点都不为好点,点权为度
        1ll,degree[u]
    };
    for(int i=head[u]; ~i; i=e[i].next) {
        int v=e[i].to;
        if(v==f)continue;
        DFS(v,u);
        dp[u][1].ans+=dp[v][0].ans;
        //这个点设置为好点,那么邻接点不为好点
        dp[u][1].sum+=dp[v][0].sum;
        //统计子树点权和
        x t=getmax(dp[v][0],dp[v][1]);
        //如果这个点不是好点,选择好点多且子树和少的方案
        dp[u][0].ans+=t.ans;
        //统计好点
        dp[u][0].sum+=t.sum;
        //统计子树点权和
    }
}
void Print(int u,int f,bool flag) {
    w[u]=flag?degree[u]:1;//如果是好点,点权为度,否则为1
    for(int i=head[u]; ~i; i=e[i].next) {
        int v=e[i].to;
        if(v==f)continue;
        Print(v,u,flag?0:getmax(dp[v][0],dp[v][1])==dp[v][1]);
        //flag=1,是好点,那么邻接点v不为好点,否则判断v是不是好点
    }
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>n;
    memset(head,-1,sizeof(head));
    for(int i=1,u,v; i<n; i++) {//建图
        cin >>u>>v;
        Add(u,v);
        Add(v,u);
        degree[v]++,degree[u]++;//记录度
    }
    if(n==2) {//特判节点数为2
        cout <<"2 2\n1 1";
        return 0;
    }
    DFS(1,1);//深搜
    x t=getmax(dp[1][0],dp[1][1]);//获得最值
    Print(1,1,t==dp[1][1]);//t==dp[1][1]判断1是不是好点
    cout <<t.ans<<" "<<t.sum<<endl;
    for(int i=1; i<=n; i++)cout <<w[i]<<" ";
    return 0;
}

E Power Board

题目大意:n×m的矩阵,第i行第j列的元素为 i j i^j ij,找出矩阵中出现的所有数字的种类个数

思路:题目思路很精彩,也很复杂,我尽量讲清楚
首先可以发现一个规律,对于第 k k k行的第 p p p个数字,在 k p k^p kp行的第1个数字会重复出现,那么,互为倍数且为幂次的行就可以视为一个集合,例如2,4,8,16…6,36,216,…,每个集合只有其内部元素进行了幂运算才能得到其余内部元素,对于每个集合,假设其最小元素为 x x x,那么单独从题设中 n × m n×m n×m的矩阵中可以得到如下矩阵
x x 2 … x m x 2 x 4 … x 2 m ⋮ ⋮ ⋮ ⋮ x k x 2 k … x k m \begin{matrix} x & x^2 & \dots&x^m \\ x^2 & x^4 & \dots &x^{2m}\\ \vdots &\vdots&\vdots&\vdots\\ x^k & x^{2k} & \dots &x^{km}\\ \end{matrix} xx2xkx2x4x2kxmx2mxkm
现在的任务就是从这 k × m k×m k×m的矩阵中去重,可以发现,只要幂次相同,那么矩阵对应元素就一定相同,因此可以单独处理幂数,就可以得到如下矩阵
1 2 … m 2 4 … 2 m ⋮ ⋮ ⋮ ⋮ k 2 k … k m \begin{matrix} 1& 2 & \dots&m \\ 2 & 4 & \dots &{2m}\\ \vdots &\vdots&\vdots&\vdots\\ k & {2k} & \dots &{km}\\ \end{matrix} 12k242km2mkm
那么 k k k最大为多少?,求 k k k的最大值,也就是判断不等式 x k ≤ n x^k\le n xkn x x x最小为 2 2 2 n ≤ 1 0 6 n\le10^6 n106,那么可以得到 k k k最大值为 20 20 20
x x x为不同的值时, k k k的大小也随之变化,所得到的幂数矩阵是不一样的,但是去重的条件是一样的,就是幂数相同,那么,如果直接预处理幂数矩阵,进行幂数的去重,就可以很快针对不同的 x x x获得其去重之后的所有不同的幂数,因此,可以尝试 k k k 1 1 1 20 20 20的不同去重(分别对应上文提到的不同的集合,对于每个 k k k有数个集合是公用的),那么可以得到不同 k k k下不同幂数的个数
由于幂数间存在倍数关系,可以使用埃氏筛
预处理完之后,根据题目的输入,遍历每一行,对于该行首个元素,如果其幂数矩阵已经统计过,直接跳过即可,否则统计其幂数矩阵,并且标记小于 n n n的其所有幂数
代码

#include <bits/stdc++.h>
#define int long long
const int inf=1e13;
using namespace std;
const int maxn=1e6+5;
int n,m,sum[121],cnt;
bool vis[maxn*21];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>n>>m;
    for(int i=1; i<20; i++) {//预处理幂数矩阵,i为k值
        for(int j=1; j<=m; j++)
            cnt+=!vis[i*j],vis[i*j]=1;
        sum[i]=cnt;//记录不同k下去重之后的数量
    }
    int res=1;
    memset(vis,0,sizeof(vis));
    for(int i=2; i<=n; i++) {
        if(vis[i])continue;//代表幂数矩阵已经计算了
        int s=0;
        for(int j=i; j<=n; j*=i)vis[j]=1,s++;//表示已经在幂数矩阵中算过了
        res+=sum[s];//统计幂数矩阵
    }
    cout <<res<<endl;
    return 0;
}

参考文献

  1. Codeforces Round #774 (Div. 2) C. Factorials and Powers of Two
  2. Codeforces Round #774 (Div. 2)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值