HDU 4858 项目管理 : 均摊复杂度

题目描述:
我们建造了一个大项目!这个项目有n个节点,用很多边连接起来,并且这个项目是连通的!
两个节点间可能有多条边,不过一条边的两端必然是不同的节点。
每个节点都有一个能量值。

现在我们要编写一个项目管理软件,这个软件呢有两个操作:
1.给某个项目的能量值加上一个特定值。
2.询问跟一个项目相邻的项目的能量值之和。(如果有多条边就算多次,比如a和b有2条边,那么询问a的时候b的权值算2次)。

题解:
对于每个查询的答案,有两种获得答案的方式:

  1. 更新时单点更新点权val,查询时点x的答案为相邻点的val和,这种方式更新O(1),查询O(n)
  2. 更新时除了更新点权val,同时还更新周围点的答案ans,查询时直接输出答案,这种方式更新O(n),查询O(1)

均摊复杂度的思想:上面两种操作复杂度都不行,不管采用哪种,最坏复杂度都会达到O(n*m)。考虑以根号m为界限划分两种操作。对于一个点,如果这个点的度d < sqrt(m) , 可以通过遍历相邻点的权值和得到答案,如果这个点的d > sqrt(m),那么我们希望通过更新时预处理的方式使得答案可以直接输出而不用遍历邻接点。对于更新时预处理:如果一个点的d < sqrt(m) 我们可以在更新时更新这个点的所有相邻点的答案,但是一个点的d > sqrt(m) 怎么整?

对于d > sqrt(m) 的情况,他的邻接点分两种,一种是 d < sqrt(m)的点,这种点不需要去更新,因为它求答案的方式不是通过预处理而是查询时遍历邻接点来求。对于d > sqrt(m),就需要更新。

把复杂度降下来需要删边,将点分为重点和轻点,重点是度数 > sqrt(m)的点,轻点是度数 <= sqrt(m)的点,重点和邻接点中的重点连边,轻点和所有邻接点连边。

有没有可能一条边都删不掉?有的,完全图就是,但这种图点数只有sqrt(m)个,符合复杂度要求。
边越多,点就越少。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 100;
vector<int> g[maxn];
int t,n,m,q,block;
struct ss{
	int u,v;
}edg[maxn];
int d[maxn];
long long sum[maxn],val[maxn];
void add(int u,int v) {
	if(d[u] <= block || d[u] > block && d[v] > block) 
		g[u].push_back(v);
	if(d[v] <= block || d[u] > block && d[v] > block)
		g[v].push_back(u);
}
int main() {
	scanf("%d",&t);
	while(t--) {
		scanf("%d%d",&n,&m);
		memset(d,0,sizeof d);
		memset(val,0,sizeof val);
		memset(sum,0,sizeof sum);
		for(int i = 1; i <= n; i++)
			g[i].clear();
		for(int i = 1; i <= m; i++) {
			scanf("%d%d",&edg[i].u,&edg[i].v);
			d[edg[i].u]++;d[edg[i].v]++;
		}	
		block = sqrt(m);
		for(int i = 1; i <= m; i++) {
			int u = edg[i].u,v = edg[i].v;
			add(u,v);
		}
		scanf("%d",&q);
		for(int i = 1; i <= q; i++) {
			int p,x,y;
			scanf("%d",&p);
			if(p == 0) {
				scanf("%d%d",&x,&y);
				val[x] += y;
				for(int i = 0; i < g[x].size(); i++) {
					int v = g[x][i];
					sum[v] += y;
				}
			}
			else {
				scanf("%d",&x);
				if(d[x] <= block) {
					long long ans = 0;
					for(int i = 0; i < g[x].size(); i++) {
						int v = g[x][i];
						ans += val[v];
					}
					printf("%lld\n",ans);
				}
				else {
					printf("%lld\n",sum[x]);
				}
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值