我遇到的acm好题 1

本文深入探讨了多种与树形结构相关的算法问题,包括求点到点集距离、曼哈顿距离、森林的构建与操作、树的深度优先搜索与广度优先搜索。通过实例解析,展示了如何利用树的性质解决实际问题,并提供了相应的代码实现。同时,文章还涉及了动态规划、高精度计算和逆序对等概念,对于提升算法理解与编程能力具有很大帮助。
摘要由CSDN通过智能技术生成

K - Rooted Subtrees
这道题的题意看起来很玄妙,答案很简单,值得再次尝试。
题解

Another Coin Weighing Puzzle
构造

Alliances
这是一道求点到点集距离的经典问题。
思路
我们先求所有点的lca(点集的根节点) 。
1.如果点和lca不等于lca时,求dist(V , lca)
2.如果相等,说明在lca的子树(并不能确定点是否在点集内部)
我们需要对每个点(点集)的dfs序与之比较,找到最相近的两个点进行比较。
注:比较时,我们比较的时v与lca(v , Ver) Ver <- E

牛客练习赛4B
曼哈顿距离的经典模板题 。 y总课没有,就在这加一份。

牛客练习赛4C
这是数据结构的一道经典题,要想学号数据结构就要学着建森林,这维护了一个森林。森林对应于一个数的32位(二进制的32位)的总数目 , 每一个森林(我用了树状数组)放着相应下标元素相应位是否有1 。

牛客练习赛D
这道题的难度并不高,但是作为一个很好的思维题来提升树方面的知识完全足用, 首先这个数的度数可以确定一个树(特定条件下),有时候我们确定度数可以唯一确定一个树或者树的形状。

Factorial Surplus Tail
这题的思维很难想,代码也很难写,但是写出来真的很锻炼自己,这是一个类似于dp的做法,其实应该叫递归,csdn有很多题解,这道题是一个很好的思维题。

Fantasy Strange Tree
这题也是牛客上的一道思维题,我已经猜出来要以u分开讨论,我一直找不到合适的计算方式,结果答案给出了一个可行解,用dp表示四个可行向量,然后最后根据所加的情况依次计算答案,我脑子笨,在计算变量算了半天。但是还是有最终代码。

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std ;

const int N = 1e5 + 10 ;
const int mod = 1e9 + 7 ;
// a : 无 b : left c : right d : left and right
char str1[N] , str2[N] ;

int main()
{
	scanf("%s%s" , str1 + 1 , str2 + 1) ;
	int n = strlen(str1 + 1) ;
	int top = n ;
	int m = strlen(str2 + 1) ;	
	int a = 1 , b = 0 , c = 0 , d = 0 ;
	for(int i = 1 ; i <= m ; i ++)
	{
		int a1 = a , b1 = b , c1 = c , d1 = d ;
		if(str2[i] == 'L')
		{
			a = a1 + c1 ;
			b = a1 + b1 ;
			d = d1 + c1 ;
			c = 0 ;
		}
		else if(str2[i] == 'R')
		{
			a = a1 + b1 ;
			c = a1 + c1 ;
			d = d1 + b1 ;
			b = 0 ;
		}
		else
		{
			if(!top) continue ;
			char op = str1[top --] ;
			if(op == 'L')
				b ++ ;
			else 
				c ++ ;
		}
		a %= mod , b %= mod , c %= mod , d %= mod ;
		// cout << a << " " << b << " " << c << " " << d << endl ;
	}
	printf("%d" , ((a + b) % mod + (c + d) % mod) % mod) ;
}

Split
这是我遇到的贪心好题, 我以为是对半切是最好的,但是仔细看了大佬的代码之后,其实是等分切的得分最高。如果有大神能说出来等分切最高的原因,一定要私信我哦。

Task
这是我遇到的爆搜好题,我对于爆搜怎么去搜还是不太专业,思路还是很简单的,对于目前的情况可以采取全部横着刷,或者竖着刷到最小值,然后分开再爆搜。

点权和

这道题是真的难,我用了一个线段树套层序序列去做时间复杂度(3 logn * m)大概率常数太大,卡常了,结果题解给出了更吊的办法,我觉得我好菜,题目类似于dp,对于每个点设置了一个状态量,每次都去维护这个状态量。 这个可以看一看,代码可以不需要写,但是一定要试着写一写关键步骤。
我在这个放一下线段树只过了70%的代码(蒟蒻)。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std ;

const int N = 1e5 + 10 ;
const int M = 1e5 + 10 ;
const int mod = 19260817 ;

int h[N] , e[M] , ne[M] , idx ;
int fa[N] ;
int n , m ; 

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

struct Tnode
{
	int l , r ;
	long long val ;
	int tag ; 
} tr[N << 2] ;

void pushup(int u)
{
	auto &root = tr[u] , &left = tr[u << 1] , &right = tr[u << 1 | 1] ;
	root.val = left.val + right.val ;
}

void pushdown(int u)
{
	auto &root = tr[u] , &left = tr[u << 1] , &right = tr[u << 1 | 1] ;	
	if(root.tag)
	{
		left.val += 1ll * (left.r - left.l + 1) * root.tag ;
		right.val += 1ll * (right.r - right.l + 1) * root.tag ;
		left.tag += root.tag , right.tag += root.tag ;
		root.tag = 0 ; 
	}
}

