PAT甲级 1076 Forwards on Weibo(30)


原题链接

题目

在这里插入图片描述
在这里插入图片描述

题目翻译

微博被称为中国的推特。在微博上,一个用户可能有很多粉丝,也可能关注许多其他用户。因此,通过粉丝关系形成了一个社交网络。当一个用户在微博上发表帖子时,他/她所有的粉丝都可以查看和转发他/她的帖子,然后这些帖子可以被他们的粉丝再次转发。现在,假设只计算L级的间接粉丝,需要计算任何一个特定用户的最大潜在转发量。

输入格式
每个输入文件包含一个测试用例。对于每个案例,第一行包含两个正整数:N(≤1000),用户的数量;L(≤6),被计算的间接粉丝的级别数。因此,所有用户都被假设编号从1到N。然后是N行,每行的格式如下:

M[i] user_list[i]

其中 M[i](≤100)是用户[i]关注的总人数;user_list[i] 是用户[i]关注的 M[i] 个用户的列表。保证没有人会关注自己。所有数字之间用空格分隔。

然后最后给出一个正整数 K,后面跟着 K 个用户ID用于查询。

输出格式

对于每个用户ID,你需要在一行中打印出这个用户可以触发的最大潜在转发量,假设每个可以看到初始帖子的人都会转发一次,并且只计算L级的间接粉丝

基本思路

求一个用户的最大潜在转发量,就是一层一层地去找粉丝数,不超过L层。每次一层一层去找粉丝数时我们需要知道当前用户是在第几层?是否转发过?到这里,我一开始想到的是用dfs(深度优先搜索),但是最后一个用例超时了,应该是递归调用太多了,爆栈了。所以得用bfs(宽度优先搜索),将该层的用户编号和该用户的层数用一个队列存储起来,然后遍历每个用户的粉丝编号,如果没有转发过答案就+1,如果当前层数<最大层数L,就将它的粉丝编号和它的层数入队

深度优先搜索(dfs)

思路比较简单,先定义一个用户结构体userID,结构体里面存储vector容器,vector存储关注该用户的粉丝编号。再定义结构体数组user[M],将全部用户编号以及该用户的粉丝编号存储起来。

struct userID {
	vector<int> m;
}user[M];

再来看递归函数dfs的写法,将当前用户的粉丝编号遍历一遍,vis数组用来标记当前用户是否转发过,如果为false,最大转发量就+1,然后标记当前用户为true。然后再判断该层是否小于最大层数L,如果小于就继续递归该粉丝和下一层层数。

void dfs(int id,int l)
{
	for (int i = 0; i < user[id].m.size(); i++)
	{
		if (vis[user[id].m[i]] == false)
		{
			ans += 1;
			vis[user[id].m[i]] = true;
		}
		if(l<L)
		{
		    dfs(user[id].m[i],l+1);
		}
	}
}

完整代码

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int M = 1010;
typedef long long ll;
int N, L, K;
int k[M];
bool vis[M];
struct userID {
	vector<int> m;
}user[M];
ll ans = 0;
void dfs(int id,int l)
{
	for (int i = 0; i < user[id].m.size(); i++)
	{
		if (vis[user[id].m[i]] == false)
		{
			ans += 1;
			vis[user[id].m[i]] = true;
		}
		if(l<L)
		{
		    dfs(user[id].m[i],l+1);
		}
	}
}
int main()
{
	cin >> N >> L;
	for(int i=1;i<=N;i++)
	{
	    int u;
	    cin >> u;
		for (int j = 0; j < u; j++)
		{
			int x;
			cin >> x;
			user[x].m.push_back(i);
		}
	}
	cin >> K;
	for (int i = 0; i < K; i++)
	{
		cin >> k[i];
	}
	for (int i = 0; i < K; i++)
	{
		ans = 0;
		memset(vis, false, sizeof vis);
		vis[k[i]] = true;
		dfs(k[i], 1);
		cout << ans << endl;
	}
	return 0;
}

通过评测结果发现只过了90%的样例,最后一个运行超时。可能原因就是递归调用太多,导致栈溢出
在这里插入图片描述
递归搜索dfs不行,可以尝试使用非递归搜索bfs

宽度优先搜索(bfs)

延续上述思路,先创建一个队列,该队列存储的是对组pair<int,int>对组第一个数是用户编号id,第二个数是当前用户所在的层数

