2020.02.02日常总结——有趣的例题讲解

一 本 通 1478 : T h e   x o r − l o n g e s t   P a t h \color{green}{一本通1478:The\ xor-longest\ Path} 1478The xorlongest Path

【 题 意 】 : \color{blue}{【题意】:} 输入一棵 n n n个点的 带 权 树 \color{red}{带权树} (即每条边有边权的树),求树上最长异或路径。

【 数 据 范 围 】 : \color{blue}{【数据范围】:} w w w为边权,则有 1 ≤ w < 2 31 1\leq w < 2^{31} 1w<231 1 ≤ n ≤ 1 × 1 0 5 1 \leq n \leq 1 \times 10^5 1n1×105,且所有的点从 1 1 1 n n n编号。

【 思 路 】 : \color{blue}{【思路】:} 像这种,求树上路径的异或和或长度和等等可以利用前缀和算法维护的信息时,我们一般都要用到树上前缀和算法。

具体地,我们可以记 d i d_i di表示从根(无根树吗,随便找个点当根得了,我们就当它是 1 1 1吧)到 i i i的路径异或和。根据异或的性质,两点 ( x , y ) (x,y) (x,y)间路径的异或和即 d x d_x dx异或 d y d_y dy

于是,我们可以把所有的 d i ( 1 ≤ i ≤ n ) d_i(1 \leq i \leq n) di(1in)都求出来,然后把它们塞进一棵Trie内,问题就转化为了求两两数之间的异或最大值。这是一个Trie的简单应用题,这里不在介绍。

【 代 码 】 : \color{blue}{【代码】:}

const int N=1e5+100;
struct Tire{
	int ch[N*30][2],tot;//千万别只开ch[N][2]
	void init_trie(){
		memset(ch,-1,sizeof(ch));
		tot=0;
	}
	void insert(int x){
		register int u=0;
		for(int i=30;i>=0;i--){
			int c=x&(1<<i)?1:0;
			if (ch[u][c]==-1)
				ch[u][c]=++tot;
			u=ch[u][c];
		}
	}
	int query(int x){
		register int u=0,ans=0;
		for(int i=30;i>=0;i--){
			int c=x&(1<<i)?1:0;
			if (ch[u][1-c]!=-1){
				ans+=(1<<i);
				u=ch[u][1-c];
			}
			else u=ch[u][c];
		}
		return ans;
	}
}trie;//封装一个Trie的模板
struct node{
	int next,to,value;
}e[N<<1];int h[N],tot;
inline void add(int a,int b,int w){
	e[++tot]=(node){h[a],b,w};h[a]=tot;
}//链式前向星存图
int d[N],ans,n;
void dfs_init(int u,int fa){
	for(int i=h[u];i;i=e[i].next){
		register int to=e[i].to;
		if (to==fa) continue;
		d[to]=d[u]^e[i].value;
		dfs_init(to,u);
	}
}//求前缀异或和
int main(){
	n=read();ans=-1;
	trie.init_trie();//记得要初始化
	for(int i=1;i<n;i++){
		int u=read(),v=read(),w=read();
		add(u,v,w);add(v,u,w);
	}//因为是无根树,所以最好建立无向图
	dfs_init(1,-1);//别忘了调用
	for(int i=1;i<=n;i++){
		trie.insert(d[i]);
		ans=max(ans,trie.query(d[i]));
	}//先后顺序无所谓
	printf("%d",ans);
	return 0;
}

洛 谷 P 2261     [ C Q O I 2007 ] 余 数 求 和 \color{green}{洛谷P2261\ \ \ [CQOI2007]余数求和} P2261   [CQOI2007]

【 题 意 】 : \color{blue}{【题意】:} 给定 N , K N,K N,K,求( % \% %表示求余):
∑ i = 1 N K % i \sum^{N}_{i=1} K \%i i=1NK%i

【 数 据 范 围 】 : \color{blue}{【数据范围】:} 1 ≤ N , K ≤ 1 × 1 0 9 1 \leq N,K \leq 1 \times 10^9 1N,K1×109

【 思 路 】 : \color{blue}{【思路】:} 首先,我们来看看计算机是怎么计算求余的结果的。假设现在要求 P % Q P \% Q P%Q,结果为 X X X,我们的计算机会这么算:

X = P − Q × ⌊ P Q ⌋ X=P-Q \times \lfloor \frac{P}{Q} \rfloor X=PQ×QP

这给我们什么启示?我们发现 ⌊ P Q ⌋ \lfloor \frac{P}{Q} \rfloor QP变化的特别慢,至多会变化 P \sqrt P P 次, 1 × 1 0 9 < 1 × 1 0 5 \sqrt{1 \times 10^9}<1 \times 10^5 1×109 <1×105,而且所有令 ⌊ P Q ⌋ \lfloor \frac{P}{Q} \rfloor QP不变的 Q Q Q是连续的,我们完全可以考虑所有的 ⌊ P Q ⌋ \lfloor \frac{P}{Q} \rfloor QP的值,然后统计答案。

【 代 码 】 : \color{blue}{【代码】:}
在这里插入图片描述
【 后 记 】 : \color{blue}{【后记】:} 我们发现代码非常的简单,但也非常的不好理解,尤其是那个for循环。这里,我们简单讲讲。

在这里插入图片描述
它的作用是求出最大的 r r r,使得 ⌊ k l ⌋ = ⌊ k r ⌋ \lfloor \frac{k}{l} \rfloor=\lfloor \frac{k}{r} \rfloor lk=rk,根据它的特有的性质,我们可以知道对于任意 i ( l ≤ i ≤ r ) i(l \leq i \leq r) i(lir),有 ⌊ k i ⌋ = ⌊ k l ⌋ = ⌊ k r ⌋ \lfloor \frac{k}{i} \rfloor=\lfloor \frac{k}{l} \rfloor=\lfloor \frac{k}{r} \rfloor ik=lk=rk

在这里插入图片描述
这个减号是什么意思?其实是这样的,因为 X = P − Q × ⌊ P Q ⌋ X=P-Q \times \lfloor \frac{P}{Q} \rfloor X=PQ×QP,所以我们可以把所求式转化为:

∑ i = 1 N K % i = ∑ i = 1 N K − i × ⌊ K i ⌋ = N × K − ∑ i = 1 N i × ⌊ K i ⌋ \sum^{N}_{i=1} K \% i=\sum^{N}_{i=1} K-i \times \lfloor \frac{K}{i} \rfloor=N \times K - \sum^{N}_{i=1} i \times \lfloor \frac{K}{i} \rfloor i=1NK%i=i=1NKi×iK=N×Ki=1Ni×iK

因为上文已经说道 ⌊ K i ⌋ \lfloor \frac{K}{i} \rfloor iK都一样,所以原式可以继续变形为:

原 式 = N × K − ( ∑ i = 1 N i ) × ⌊ K l ⌋ 原式=N \times K-(\sum\limits_{i=1}^{N} i) \times \lfloor \frac{K}{l} \rfloor =N×K(i=1Ni)×lK

这样就不难理解为什么我们有这么一段代码了吧。

Q:实在不能理解怎么办?
A:自己举个例子,模拟一下。例子可以很好的帮助我们理解。
Q:数论题自己想不到,但一看题解就懂是怎么回事?
A:您这种情况跟笔者一模一样,笔者只能说:多刷题吧!
Q:您这些思路怎么来的?我怎么一开始没想到?
A:您好,我一开始也没想到,思路全部来自题解或老师的讲解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值