void modify(int u , int l , int r , int v)
{
	if(tr[u].l >= l && tr[u].r <= r)
	{
		tr[u].val += (tr[u].r - tr[u].l + 1) * v ;
		tr[u].tag += v ;
		return ;
	}
	pushdown(u) ;
	int mid = tr[u].r + tr[u].l >> 1 ;
	if(l <= mid) modify(u << 1 , l , r , v) ;
	if(r > mid) modify(u << 1 | 1 , l , r , v) ;
	pushup(u) ;
}

int query(int u , int l , int r)
{
	if(tr[u].l >= l && tr[u].r <= r)	
		return tr[u].val ;
	pushdown(u) ;
	int mid = tr[u].r + tr[u].l >> 1 , res = 0 ;
	if(l <= mid) res += query(u << 1 , l , r) ;
	res %= mod ; 
	if(r > mid) res += query(u << 1 | 1 , l , r) ;
	res %= mod ;
	return res ; 
}

void build(int u , int l , int r)
{
	tr[u] = {l , r} ;
	if(l == r) return  ; 
	int mid = l + r >> 1; 
	build(u << 1 , l , mid) ;
	build(u << 1 | 1 , mid + 1 , r) ;
}

int tmp[N] ;
int q[N] , hh = 1 , tt = 1 ; 
bool st[N] ;
int L[N] , R[N] , id[N] ;

void bfs()
{
	q[tt ++] = 1 ;
	st[1] = true ;
	while(hh < tt)
	{
		int top = q[hh ++] ;
		for(int i = h[top] ; ~i ; i = ne[i])
		{
			int j = e[i] ;
			if(st[j]) continue ;
			st[j] = true ;
			q[tt ++] = j ;
		}
	}	
	for(int i = 1 ; i <= n ; i ++)
	{
		id[q[i]] = i ;
		int father = fa[q[i]] ;
		L[father] = min (L[father] , i) ;
		R[father] = i ; 
	}
}

int solve(int x)
{
	if(L[x] <= R[x])
		modify(1 , L[x] , R[x] , 1) ;
	modify(1 , id[x] , id[x] , 1) ;
	if(x != 1)
		modify(1 , id[fa[x]] , id[fa[x]] , 1) ;
	int res = 0 ;
	if(L[x] <= R[x])
		res += query(1 , L[x] , R[x]) ;
	res %= mod ;
	res += query(1 , id[x] , id[x]) ;
	res %= mod ;
	if(x != 1)
		res += query(1 , id[fa[x]] , id[fa[x]]) ;
	res %= mod ;
	return res ; 
}
int main(void)
{
	memset(L , 0x3f ,sizeof L) ;
	memset(h , -1 , sizeof h) ;
	scanf("%d%d" , &n , &m) ;
	build(1 , 1 , n) ;
	for(int i = 1 ; i < n ; i ++)
	{
		scanf("%d" , &fa[i + 1]) ;
		add(fa[i + 1] , i + 1) ;
	}
	bfs() ;
	int ans = 0 ;
	for(int i = 1 ; i <= m ; i ++)
	{
		int x ; 
		scanf("%d" , &x) ;
		ans = (ans + (1ll * i * solve(x)) % mod) % mod ;
	}
	printf("%d" , ans) ;
}

再看看按照标准答案做的最终答案,我只能说本蒟蒻有点太垃圾了。

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std ;

const int N = 1e5 + 10 ;
const int mod = 19260817 ;

int son[N] , sson[N] , me[N] ;
int fa[N] , sz[N] ;
int ans ;

int main(void)
{
	int n , m ; 
	scanf("%d%d" , &n , &m) ;
	for(int i = 2 ; i <= n ; i ++)	scanf("%d" , &fa[i]) , sz[fa[i]] ++ ;
	for(int i = 1 ; i <= m ; i ++)
	{
		int v ;
		scanf("%d" , &v) ;
		me[v] ++ ;
		if(fa[v]) son[fa[v]] ++ ;
		if(fa[fa[v]]) sson[fa[fa[v]]] ++ ;
		int ff = (me[fa[fa[v]]] + me[fa[v]] + son[fa[v]]) % mod ;
		int vv = (me[v] + me[fa[v]] + son[v]) % mod ;
		int ss = (son[v] + sson[v] + 1ll * sz[v] * me[v] % mod) % mod ; 
		ans = (ans + 1ll * i * (ff + vv + ss) % mod) % mod ;
	}	
	printf("%d" , ans) ;
}

珂朵莉的数列

这道题总体来说不是很难,没有题解说的那么难,其实这考察的是一对逆序对对答案的贡献,思路
可以私信我,然后当你用树状数组写完之后应该是这个样子。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std ;

const int N = 1e9 + 10 ;

int tx[N] ;

int lowbit(int x)
{
	return x & -x ;
}

void insert(int x , int v)
{
	for(int i = x ; i < N ; i += lowbit(i))	tx[i] += v ; 
}

int query(int x)
{
	int res = 0 ; 
	for(int i = x ; i ; i -= lowbit(i)) res += tx[i] ;
	return res ; 
}

int main(void)
{
	int n , ans = 0 , res = 0 ; 
	scanf("%d" , &n) ;
	for(int i = 1 ; i <= n ; i ++)
	{
		int t ; 
		scanf("%d" , &t) ;
		int qr = query(t - 1) ;
		qr = res - qr ; 
		ans += qr * (n - i + 1);
		res += i ; 
		insert(t , i) ;
	}	
	printf("%d" , ans) ;
}

这个时候你发现其实已经可以拿到20 %的分数了,为什么拿不到100%,这个时候我们需要考虑到一个很可怕的事情,- 高精度,呃 , 高精度啊,狗都不屑 , 爷不写了(逃)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值