queue<pair<int, int>> q;
q.push({用户编号id,用户所在的层数});

步骤
(1)判断当前队列是否为空,如果不为空,获取队头,
(2)遍历该用户的所有关注者——>如果没有转发过则最大转发量+1(再将该关注者标志为转发过),判断该用户的当前层数是否小于最大层数L,如果小于则将该关注者的编号和所在层数入队,注意:关注者的所在层数要加一

q.push({ user[id].m[i],level + 1 });

将发表帖子的用户编号以及层数(层数为1)入队,再重复步骤(1)(2) 直到队列为空

void bfs(int id)
{
	queue<pair<int, int>> q;
	q.push({id,1});
	while (!q.empty())
	{
		auto node = q.front();
		q.pop();
		int id = node.first;
		int level = node.second;
		for (int i = 0; i < user[id].m.size(); i++)
		{
			if (vis[user[id].m[i]] == false)
			{
				ans += 1;
				vis[user[id].m[i]] = true;
			}
			if (level < L)
			{
				q.push({ user[id].m[i],level + 1 });
			}
		}
	}
}

完整代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int M = 1010;
typedef long long ll;
int N, L, K;
int k[M];
bool vis[M];
struct userID {
	vector<int> m;
}user[M];
ll ans = 0;
void bfs(int id)
{
	queue<pair<int, int>> q;
	q.push({id,1});
	while (!q.empty())
	{
		auto node = q.front();
		q.pop();
		int id = node.first;
		int level = node.second;
		for (int i = 0; i < user[id].m.size(); i++)
		{
			if (vis[user[id].m[i]] == false)
			{
				ans += 1;
				vis[user[id].m[i]] = true;
			}
			if (level < L)
			{
				q.push({ user[id].m[i],level + 1 });
			}
		}
	}
}
int main()
{
	cin >> N >> L;
	for(int i=1;i<=N;i++)
	{
	    int u;
		cin >> u;
		for (int j = 0; j < u; j++)
		{
			int x;
			cin >> x;
			user[x].m.push_back(i);
		}
	}
	cin >> K;
	for (int i = 0; i < K; i++)
	{
		cin >> k[i];
	}
	for (int i = 0; i < K; i++)
	{
		ans = 0;
		memset(vis, false, sizeof vis);
		vis[k[i]] = true;
		bfs(k[i]);
		cout << ans << endl;
	}
	return 0;
}

通过评测结果发现还是只过了90%的样例,报错结果是内存超限,那么可能的原因就是堆内存溢出
在这里插入图片描述
我们仔细看下面的代码可以发现:不管当前用户的关注者是否转发过帖子,只要当前用户的层数小于最大层数L,那么该用户的所有关注者都会入队。实际上是没有必要的,如果该用户的某个关注者已经转发过,再将其入队会造成重复查询(到下一层再查询该关注者的所有关注者时,发现已经全部转发过),使内存消耗更大,从而导致堆内存溢出。

for (int i = 0; i < user[id].m.size(); i++)
{
	if (vis[user[id].m[i]] == false)
	{
		ans += 1;
		vis[user[id].m[i]] = true;
	}
	if (level < L)
	{
		q.push({ user[id].m[i],level + 1 });
	}
}

那么应该怎么改?——>只有当前用户的关注者没有转发过且当前层数小于最大层数L,才能将关注者编号和下一层层数入队

for (int i = 0; i < user[id].m.size(); i++)
{
	if (vis[user[id].m[i]] == false)
	{
		ans += 1;
		vis[user[id].m[i]] = true;
		if (level < L)
		{
			q.push({ user[id].m[i],level + 1 });
		}
	}
}

这种写法相当于剪枝就是将没有必要搜索而且不影响结果的分支去除掉,这样既提高了搜索效率,也节省了计算资源

剪枝是一种在算法中减少搜索空间的技术,特别是在解决优化问题和决策问题时使用。它的核心思想是在搜索过程中提前排除那些不可能产生最优解或者不符合特定条件的分支,从而减少不必要的计算和资源消耗。

还有一种剪枝的写法(个人不建议)

