首页 > 其他分享 >CF1420D & 102012G [线段交集问题]

CF1420D & 102012G [线段交集问题]

时间:2024-03-24 11:22:05浏览次数:33  
标签:return int 线段 102012G inv CF1420D read 端点 ll

CF1420D Rescue Nibel!

首先要发现一个性质:如果一些线段有交集,那么交集一定是条线段,并且一定有其中一条线段的左端点是交集的左端点。

所以方案可以转化为求其中一条线段的左端点是交集的左端点的方案数。

这启发我们枚举每个点作为交集的左端点,计算至少有一条线段的左端点是这个点的方案数。如果此时经过这个点的线段数是 \(p\),以这个点作为左端点的线段数是 \(s\),方案数即为 \(\binom{p}{k}-\binom{p-s}{k}\)。

不会重复的原因:因为每个交集都只有一个左端点,所以我们枚举左端点,每个方案只会被计算一次贡献。

离散化一下,枚举每个点计算贡献即可。

#include <bits/stdc++.h>
typedef long long ll;
ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	}
	return x * f;
}
ll n, k, tot, mod = 998244353, ans, now;
ll a[600010], l[300010], r[300010], cntl[600010], cntr[600010], inv[300010], fac[300010];
ll qpow(ll a, ll b) {
	ll ret = 1;
	while(b) {
		if(b & 1) ret = ret * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ret;
}
void init(int t) {
	fac[0] = 1;
	for(int i = 1; i <= t; i++) {
		fac[i] = fac[i - 1] * i % mod;
	}
	inv[t] = qpow(fac[t], mod - 2);
	for(int i = t - 1; i >= 0; i--) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	}
}
ll C(ll m, ll n) {
	if(m < n || m < 0 || n < 0) return 0;
	return fac[m] * inv[n] % mod * inv[m - n] % mod;
}
void Solve() {
	n = read(), k = read();
	init(300000);
	for(int i = 1; i <= n; i++) {
		l[i] = read(), r[i] = read();
		a[++tot] = l[i], a[++tot] = r[i];
	}
	std::sort(a + 1, a + tot + 1);
	int len = std::unique(a + 1, a + tot + 1) - a - 1;
	for(int i = 1; i <= n; i++) {
		l[i] = std::lower_bound(a + 1, a + len + 1, l[i]) - a;
		r[i] = std::lower_bound(a + 1, a + len + 1, r[i]) - a;
		cntl[l[i]]++, cntr[r[i] + 1]++;
	}
	for(int i = 1; i <= tot; i++) {
		now += cntl[i] - cntr[i];
		ans = (ans + C(now, k) - C(now - cntl[i], k) + mod) % mod; 
	}
	std::cout << ans << "\n";
}

int main() {
	
	Solve();

	return 0;
}

102012G Rikka with Intersections of Paths

求树上选 \(k\) 条路径至少有一个共同交点的方案数。

考虑转化,我们需要一个性质:如果树上两条路径有公共点,那么其中一定有一个公共点是某一条路径的两个端点的 \(lca\)。

所以我们转化为枚举公共点计算贡献。求经过公共点的路径中至少有 \(1\) 条路径的 \(lca\) 是公共点的方案数。发现这题求的形式和几乎一样,如果此时经过这个点的路径数是 \(p\),以这个点作为 \(lca\) 的路径数是 \(s\),方案数即为 \(\binom{p}{k}-\binom{p-s}{k}\)。

为什么这样不会重复?因为我们枚举的公共点是其中一条路径的 \(lca\),而其他路径的 \(lca\) 的深度一定都小于等于公共点(不然不可能经过公共点),也就是此时这个点是方案中深度最深的公共点,每个方案都有且只有一个最深公共点,所以每个方案只会被计算一次。

\(p\) 直接树上差分,\(s\) 不用说了。最后计算枚举点贡献即可。

#include <bits/stdc++.h>
typedef long long ll;
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	}
	return x * f;
}
int t, n, m, k, cnt, mod = 1000000007;
ll ans, fac[300010], inv[300010];
int h[300010], dep[300010], anc[300010][22];
int w[300010], w2[300010];
struct node{
	int to, nxt;
} e[600010];
void add(int u, int v) {
	e[++cnt].to = v;
	e[cnt].nxt = h[u];
	h[u] = cnt;
}
void dfs(int u, int fa) {
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == fa) continue;
		dep[v] = dep[u] + 1;
		anc[v][0] = u;
		dfs(v, u);
	}
}
void dfs2(int u, int fa) {
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if(v == fa) continue;
		dfs2(v, u);
		w[u] += w[v];
	}
}
ll qpow(ll a, ll b) {
	ll ret = 1;
	while(b) {
		if(b & 1) ret = ret * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ret;
}
void init() {
	for(int j = 1; j <= 19; j++) {
		for(int i = 1; i <= n; i++) {
			anc[i][j] = anc[anc[i][j - 1]][j - 1];
		}
	}
	
}
void setup(int cnt) {
	fac[0] = 1;
	for(int i = 1; i <= cnt; i++) {
		fac[i] = fac[i - 1] * i % mod;
	}
	inv[cnt] = qpow(fac[cnt], mod - 2);
	for(int i = cnt - 1; i >= 0; i--) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	}
}
int lca(int u, int v) {
	if(dep[u] < dep[v]) std::swap(u, v);
	for(int i = 19; i >= 0; i--) if(dep[anc[u][i]] >= dep[v]) u = anc[u][i];
	if(u == v) return u;
	for(int i = 19; i >= 0; i--) if(anc[u][i] != anc[v][i]) u = anc[u][i], v = anc[v][i];
	return anc[u][0];
}
ll C(ll m, ll n) {
	if(m < n || m < 0 || n < 0) return 0;
	return fac[m] * inv[n] % mod * inv[m - n] % mod;
}
void Solve() {
	cnt = 0;
	memset(h, 0, sizeof(h));
	memset(w, 0, sizeof(w));
	memset(w2, 0, sizeof(w2));  
	n = read(), m = read(), k = read();
	for(int i = 1; i < n; i++) {
		int u = read(), v = read();
		add(u, v), add(v, u);
	}
	dep[1] = 1;
	dfs(1, 0);
	init();
	for(int i = 1; i <= m; i++) {
		int u = read(), v = read();
		int rt = lca(u, v);
		w[u]++, w[v]++, w[rt]--, w[anc[rt][0]]--;
		w2[rt]++;
	}
	dfs2(1, 0);
	ans = 0;
	for(int i = 1; i <= n; i++) {
		// std::cout << w[i] << " " << w2[i] << "\n";
		ans = (ans + C(w[i], k) - C(w[i] - w2[i], k) + mod) % mod;
	}
	std::cout << ans << "\n";
}

