CCF-CSP认证考试(第32次)总结+题解

总结:

这一次我选了时间较近的一次进行练习,结果有点悲,前两题就一个质因数分解还好,第三题我走了弯路,想通过类似线段树的手段处理,结果还差了系数,最后一个点tle了。。。

第四题直接放弃了。。(太菜,没脸见人)

第五题还抱有幻想,当然也只有幻想。。

后两题只能打暴力了,以我现在水平,希望努力一个月能有所改变吧。

题解:(这里只给到三题题解,再往后我也不会了。。)

题一: 仓库规划

西西艾弗岛上共有 n 个仓库,依次编号为 1∼n。

每个仓库均有一个 m 维向量的位置编码,用来表示仓库间的物流运转关系。

具体来说,每个仓库 i 均可能有一个上级仓库 j,满足:仓库 j 位置编码的每一维均大于仓库 i位置编码的对应元素。

比如编码为 (1,1,1) 的仓库可以成为 (0,0,0) 的上级,但不能成为 (0,1,0) 的上级。

如果有多个仓库均满足该要求,则选取其中编号最小的仓库作为仓库 i 的上级仓库;如果没有仓库满足条件,则说明仓库 i 是一个物流中心,没有上级仓库。

现给定 n 个仓库的位置编码,试计算每个仓库的上级仓库编号。

输入格式

输入共 n+1 行。

输入的第一行包含两个正整数 n 和 m,分别表示仓库个数和位置编码的维数。

接下来 n 行依次输入 n 个仓库的位置编码。其中第 i 行(1≤i≤n)包含 m 个整数,表示仓库 i 的位置编码。

输出格式

输出共 n 行。

第 i 行(1≤i≤n)输出一个整数,表示仓库 i 的上级仓库编号;如果仓库 i 没有上级,则第 i 行输出 0。

数据范围

50% 的测试数据满足 m=2;
全部的测试数据满足 0绝对值不大于 1e6的整数。

代码:

#include
using namespace std;
const int N=1010;
int g[N][15];
//int ans[N];
int n,m;
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>g[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        bool Flag=0;
        for(int j=1;j<=n;j++){
            if(i==j){
                continue;
            }
            bool flag=1;
            for(int k=1;k<=m;k++){
                if(g[i][k]>=g[j][k]){
                    flag=0;
                    break;
                }
            }
            if(flag){
               cout<

题二: 因子化简

质数(又称“素数”)是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。

小 P 同学在学习了素数的概念后得知,任意的正整数 n 都可以唯一地表示为若干素因子相乘的形式。

如果正整数 n 有 m 个不同的素数因子 p1,p2,…,pm,则可以表示为:n=pt1×pt2×…×ptm。

小 P 认为,每个素因子对应的指数 ti 反映了该素因子对于 n 的重要程度。

现设定一个阈值 k,如果某个素因子 pi 对应的指数 ti 小于 k,则认为该素因子不重要,可以将 pti 项从 n 中除去;反之则将 pti 项保留。

最终剩余项的乘积就是 n 简化后的值,如果没有剩余项则认为简化后的值等于 1。

试编写程序处理 q 个查询:

每个查询包含两个正整数 n 和 k,要求计算按上述方法将 n 简化后的值。

输入格式

输入共 q+1 行。

输入第一行包含一个正整数 q,表示查询的个数。

接下来 q 行每行包含两个正整数 n 和 k,表示一个查询。

输出格式

输出共 q 行。

每行输出一个正整数,表示对应查询的结果。

数据范围

40%的测试数据满足:n≤1000;
80%的测试数据满足:n≤1e5;
全部的测试数据满足:1

思路:

质因数分解,注意去long long,会爆int。

代码:

#include
using namespace std;
typedef long long LL;
int q;
unordered_mapmp;
void divid(LL x)
{
    for(int i=2;i<=x/i;i++){
        if(x%i==0){
            int s=0;
            while(x%i==0){
                x/=i;
                s++;
            }
            mp[i]=s;
        }
        if(x>1){
            mp[x]=1;
        }
    }
}
void solve()
{
    cin>>q;
    for(int i=1;i<=q;i++){
        LL n,k;
        LL ans=1;
        mp.clear();
        cin>>n>>k;
        divid(n);
        for(int i=2;i<=n/i;i++){
            if(mp[i]>=k){
                LL o=1;
                while(mp[i]){
                    o*=i;
                    mp[i]--;
                }
                ans*=o;
            }
        }
        cout<

 题三:树上搜索

西西艾弗岛大数据中心为了收集用于模型训练的数据,推出了一项自愿数据贡献的系统。

岛上的居民可以登录该系统,回答系统提出的问题,从而为大数据中心提供数据。

为了保证数据的质量,系统会评估回答的正确性,如果回答正确,系统会给予一定的奖励。

近期,大数据中心需要收集一批关于名词分类的数据。

系统中会预先设置若干个名词类别,这些名词类别存在一定的层次关系。

例如,“动物”是“生物”的次级类别,“鱼类”是“动物”的次级类别,“鸟类”是“动物”的次级类别,“鱼类”和“鸟类”是“动物”下的邻居类别。

这些名词类别可以被按树形组织起来,即除了根类别外,每个类别都有且仅有一个上级类别。

并且所有的名词都可以被归类到某个类别中,即每个名词都有且仅有一个类别与其对应。

一个类别的后代类别的定义是:若该类别没有次级类别,则该类别没有后代类别;否则该类别的后代类别为该类别的所有次级类别,以及其所有次级类别的后代类别。

下图示意性地说明了标有星号的类别的次级类别和后代类别。

CCF-CSP认证考试(第32次)总结+题解_第1张图片

次级类别与后代类别

系统向用户提出问题的形式是:某名词是否属于某类别,而用户可以选择“是”或“否”来回答问题。

该问题的含义是:某名词是否可以被归类到某类别或其后代类别中。

例如,要确定名词“鳕鱼”的类别,系统会向用户提出“鳕鱼是否属于动物”,当用户选择“是”时,系统会进一步询问“鳕鱼是否属于鱼类”,当用户选择“是”时,即可确定“鳕鱼”可以被归类到“鱼类”这一类别。

此外,如果没有更具体的分类,某一名词也可以被归类到非叶子结点的类别中。

例如,要确定“猫”的类别,系统可以向用户提出“猫是否属于动物”,当用户选择“是”时,系统会进一步分别询问“猫”是否属于“鱼类”和“鸟类”,当两个问题收到了否定的答案后,系统会确定“猫”的类别是“动物”。

大数据中心根据此前的经验,已经知道了一个名词属于各个类别的可能性大小。

为了用尽量少的问题确定某一名词的类别,大数据中心希望小 C 来设计一个方法,以减少系统向用户提出的问题的数量。

小 C 观察了事先收集到的数据,并加以统计,得到了一个名词属于各个类别的可能性大小的信息。

具体而言,每个类别都可以赋予一个被称为权重的值,值越大,说明一个名词属于该类别的可能性越大。

由于每次向用户的询问可以获得两种回答,小 C 联想到了二分策略。他设计的策略如下:

  1. 对于每一个类别,统计它和其全部后代类别的权重之和,同时统计其余全部类别的权重之和,并求二者差值的绝对值,计为 wδδ;
  2. 选择 wδδ 最小的类别,如果有多个,则选取编号最小的那一个,向用户询问名词是否属于该类别;
  3. 如果用户回答“是”,则仅保留该类别及其后代类别,否则仅保留其余类别;
  4. 重复步骤 11,直到只剩下一个类别,此时即可确定名词的类别。

小 C 请你帮忙编写一个程序,来测试这个策略的有效性。

你的程序首先读取到所有的类别及其上级次级关系,以及每个类别的权重。

你的程序需要测试对于被归类到给定类别的名词,按照上述策略提问,向用户提出的所有问题。

输入格式

输入的第一行包含空格分隔的两个正整数 n 和 m,分别表示全部类别的数量和需要测试的类别的数量。所有的类别从 11 到 n 编号,其中编号为 11 的是根类别。

输入的第二行包含 n 个空格分隔的正整数 w1,w2,…,wn1,2,…,,其中第 i 个数 wi 表示编号为 i 的类别的权重。

输入的第三行包含 n−1−1 个空格分隔的正整数 p2,p3,…,pn2,3,…,,其中第 i 个数 pi+1+1 表示编号为 i+1+1 的类别的上级类别的编号,其中 pi∈[1,n]∈[1,]。

接下来输入 m 行,每行一个正整数,表示需要测试的类别编号。

输出格式

输出 m 行,每行表示对一个被测试的类别的测试结果。表示按小 C 的询问策略,对属于给定的被测类别的名词,需要依次向用户提出的问题。

每行包含若干空格分隔的正整数,每个正整数表示一个问题中包含的类别的编号,按照提问的顺序输出。

数据范围

对 20% 的数据,各个类别的权重相等,且每个类别的上级类别都是根类别;
对另外 20% 的数据,每个类别的权重相等,且每个类别至多有一个下级类别;
对 60%的数据,有 n≤100,且 m≤10;
对 100% 的数据,有 1≤n≤2000,1≤m≤100,且 1≤wi≤107。

思路:

想办法更新节点信息,使其不会tle,方法很多。

tle代码:

#include
using namespace std;
typedef long long LL;
const int N = 2020;
struct P {
    //int last;
    int val;
    LL sum;
    //LL cha;
}p[N];
bool st[N], st1[N],st2[N];
int h[N], e[N], ne[N], idx;
int w[N], la[N];
int n, m;
int cnt;
int x;
bool find(int u,int v) {
    if (u == v) {
        return true;
    }
    if (u == 1) {
        return false;
    }
    find(la[u],v);
}
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void pushup(int u) {
    LL sum = w[u];
    for (int i = h[u];i != -1;i = ne[i]) {
        int j = e[i];
        sum += p[j].sum;
    }
    p[u].sum = sum;
    p[u].val = w[u];
}
void built(int u) {
    if (!st1[u]) {
        p[u] = { w[u],w[u] };
        return;
    }
    for (int i = h[u];i != -1;i = ne[i]) {
        int j = e[i];
        built(j);
    }
    pushup(u);
}
void modify(int u) {
    if (st[u]) {
        p[u].sum = 0;
        return;
    }
    for (int i = h[u];i != -1;i = ne[i]) {
        int j = e[i];
        modify(j);
    }
    pushup(u);

}
void New(int u) {
    st[u] = true;
    if (!st1[u]) {
        //st[u] = true;
        cnt++;
        return;
    }
    for (int i = h[u];i != -1;i = ne[i]) {
        int j = e[i];
        New(j);
    }
}
void deNew(int u) {
    st[u] = false;
    if (!st1[u]) {
        return;
    }
    for (int i = h[u];i != -1;i = ne[i]) {
        int j = e[i];
        deNew(j);
    }
}
void solve()
{
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    for (int i = 1;i <= n;i++) {
        cin >> w[i];
    }for (int i = 2;i <= n;i++) {
        cin >> la[i];
        st1[la[i]] = true;
        add(la[i], i);
    }
    while (m--) {
        int flag = 1;
        built(1);
        //int x = 0;
        cin >> x;
        int k = 0;
        LL s[N];
        memset(st, false, sizeof(st));
        memset(s, 0, sizeof(s));
        cnt = 0;
        while (cnt

ac代码:(引用)

#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 2010;

int idx,h[N],e[N],ne[N],w[N];
int fa[N];
int n,m;
LL u[N],uu[N];
bool st[N];
//st是表示每个节点的状态,st=true说明这个节点在树中,可以访问,st=false说明这个节点已经被删除,不能访问

void add(int a,int b){
    ne[idx]=h[a];
    e[idx]=b;
    h[a]=idx++;
}

LL dfs(int x){
    LL sum=w[x];
    for(int i=h[x];i!=-1;i=ne[i]){
        int j=e[i];
        uu[j]=dfs(j);
        sum+=uu[j];
    }
    return sum;
}


//返回当前节点最有可能被查询的那个节点
int query(int head,LL all){
    int temp=head;
    for(int i=h[head];i!=-1;i=ne[i]){
        int j=e[i];
        if(!st[j]) continue;
        int p=query(j,all); //找到它的子节点最有可能被查询的那个节点
        if(abs(u[p]*2-all)>n>>m;
    for(int i=1;i<=n;i++) {
        cin>>w[i];
    }
    fa[1]=0;
    for(int i=2;i<=n;i++){
        int a;
        cin>>a;
        fa[i]=a;
        add(a,i);
    }
    uu[1]=dfs(1);
    while(m--){
        memset(st,true,sizeof st);
        memcpy(u,uu,sizeof uu);//每次迭代前利用uu重置u数组,典型的空间换时间
        int head=1;
        int x;
        cin>>x;//x是我们想要的那个节点
        while (1)
        {
            int t=query(head,u[head]);//t是现在要判断的节点 
		    bool fl=false;
			for(int i=h[head];i!=-1;i=ne[i]){
		        int j=e[i];
		        if(!st[j]) continue;
		        fl=true;
		    }
		    if(!fl) break;//当现在整个树只有一个节点时,退出循环
            cout<

 

你可能感兴趣的:(算法)