for (int i = 0; i < user[id].m.size(); i++)
{
	//不推荐这种写法,因为可能还要将最高层的用户编号入队列,但是已经判断过最高层的用户是否转发过了
// 	if (vis[user[id].m[i]] == false && level<=L)
// 	{
// 		ans += 1;
// 		vis[user[id].m[i]] = true;
// 		q.push({ user[id].m[i],level + 1 });
// 	}

	//推荐这种写法
	if (vis[user[id].m[i]] == false)
	{
		ans += 1;
		vis[user[id].m[i]] = true;
		if (level < L)
		{
			q.push({ user[id].m[i],level + 1 });
		}
	}
}

完整代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int M = 1010;
typedef long long ll;
int N, L, K;
int k[M];
bool vis[M];
struct userID {
	vector<int> m;
}user[M];
ll ans = 0;
void bfs(int id)
{
	queue<pair<int, int>> q;
	q.push({id,1});
	while (!q.empty())
	{
		auto node = q.front();
		q.pop();
		int id = node.first;
		int level = node.second;
		for (int i = 0; i < user[id].m.size(); i++)
		{
			if (vis[user[id].m[i]] == false)
			{
				ans += 1;
				vis[user[id].m[i]] = true;
				if (level < L)
			    {
				    q.push({ user[id].m[i],level + 1 });
			    }
			}
		}
	}
}
int main()
{
	cin >> N >> L;
	for(int i=1;i<=N;i++)
	{
	    int u;
		cin >> u;
		for (int j = 0; j < u; j++)
		{
			int x;
			cin >> x;
			user[x].m.push_back(i);
		}
	}
	cin >> K;
	for (int i = 0; i < K; i++)
	{
		cin >> k[i];
	}
	for (int i = 0; i < K; i++)
	{
		ans = 0;
		memset(vis, false, sizeof vis);
		vis[k[i]] = true;
		bfs(k[i]);
		cout << ans << endl;
	}
	return 0;
}

通过评测结果发现通过了全部样例
在这里插入图片描述

个人的另一种写法

此写法直接开辟了二维动态数组,省去了创建结构体和结构体数组,但实际上大同小异。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int M = 1010;
typedef long long ll;
int N, L, K;
int k[M];
bool vis[M];
vector<vector<int>> vc(M);
ll ans = 0;
void bfs(int id)
{
    queue<pair<int, int>> q;
    q.push({id, 1});
    while (!q.empty())
    {
        auto node = q.front();
        q.pop();
        int id = node.first;
        int level = node.second;

        for (int i = 0; i < vc[id].size(); i++)
        {
            if (vis[vc[id][i]] == false)
            {
                ans += 1;
                vis[vc[id][i]] = true;
                if (level < L)
                {
                    q.push({vc[id][i], level + 1});
                }
            }
        }
    }
}
int main()
{
	cin >> N >> L;
	for(int i=1;i<=N;i++)
	{
	    int u;
		cin >> u;
		for (int j = 0; j < u; j++)
		{
			int x;
			cin >> x;
			vc[x].push_back(i);
		}
	}
	cin >> K;
	for (int i = 0; i < K; i++)
	{
		cin >> k[i];
	}
	for (int i = 0; i < K; i++)
	{
		ans = 0;
		memset(vis, false, sizeof vis);
		vis[k[i]] = true;
		bfs(k[i]);
		cout << ans << endl;
	}
	return 0;
}

评测结果显示通过了全部样例
在这里插入图片描述

yxc的写法

此写法用邻接表来存储图,需要注意的是一共有1000个点,每个点最多存100条边,所以点数N可以开1e3+10,边数M最多为1e5+10

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

//点数和边数
const int N=1e3+10,M=1e5+10;

int n,l;
int h[N],e[M],ne[M],idx; //用邻接表来存储图
bool st[N]; //状态数组

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

int bfs(int start){
    queue<int> q;
    memset(st, 0, sizeof st);

    q.push(start); //队列初始化
    st[start]=true;

    int res=0;
    //l为关注着的层数
    for(int i=0;i<l;i++){ //i为每层的关注者
        int sz=q.size();
        //注意:这个while中sz为每层关注着的人数,所以要暂存
        while(sz--){
            auto t=q.front();
            q.pop();
            //遍历start的第i层关注者
            for(int j=h[t];j!=-1;j=ne[j]){
                auto x=e[j]; //e[j]存放关注者的编号
                if(!st[x]){
                    q.push(x);
                    st[x]=true;
                    res++;
                }
            }
        }
    }

    return res;
}