int main() {
	setup(300000);
	t = read();

	while(t--) {
		Solve();
	}

	return 0;
}

标签:return,int,线段,102012G,inv,CF1420D,read,端点,ll
From: https://www.cnblogs.com/FireRaku/p/18092165

相关文章

  • 第二课——线段树
    上一节课讲了树状数组,也介绍了树状数组的优点与不足,这里简单回顾一下。优点:树状数组的代码非常简短,易于实现,被刘老师亲切的称为IO选手的"HelloWorld!",就是因为代码短。缺点:树状数组的缺点也非常的明显,只能处理单点修改区间查询或者区间修改单点查询的问题(以较高的效率)。而区间修......
  • CF938G-动态连通图最短xor路(线段树分治、并查集、线性基)
    link:https://codeforces.com/contest/938/problem/G[!Description]有一张连通的无向图简单图,三种操作:1、加边\((x,y,d)\)2、删边\((x,y)\)3、询问\(x\toy\)的路径中异或最小的路径(不一定是简单路径)保证每次操作后图是连通的\(1\leqn,m,q\leq2\times10^5\).这......
  • 楼房重建线段树
    link维护单调序列、点修的线段树。首先考虑一个楼房被看见的必要条件是前面没有斜率大于它的楼房,不等式可以推出来。然后本质上是维护一个从\(1\)号开始斜率单调上升的序列长度,注意,不能跳跃选择,即某栋楼房能加入序列则必须加入。线段树维护区间最大值和区间上升序列长度,由于......
  • 李超线段树
    模板题动态添加线段,求某个\(x\)对应的\(y\)最大是多少,且对应哪条直线。因为\(x\)比较小,考虑在\(x\)轴上建立线段树。把每个线段写成\(y=kx+b\)的解析式形式并求出它的定义域\([l,r]\),每条线段就可以看作是一个应用在\([l,r]\)上的区间修改。每次查询就是单点查询。......
  • P1712 [NOI2016] 区间 线段树+双指针
    //Problem://P1712[NOI2016]区间////Contest:Luogu//URL:https://www.luogu.com.cn/problem/P1712//MemoryLimit:250MB//TimeLimit:1000ms////PoweredbyCPEditor(https://cpeditor.org)#include<iostream>#include<algorithm......
  • CF1514D-区间(绝对)众数-莫队、随机化、可持久化线段树
    link:https://codeforces.com/contest/1514/problem/D很久以前小号打的场了,当时D题写的莫队,现在重新来看看。题意:给一个序列\([a_1,\dots,a_n]\),有q次询问,每次问:把\([a_l,\dots,a_r]\)划分最少几个不相交子序列,才能使得每个子序列是beautiful的。称一个序列\(a_1,\dots,a_x\)......
  • 大都市meg(线段树/树状数组+LCA)
    题目描述在经济全球化浪潮的影响下,习惯于漫步在清晨的乡间小路的邮递员BlueMary也开始骑着摩托车传递邮件了。不过,她经常回忆起以前在乡间漫步的情景。昔日,乡下有依次编号为1..n的n个小村庄,某些村庄之间有一些双向的土路。从每个村庄都恰好有一条路径到达村庄1(即比特堡)。并且......
  • 线段树模板
    线段树模板沉淀了很久,总算是掌握了线段树的经典模板了。但是还是需要深刻练习才能反复加深印象呢//线段树模板#include<bits/stdc++.h>usingnamespacestd;#defineintlonglongconstintN=1e5+10;intans[N*4];intt1[N*4],t2[N*4];inta[N];//存放输入数据intn,......
  • 数据结构——线段树 学习笔记
    数据结构——线段树学习笔记classseg_t{private:structemm{intl,r;structv;};vector<emm>a;voidpush_up(intk){}voidaction(intk,intv){}voidpush_down(intk){}voidbuild(vector<any>q,intk,intl,intr){a[k].l=......
  • 线段树(C++)
    线段树的本质就是树状数组,只不过线段树不再需要lowbit函数来定位对应数据的存储位置,取而代之的则是直接计算分叉结果位置。node结构体​ 通常而言,线段树所需要的存储空间约等于原数组的4倍。由于线段树需要存储区间的范围,所以我们需要自己定义一个新结构体来方便存储:constint......