int main(){
    cin>>n>>l;

    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        int cnt; //第i名用户关注的总人数
        cin>>cnt;
        while(cnt--){
            int x;
            cin>>x;
            add(x,i); //i是x的粉丝,让x指向i
        }
    }

    int m;
    cin>>m;
    while(m--){
        int x;
        cin>>x;
        cout<<bfs(x)<<endl;
    }

    return 0;
}

评测结果显示通过了全部样例
在这里插入图片描述

总结

这道题的英文单词有点难理解,比如:forward:转发follower:粉丝indirect:间接地

这些单词需要结合上下文才能慢慢读懂,比如forward很多人会翻译成向前的意思

对于深度优先搜索(dfs)和宽度优先搜索(bfs),我们优先考虑能不能使用bfs+剪枝一次通过,因为dfs的局限性有点大,数据稍微大一点就很容易超时(当然也可以通过dfs+剪枝来降低时间复杂度,但是不确定能不能通过)

到这里就结束啦,对以上内容有异议的欢迎大家来讨论。

内容概要:本文介绍了一个关于超声谐波成像中幅度调制聚焦超声所引起全场位移和应变的分析模型,并提供了基于Matlab的代码实现。该模型旨在精确模拟和分析在超声谐波成像过程中,由于幅度调制聚焦超声作用于生物组织时产生的力学效应,包括全场的位移与应变分布,从而为医学成像和治疗提供理论支持和技术超声谐波成像中幅度调制聚焦超声引起的全场位移和应变的分析模型(Matlab代码实现)手段。文中详细阐述了模型构建的物理基础、数学推导过程以及Matlab仿真流程,具有较强的理论深度与工程应用价值。; 适合人群:具备一定声学、生物医学工程或力学背景,熟悉Matlab编程,从事医学成像、超声技术或相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于超声弹性成像中的力学建模与仿真分析;②支持高强度聚焦超声(HIFU)治疗中的组织响应预测;③作为教学案例帮助理解超声与组织相互作用的物理机制;④为相关科研项目提供可复用的Matlab代码框架。; 阅读建议:建议读者结合超声物理和连续介质力学基础知识进行学习,重点关注模型假设、偏微分方程的数值求解方法及Matlab实现细节,建议动手运行并修改代码以加深理解,同时可拓展应用于其他超声成像或治疗场景的仿真研究。
### 关于PAT Basic Level Practice的测试点及题目解析 #### 题目难度分级 PAT(Programming Ability Test)是由浙江大学举办的计算机程序设计能力考试,分为不同级别。其中乙级即Basic Level主要面向初学者,考察基本编程技能[^1]。 #### 测试点特点 对于PAT Basic Level中的某些特定题目而言,其测试点设置较为严格。例如,在处理字符串匹配类问题时,需要注意算法逻辑中何时应当终止循环以防止不必要的重复计算;而在涉及数值运算的问题里,则可能因为边界条件而增加复杂度[^3]。 #### 编程语言的选择影响 值得注意的是,尽管大部分简单题目可以作为学习某种新语言的良好实践材料,但在实际操作过程中可能会遇到由于所选语言特性而导致难以通过全部测试点的情况。比如Java在面对部分效率敏感型试题时表现不佳,这可能是由于该语言本身的执行速度相对较慢以及内存管理方式等因素造成的。因此有时不得不转而采用其他更适合解决此类问题的语言版本来完成解答[^2]。 ```cpp #include<bits/stdc++.h> using namespace std; int a[100000]; int c=1; void getPrime(){ int flag=0; for(int i=2;i<105000;i++){ flag=1; for(int j=2;j<=sqrt(i);j++){ if(i%j==0){ flag=0; break; } } if(flag==1) a[c++]=i; } } int main(){ int m,n,i,t=1; scanf("%d %d",&m,&n); getPrime(); for(i=m;i<=n;i++){ if(t%10==1){ printf("%d",a[i]); t++; }else{ printf(" %d",a[i]); t++; } if((t-1)%10==0) printf("\n"); } return 0; } ``` 上述C++代码展示了如何实现一个简单的质数打印功能,并且针对输出格式进行了特殊处理以满足特定要求。这段代码很好地体现了编写高效解决方案的重要性,尤其是在应对像PAT这样的在线评测系统时[^4]。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值