CF Diary VI

4.30-7.2
\(10\) 题一篇 \(\texttt{>o<}\)

986C. AND Graph(*2500)

题意

\(m(1\le m\le2^n,0\le n\le22)\)个不同的在 \([0,2^n)\) 之间的数,两个数如果他们按位与之后的结果为 \(0\) ,那么就连一条无向边,求图中连通块的个数。

题解

考虑一个数 \(x\) 能连的所有节点为 \(x\oplus(2^n-1)\) 的子集,于是考虑对所有给出的数字 \(dfs\) ,直接暴力做相当于枚举了每个数的子集,复杂度 \(O(3^n)\) 必然不行。考虑对于每个给出的数 \(x\) ,从 \(x\oplus(2^n-1)\) 开始 \(dfs\) ,通过 \(dfs\) 时不断减少二进制中 \(1\) 的个数来到达其所有子集,并标记,表示与 \(x\) 连通,如果当前到了一个输入给出的数字 \(y\) ,则额外从 \(y\oplus(2^n-1)\) 开始按上述方法 \(dfs\) ,每个点的连边至多 \(n\) 条,于是复杂度 \(O(n2^n)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 100010;

int B[(1 << 22) + 5], N, M;
bool vis[(1 << 22) + 5], in[(1 << 22) + 5];

void dfs(int x)
{
	vis[x] = 1;
	int s = ((1 << N) - 1) ^ x;
	if (in[x] && !vis[s])
		dfs(s);
	for (int i = 0; i < N; i++)
	{
		if (((x >> i) & 1) && !vis[x ^ (1 << i)])
			dfs(x ^ (1 << i));
	}
}

void solve()
{
	int cnt = 0;
	for (int i = 1; i <= M; i++)
	{
		if (!vis[B[i]])
			vis[B[i]] = true, dfs(((1 << N) - 1) ^ B[i]), cnt++;
	}
	cout << cnt << endl;
}

int main()
{
	IOS, cin >> N >> M;
	for (int i = 1; i <= M; i++)
		cin >> B[i], in[B[i]] = true;
	solve();
	return 0;
}

P2827 [NOIP2016 提高组] 蚯蚓

\(\texttt{Difficulty:蓝}\)

题意

初始 \(n(1\le n\le10^5)\) 个蚯蚓,每秒把最长的按比例 \(0<p<1\) 切成两块(长度下取整)并加入蚯蚓群(每次切的比例相等,由题目给出),整个过程持续 \(m(0\le m\le7\cdot10^6)\) ,每次操作后,当轮未被切的蚯蚓长度全体加 \(q\) ,求每秒拿出要切的蚯蚓长度,以及 \(m\) 秒后所有蚯蚓的长度。

题解

蚯蚓长度的增加简单维护一个全局的增量即可轻松模拟切的过程,但用堆直接维护会超时,注意到每次蚯蚓切完后的新蚯蚓有单调性,设上次切的长度为 \(x\) ,本次切的为 \(y\) ,即 \(\lfloor px\rfloor+q \ge \lfloor py\rfloor\)\(\lfloor (1-p)x\rfloor+q \ge \lfloor (1-p)y\rfloor\) 。利用这个性质维护 \(3\) 个队列,一个维护初始蚯蚓,剩下两个分别维护切完后大蚯蚓和小蚯蚓,每次切完分别扔到对应队列末尾,由上述性质可知这些队列都单调,复杂度 \(O(nlogn+m)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, pair<LL, LL>>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 100010;

LL N, M, Q, U, V, T, add = 0, A[maxn];
queue<LL>que1, que2, que3;

void solve()
{
	sort(A + 1, A + N + 1);
	for (int i = N; i >= 1; i--)
		que2.push(A[i]);
	for (int i = 1; i <= M; i++)
	{
		LL K = -INF;
		if (que1.size())
			K = max(K, que1.front());
		if (que2.size())
			K = max(K, que2.front());
		if (que3.size())
			K = max(K, que3.front());
		K += add;
		if (que1.size() && K == que1.front() + add)
			que1.push(K * U / V - add - Q), que3.push(K - K * U / V - add - Q), que1.pop();
		else if(que2.size() && K == que2.front() + add)
			que1.push(K * U / V - add - Q), que3.push(K - K * U / V - add - Q), que2.pop();
		else
			que1.push(K * U / V - add - Q), que3.push(K - K * U / V - add - Q), que3.pop();
		if (i % T == 0)
			cout << K << ' ';
		add += Q;
	}
	cout << endl;
	LL rk = 0;
	while (true)
	{
		LL K = -INF;
		rk++;
		if (que1.size())
			K = max(K, que1.front());
		if (que2.size())
			K = max(K, que2.front());
		if (que3.size())
			K = max(K, que3.front());
		if (K == -INF)
			break;
		K += add;
		if (que1.size() && K == que1.front() + add)
			que1.pop();
		else if (que2.size() && K == que2.front() + add)
			que2.pop();
		else
			que3.pop();
		if (rk % T == 0)
			cout << K << ' ';
	}
	cout << endl;
}

int main()
{
	IOS, cin >> N >> M >> Q >> U >> V >> T;
	LL tmp;
	for (int i = 1; i <= N; i++)
		cin >> A[i];
	solve();
	return 0;
}

P3045 [USACO12FEB]Cow Coupons G

\(\texttt{Difficulty:蓝}\)

题意

\(N\) 头牛, \(K\) 张优惠券 \((1\le K\le N\le5\cdot10^4)\) ,每头牛原价 \(P_i\) ,优惠价 \(C_i(1\le P_i, C_i\le10^9)\)\(M(1\le M\le10^{14})\) 块钱,最多买多少牛。

题解

考虑在买够 \(K\) 头牛之前都是尽可能买 \(C_i\) 小的牛,因为此时买牛必然用券,如果买了 \(C_i\) 更大的显然不优。于是我们可以先买 \(C_i\)\(K\) 小的 \(K\) 头牛,之后再进行调整。考虑接下来买牛,要么是原价购入 \(P_i\) ,要么是用优惠卷购入,此时必须放弃之前优惠即 \(P_j-C_j\) 最小的优惠来用在新购入的牛身上,这样代价为 \(P_j-C_j+C_k\) 。我们之后不断贪心的以购入代价最小的牛,用 \(3\) 个大根堆来维护 \(C, P, P-C\) ,同时标记一下有哪些牛买过了即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 50010;
LL N, K, M, P[maxn], C[maxn];
bool used[maxn];
struct Cow {
	LL p, c, id;
	bool operator<(const Cow& rhs)const {
		return c < rhs.c;
	}
}A[maxn];
priority_queue<PII, vector<PII>, greater<PII>>que1, que2, que3;

bool cmp(const PII& a, const PII& b)
{
	return a.second < b.second;
}

void solve()
{
	LL sum = 0, ans = 0;
	sort(A + 1, A + N + 1);
	for (int i = 1; i <= K; i++)
	{
		if (sum + A[i].c <= M)
			sum += A[i].c, ans++, que3.push(PII(A[i].p - A[i].c, A[i].id)), used[A[i].id] = true;
		else
		{
			cout << ans << endl;
			return;
		}
	}
	for (int i = K + 1; i <= N; i++)
		que1.push(PII(A[i].p, A[i].id)), que2.push(PII(A[i].c, A[i].id));
	while (true)
	{
		LL mip = INF, mic = INF, mipc = INF;
		while (!que1.empty() && used[que1.top().second])
			que1.pop();
		if (que1.size())
			mip = min(mip, que1.top().first);
		while (!que2.empty() && used[que2.top().second])
			que2.pop();
		if (que2.size())
			mic = min(mic, que2.top().first);
		if (mic == INF && mip == INF)
			break;
		mipc = que3.top().first;
		if (mic != INF && mipc + mic < mip)
		{
			if (sum + mipc + mic <= M)
				sum += mipc + mic, ans++, que3.pop(), que3.push(PII(P[que2.top().second] - C[que2.top().second], que2.top().second)), used[que2.top().second] = true, que2.pop();
			else
			{
				cout << ans << endl;
				return;
			}
		}
		if (mip != INF && mipc + mic >= mip)
		{
			if (sum + mip <= M)
				sum += mip, ans++, used[que1.top().second] = true, que1.pop();
			else
			{
				cout << ans << endl;
				return;
			}
		}
	}
	cout << ans << endl;
}

int main()
{
	IOS, cin >> N >> K >> M;
	for (int i = 1; i <= N; i++)
		cin >> A[i].p >> A[i].c, A[i].id = i, P[i] = A[i].p, C[i] = A[i].c;
	solve();
	return 0;
}

AQT2312 成爷搭寄木

\(\texttt{Difficulty:Unknown}\)

题意

\(4\le n\le10^5\) 维超立方体,给定各维边长 \(a_i(1\le a_i\le10^{18})\) ,将其所有表面染色后划分为单位超立方体,求分别有多少个单位超立方体有 \(x(0\le x\le n)\) 面被染色。

题解

用一个 \(n\) 维向量表示一个单位超立方体的位置,容易发现若其中每有一个维度的坐标为 \(1\)\(a_i\) ,则有一面会被染色。先仅考虑 \(a_i>1\) 的情况,考虑二项式反演,记 \(f(i)\) 为先钦点 \(i\) 维满足要求,其他维任意的情况,\(g(k)\) 为恰有 \(k\) 面被涂色的答案,有

\[f(k)=2^k\cdot[x^k]\prod\limits_{i=1}^n(x+a_i) \]

其中 \([x^k]\prod\limits_{i=1}^n(x+a_i)\) 表示从 \(a_i\) 中选 \(n-k\) 个数相乘的所有方案求和,这个式子可以通过分治技巧计算,复杂度 \(\mathcal{O}(n\log^2n)\) ,之后可以用二项式反演的卷积形式求出所有 \(g(k)\)
如果边长中有 \(1\) ,相当于该立方体有一维消失,并且直接为其它立方体贡献两个染色面, 因此统计所有 \(1\) 的个数 \(cnt\) ,将所有 \(1\) 去掉降维后求的 \(g(k)\) ,之后将答案向右平移 \(2\cdot cnt\) 即可,总的复杂度 \(\mathcal{O}(n\log^2n)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 300010;
const LL inv2 = (mod + 1) / 2;

LL qpow(LL x, LL y)
{
	if (y < 0)
		return 0;
	LL r = 1;
	for (; y; y >>= 1, x = mul(x, x))
	{
		if (y & 1)
			r = mul(r, x);
	}
	return r;
}

namespace NTT
{
	LL rev[maxn], a[maxn], b[maxn], w[maxn], iw[maxn], len, l;
	LL init(LL x, LL y)
	{
		for (len = 1, l = 0; len <= x + y; len <<= 1, ++l)
			continue;
		for (LL i = 0; i < len; ++i)
			rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
		LL b = qpow(3, (mod - 1) / len), ib = qpow(b, mod - 2);
		w[len / 2] = iw[len / 2] = 1;
		for (LL i = 1; i < len / 2; ++i)
			w[len / 2 + i] = mul(w[len / 2 + i - 1], b), iw[len / 2 + i] = mul(iw[len / 2 + i - 1], ib);
		for (LL i = len / 2 - 1; i; --i)
			w[i] = w[i << 1], iw[i] = iw[i << 1];
		return len;
	}
	void ntt(LL* a, LL f)
	{
		for (LL i = 0; i < len; ++i)
		{
			if (i < rev[i])
				swap(a[i], a[rev[i]]);
		}
		for (LL i = 1, id = 1; i < len; i <<= 1, ++id)
		{
			for (LL j = 0; j < len; j += i << 1)
			{
				for (LL k = 0; k < i; ++k)
				{
					LL x = a[j + k], y = mul((f == 1 ? w[i + k] : iw[i + k]), a[i + j + k]);
					a[j + k] = (x + y >= mod ? x + y - mod : x + y);
					a[i + j + k] = (x - y < 0 ? x - y + mod : x - y);
				}
			}
		}
		if (f == -1)
		{
			LL in = qpow(len, mod - 2);
			for (LL i = 0; i < len; ++i)
				a[i] = mul(a[i], in);
		}
	}
	void solve(LL* f, LL* g, LL* s, LL x, LL y)
	{
		init(x, y);
		for (LL i = 0; i < len; i++)
			a[i] = f[i], b[i] = g[i];
		ntt(a, 1), ntt(b, 1);
		for (LL i = 0; i < len; ++i)
			a[i] = mul(a[i], b[i]);
		ntt(a, -1);
		for (LL i = 0; i < len; i++)
			s[i] = a[i];
	}
}
vector<int> tmp0[maxn];
LL al[maxn], ar[maxn], c[maxn], cnt = 0, tmp;
LL T, N, X[maxn], Y[maxn], Z[maxn], A[maxn * 2], B[maxn * 2], C[maxn * 2], f[maxn], invf[maxn];

LL F(LL i)
{
	return qpow(2ll, i) % mod * tmp0[1][N - i] % mod;
}

void conquer(LL* d, LL* c, int x, int l, int r)
{
	if (l == r)
	{
		tmp0[x].push_back(1), tmp0[x].push_back(d[l]);
		return;
	}
	int mid = (l + r) >> 1;
	conquer(d, c, x << 1, l, mid), conquer(d, c, x << 1 | 1, mid + 1, r);
	LL ll = tmp0[x << 1].size() - 1, lr = tmp0[x << 1 | 1].size() - 1;
	for (LL i = 0; i <= ll; i++)
		al[i] = tmp0[x << 1][i];
	for (LL i = 0; i <= lr; i++)
		ar[i] = tmp0[x << 1 | 1][i];
	NTT::solve(al, ar, c, ll, lr);
	for (LL i = 0; i <= ll + lr; i++)
		tmp0[x].push_back(c[i]);
	for (LL i = 0; i <= ll + lr; i++)
		al[i] = ar[i] = c[i] = 0;
}

void solve()
{
	if (cnt == T)
	{
		for (LL i = 0; i < T * 2; i++)
			cout << 0 << ' ';
		cout << 1 << endl;
		return;
	}
	cnt *= 2, f[0] = 1;
	for (LL i = 1; i <= 100000; i++)
		f[i] = f[i - 1] * i % mod;
	invf[100000] = qpow(f[100000], mod - 2);
	for (LL i = 100000; i; i--)
		invf[i - 1] = invf[i] * i % mod;
	conquer(X, Y, 1, 0, N);
	for (LL i = 0; i <= N; i++)
	{
		B[i] = ((i % 2 == 0 ? 1ll : -1ll) * invf[i] % mod + mod) % mod;
		A[N - i] = f[i] * F(i) % mod;
	}
	NTT::solve(A, B, C, N, N);
	for (LL i = 0; i < cnt; i++)
		cout << 0 << ' ';
	for (LL i = cnt; i <= cnt + N; i++)
		cout << C[N - i + cnt] * invf[i - cnt] % mod << ' ';
	for (LL i = cnt + N + 1; i <= T * 2; i++)
		cout << 0 << ' ';
	cout << endl;
}

int main()
{
	IOS, cin >> T, N = 0;
	for (int i = 1; i <= T; i++)
	{
		cin >> tmp;
		if (tmp == 1)
			cnt++;
		else
			tmp %= mod, X[++N] = tmp;
	}
	solve();
	return 0;
}

P5344 【XR-1】逛森林

\(\texttt{Difficulty:Purple}\)

题意

\(n(1\le n\le(5\cdot10^4)\) 个点, \(m(1\le m\le10^6)\) 次操作,每次操作:
\(1\ u_1\ v_1\ u_2\ v_2\ w\)\(u_1\)\(v_1\) 路径上所有点向 \(u_2\)\(v_2\) 路径上所有点连权值为 \(w\) 的有向边,若 \(u_1\)\(v_1\) 不连通或 \(u_2\)\(v_2\) 不连通则忽视该操作。
\(2\ u\ v\ w\)\(u\)\(v\) 连权值为 \(w\) 的无向边,若二点已由第二种操作的边连通,忽视此操作。
\((1\le w\le100)\) ,给定起点 \(S\) ,求 \(m\) 次操作后 \(S\) 到各点最短路。

题解

显然仅考虑加无向边的操作完成后是一棵森林,并且第一种的加边操作若合法则可以全部等到森林建成后处理。因此用并查集可以维护各点连通性筛选出所有需要处理的第一种操作。
对于区间加边考虑优化建图,最朴素的想法树剖之后线段树优化建图后跑最短路,但这种做法单次加边操作 \(\mathcal{O}(\log^2n)\) ,点数 \(\mathcal{O}(n+mlogn)\) ,要加 \(\mathcal{O}(m\log n)\) 次边,边数 \(\mathcal{O}(m\log^2n)\) ,最后求最短路时间 \(\mathcal{O}(m\log ^2n\log (m\log ^2n))\) ,本题中 \(m\approx n\log n\) ,于是复杂度约为 \(\mathcal{O}(m\log^3n)\) ,很寄。
由于在树上,考虑改用其他方式优化建图,即倍增优化建图。利用倍增求二点 \(lca\) 的过程,考虑将倍增数组中各指向 \(2^k\) 级祖先的链建立出、入点,之后各链形成类似树形的结果,用线段树优化建图那样的方式分别连接各级的链,对于原树上具体的一个点,相当于其既是出点,也是入点。
之后连边我们直接用求路径端点 \(lca\) 相同的方式爬树,每过一个链就将该链和对应该次连边的虚节点连边。需要注意如果端点一开始相同,直接由端点向虚点连边,爬树结束后仍差一个距离为 \(1\) 的链到 \(lca\) ,不要忘记对其各自连边。
单次连边过程 \(\mathcal{O}(\log n)\) ,点数 \(\mathcal{O}(m+nlogn)\) , 边数 \(\mathcal{O}(mlogn)\) , 最后求最短路时间 \(\mathcal{O}(m\log n\log (m\log n))\) ,复杂度约为 \(\mathcal{O}(m\log^2n)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 50010;
const int maxm = 1000010;
struct Edge {
	int to, val;
};
vector<Edge> G[maxm * 5];
int N, fa[17][maxn], dep[maxn], M, S, op[maxm], u1[maxm], v1[maxm], u2[maxm], v2[maxm], w[maxm], tot = 0, in[17][maxn], out[17][maxn];
bool can[maxm], vis[maxn];
void add_edge(int from, int to, int val)
{
	G[from].push_back(Edge{ to,val });
}

void dfs(int v, int p, int d)
{
	vis[v] = true, fa[0][v] = p, dep[v] = d, in[0][v] = ++tot, out[0][v] = ++tot;
	for (auto& [x, y] : G[v])
	{
		if (x != p)
			dfs(x, v, d + 1);
	}
}

void lca_init()
{
	for (int i = 1; i <= N; i++)
	{
		if (!vis[i])
			dfs(i, 0, 0);
	}
	for (int v = 1; v <= N; v++)
	{
		add_edge(in[0][v], v, 0), add_edge(in[0][v], fa[0][v], 0);
		add_edge(v, out[0][v], 0), add_edge(fa[0][v], out[0][v], 0);
	}
	for (int k = 0; k + 1 < 17; k++)
	{
		for (int v = 1; v <= N; v++)
		{
			if (!fa[k][v])
				fa[k + 1][v] = 0;
			else
			{
				fa[k + 1][v] = fa[k][fa[k][v]], in[k + 1][v] = ++tot, out[k + 1][v] = ++tot;
				add_edge(in[k + 1][v], in[k][v], 0), add_edge(in[k + 1][v], in[k][fa[k][v]], 0);
				add_edge(out[k][v], out[k + 1][v], 0), add_edge(out[k][fa[k][v]], out[k + 1][v], 0);
			}
		}
	}
}

void con(int u, int v, int a, int b, int p, int val)
{
	if (u == v)
		add_edge(u, p, val);
	if (dep[u] > dep[v])
		swap(u, v);
	for (int k = 0; k < 17; k++)
	{
		if (((dep[v] - dep[u]) >> k) & 1)
			add_edge(out[k][v], p, val), v = fa[k][v];
	}
	if (u != v)
	{
		for (int k = 16; k >= 0; k--)
		{
			if (fa[k][u] != fa[k][v])
				add_edge(out[k][u], p, val), add_edge(out[k][v], p, val), u = fa[k][u], v = fa[k][v];
		}
		add_edge(out[0][u], p, val), add_edge(out[0][v], p, val);
	}
	if (a == b)
		add_edge(p, a, 0);
	if (dep[a] > dep[b])
		swap(a, b);
	for (int k = 0; k < 17; k++)
	{
		if (((dep[b] - dep[a]) >> k) & 1)
			add_edge(p, in[k][b], 0), b = fa[k][b];
	}
	if (a != b)
	{
		for (int k = 16; k >= 0; k--)
		{
			if (fa[k][a] != fa[k][b])
				add_edge(p, in[k][a], 0), add_edge(p, in[k][b], 0), a = fa[k][a], b = fa[k][b];
		}
		add_edge(p, in[0][a], 0), add_edge(p, in[0][b], 0);
	}
}

int par[maxn], rk[maxn];

void dsu_init()
{
	for (int i = 1; i <= N; i++)
		par[i] = i, rk[i] = 0;
}

int find(int x)
{
	if (x == par[x])
		return x;
	return par[x] = find(par[x]);
}

void unite(int x, int y)
{
	x = find(x), y = find(y);
	if (x == y)
		return;
	if (rk[x] < rk[y])
		par[x] = y;
	else
	{
		par[y] = x;
		if (rk[x] == rk[y])
			rk[x]++;
	}
}

bool same(int x, int y)
{
	return find(x) == find(y);
}

int d[maxm * 5];
priority_queue<PII, vector<PII>, greater<PII>>que;

void dijkstra()
{
	mst(d, INF), d[S] = 0, que.push(PII(0, S));
	while (!que.empty())
	{
		PII p = que.top(); que.pop();
		int v = p.second;
		if (p.first > d[p.second])
			continue;
		for (auto& [to, cost] : G[v])
		{
			if (d[to] > d[v] + cost)
				d[to] = d[v] + cost, que.push(PII(d[to], to));
		}
	}
}

void solve()
{
	dsu_init(), tot = N;
	for (int i = 1; i <= M; i++)
	{
		cin >> op[i], can[i] = true;
		if (op[i] == 1)
		{
			cin >> u1[i] >> v1[i] >> u2[i] >> v2[i] >> w[i];
			if (!same(u1[i], v1[i]) || !same(u2[i], v2[i]))
				can[i] = false;
		}
		else
		{
			can[i] = false, cin >> u1[i] >> v1[i] >> w[i];
			if (!same(u1[i], v1[i]))
				unite(u1[i], v1[i]), add_edge(u1[i], v1[i], w[i]), add_edge(v1[i], u1[i], w[i]);
		}
	}
	lca_init();
	for (int i = 1; i <= M; i++)
	{
		if (!can[i])
			continue;
		con(u1[i], v1[i], u2[i], v2[i], ++tot, w[i]);
	}
	dijkstra();
	for (int i = 1; i <= N; i++)
		cout << (d[i] == inf ? -1 : d[i]) << ' ';
	cout << endl;
}

int main()
{
	IOS, cin >> N >> M >> S, solve();
	return 0;
}

P1966 [NOIP2013 提高组] 火柴排队

\(\texttt{Difficulty:Blue}\)
感觉已经变成洛谷 \(\text{Diary}\) 了(

题意

两个长为 \(n(1\le n\le10^5)\) 的序列 \(a,b(0\le a_i,b_i\le2^{31})\) ,每个序列内值两两不同,每次可交换任意序列中相邻的两个值,最少交换多少次使得 \(\sum(a_i^2-b_i^2)\) 最小。

代码

要求的最小值为 \(\sum a_i^2+b_i^2+2\sum a_ib_i\) ,于是要求 \(\sum a_ib_i\) 最大,结论是两个序列正序对应相乘求和的时候最大,于是转化为求逆序对,离散化之后处理一下第二个序列中每个数应在的位置,然后做完了。

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 100010;

int N, A[maxn], B[maxn], H[maxn], dat[maxn];
vector<int>S1, S2;

void add(int i, int x)
{
	while (i <= N)
		dat[i] += x, i += i & (-i);
}

LL sum(int i)
{
	LL ans = 0;
	while (i)
		ans += dat[i], i -= i & (-i);
	return ans;
}

void solve()
{
	sort(all(S1)), sort(all(S2));
	for (int i = 1; i <= N; i++)
		A[i] = upper_bound(all(S1), A[i]) - S1.begin(), H[A[i]] = i;
	for (int i = 1; i <= N; i++)
		B[i] = upper_bound(all(S2), B[i]) - S2.begin(), B[i] = H[B[i]];
	LL ans = 0;
	for (int i = 1; i <= N; i++)
		add(B[i], 1), ans += i - sum(B[i]);
	cout << ans % (100000000 - 3) << endl;
}

int main()
{
	IOS, cin >> N;
	for (int i = 1; i <= N; i++)
		cin >> A[i], S1.push_back(A[i]);
	for (int i = 1; i <= N; i++)
		cin >> B[i], S2.push_back(B[i]);
	solve();
	return 0;
}

P3586 [POI2015] LOG

\(\texttt{Difficulty:Purple}\)

题意

长为 \(n\) 的初始全为 \(0\) 的序列, \(m(1\le n,m\le10^6)\) 次操作,每次操作:
\(U\ k\ a\) 将位置 \(k\) 上的数改为 \(a\)
\(Z\ c\ s\) 每次从序列中选出 \(c\) 个正整数,全部减去 \(1\) ,询问是否能操作 \(s\) 次,各询问相互独立(不改变序列)
\((1\le k,c\le n\)\(0\le a\le10^9\)\(1\le s\le10^9)\)

题解

考虑询问实际上在问一件什么事情,对于当前序列中的每一个数,如果其大于 \(s\) ,那么直接改为 \(s\) ,因为至多只能被拿 \(s\) 次,之后考虑剩下的序列和如果 \(\ge c\cdot s\) 那么一定可行,有如下的构造方案,对于每个位置,如果其不足 \(s\) ,就不断用右侧离自己最近的非零位置挪到自己这里,这样构成的 \(c\cdot s\) 矩形中同一行一定来自不同的位置,因为一个位置的值如果作为“底座”,那么他上面挪走的值应当恰好填满了左侧相邻位置,又因为各位置 \(\le s\) ,两列同属该位置的元素必不会相交,于是构造合法。
之后离散化+树状数组维护即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 1000010;

LL N, M, dat[maxn][2], n = 1e6, X[maxn], Y[maxn], A[maxn], H[maxn];
char op[maxn];

void add(LL i, LL x, LL t)
{
	while (i <= n)
		dat[i][t] += x, i += i & (-i);
}

LL sum(LL i, LL t)
{
	LL ret = 0;
	while (i)
		ret += dat[i][t], i -= i & (-i);
	return ret;
}

void solve()
{
	vector<int>xs;
	for (int i = 1; i <= M; i++)
		cin >> op[i] >> X[i] >> Y[i], xs.push_back(Y[i]);
	sort(all(xs)), xs.erase(unique(all(xs)), xs.end());
	for (int i = 1; i <= M; i++)
	{
		LL tmp = Y[i];
		Y[i] = upper_bound(all(xs), Y[i]) - xs.begin(), H[Y[i]] = tmp;
	}
	for (int i = 1; i <= M; i++)
	{
		if (op[i] == 'U')
		{
			if (A[X[i]])
				add(A[X[i]], -H[A[X[i]]], 1), add(A[X[i]], -1, 0);
			A[X[i]] = Y[i], add(A[X[i]], H[A[X[i]]], 1), add(A[X[i]], 1, 0);
		}
		else
		{
			LL all = sum(Y[i] - 1, 1) + (sum(n, 0) - sum(Y[i] - 1, 0)) * H[Y[i]];
			cout << (all >= X[i] * H[Y[i]] ? "TAK" : "NIE") << endl;
		}
	}
}

int main()
{
	IOS, cin >> N >> M, solve();
	return 0;
}

P3960 [NOIP2017 提高组] 列队

\(\texttt{Difficulty:Purple}\)

题意

一个 \(n\cdot m(1\le n,m\le3\cdot10^5)\) 的方阵,位于 \(x\)\(y\) 列的人有编号 \((x-1)m+y\) ,每个人编号永远不会发生变动。现在有 \(q(1\le q\le3\cdot10^5)\) 次询问,每次询问 \(x,y\) ,将位于 \((x,y)\) 处的人离队,之后第 \(x\) 行其他人依次向左对齐,之后第 \(m\) 列的人依次向上对齐,最后将出队的人放回 \((n,m)\) 。每次询问需要回答出队的人的编号。

题解

对于每次出队操作,实际上相当于从第 \(x\) 行前 \(m-1\) 列取出一个人,并在该行第 \(m-1\) 列加入一个人,新加入的人实际上是从 第 \(m\) 列的第 \(x\) 行取出并加入,之后将最开始出队的人直接加入到第 \(m\) 列的第 \(n\) 行。可以发现,对于每行的前 \(m-1\) 列与方阵最后一列,需要用一个支持删除与末尾加入的数据结构来维护。直接上平衡树的话空间 \(\mathcal{O}(nm)\) ,比较爆炸,考虑用动态开点的线段树来解决问题,我们对需要维护的 \(n\) 行与最后一列各用一棵线段树来维护各区间上被删过的元素数量,每次询问,如果不是最后一列,则在对应行上查找第 \(y\) 个尚未离开的元素在行中的位置,对于整个过程中依次新加入该行第 \(m-1\) 列的元素,我们用一个 \(vector\) 来维护,这部分的顺序不会因为其他行或最后一列的操作而改变;如果查找出的位置 \(<m\) ,那么直接是原序列上的编号,否则去 \(vector\) 中对应的位置找。由于在第 \(x\) 行操作,之后还要对最后一列进行类似的操作,查找第 \(x\) 个没有删过的元素,加入该行 \(vector\) ,并将本次询问的答案,加入最后一列的 \(vector\) 当中;如果询问在最后一列上,则仅用对最后一列进行操作即可。每次查询后对被删除的位置更新对应的线段树即可,更多实现细节详见代码,时间空间复杂度均为 \(\mathcal{O}(q\log n)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 300010;

int N, M, Q, tot = 0, rt[maxn];
struct Node {
	int lc, rc, dat;
}tr[maxn * 30];
vector<LL>A[maxn];

int build()
{
	tot++, tr[tot].lc = tr[tot].rc = tr[tot].dat = 0;
	return tot;
}

void pushup(int p)
{
	tr[p].dat = tr[tr[p].lc].dat + tr[tr[p].rc].dat;
}

void modify(int p, int l, int r, int a)
{
	if (l + 1 == r)
	{
		tr[p].dat++;
		return;
	}
	int mid = (l + r) >> 1;
	if (a < mid)
	{
		if (!tr[p].lc)
			tr[p].lc = build();
		modify(tr[p].lc, l, mid, a);
	}
	else
	{
		if (!tr[p].rc)
			tr[p].rc = build();
		modify(tr[p].rc, mid, r, a);
	}
	pushup(p);
}

int query(int p, int l, int r, int k)
{
	if (!p || l + 1 == r)
		return l - 1 + k;
	int mid = (l + r) >> 1, lv = mid - l - tr[tr[p].lc].dat;
	if (k <= lv)
		return query(tr[p].lc, l, mid, k);
	else
		return query(tr[p].rc, mid, r, k - lv);
}

LL opb(LL x, LL y)
{
	int rk = query(rt[N + 1], 1, N + Q + 1, x);
	if (!rt[N + 1])
		rt[N + 1] = build();
	modify(rt[N + 1], 1, N + Q + 1, rk);
	LL ans;
	if (rk <= N)
		ans = (LL)rk * M;
	else
		ans = A[N + 1][rk - N - 1];
	if (y)
		A[N + 1].push_back(y);
	else
		A[N + 1].push_back(ans);
	return ans;
}

LL opa(LL x, LL y)
{
	int rk = query(rt[x], 1, M + Q + 1, y);
	if (!rt[x])
		rt[x] = build();
	modify(rt[x], 1, M + Q + 1, rk);
	LL ans;
	if (rk < M)
		ans = (LL)(x - 1) * M + rk;
	else
		ans = A[x][rk - M];
	A[x].push_back(opb(x, ans));
	return ans;
}

void solve()
{
	int x, y;
	for (int i = 1; i <= Q; i++)
	{		
		cin >> x >> y;
		if (y == M)
			cout << opb(x, 0) << endl;
		else
			cout << opa(x, y) << endl;
	}
}

int main()
{
	IOS, cin >> N >> M >> Q, solve();
	return 0;
}

6-9 1842E. Tenzing and Triangle

\(\texttt{Difficulty:2300}\)

题意

\(n(1\le n\le2\cdot10^5)\) 个点,一条直线 \(x+y=k(1\le k\le2\cdot10^5)\) ,可以画若干个底边与该直线重合的等腰直角三角形,每个三角形花费边长 \(\cdot A(1\le A\le10^4)\) ,删去三角形所覆盖的所有点,也可以直接删去单独的点,第 \(i\) 个点代价为 \(c_i\) ,求删掉所有点的最小代价。

题解

注意到如果两个三角形有交那么直接画一个大的显然不劣,于是我们考虑记 \(a(l,r)\) 为顶点在 \((l,k-r)\) 处的三角形能节省的开销,为 \(-(r-l+1)A\) 加上被其覆盖的所有点单独删去的费用和,考虑记 \(f_i\) 为仅考虑 \(x\le i\) 时能节省的最大花费,于是显然有两种转移, \(x=i\) 没有三角形覆盖,有 \(f_i=f_{i-1}\) ,有三角形覆盖,有 \(f_i=\max\limits_{j=-1}^{i-2}\{f_j+a(j+1,i)\}\) ,两种转移取最大即可,对于第二种转移这一坨记为 \(g_j\),当 \(i\to i+1\) 时,对所有 \(j\in[-1,i-1]\) ,会全部减去一个 \(A\) ,对与纵坐标在 \(k-i-1\) 上的所有点 \((x,k-i-1)\) ,每个点对所有 \(j\in[-1,x-1]\) ,加上 \(c_i\) ,最后对 \(j=i\) 加上 \(f_i\) ,用区间加查询区间 \(\max\) 的线段树维护即可,复杂度 \(\mathcal{O}(n\log n)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
#define lc p*2
#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
//const LL mod = 1000000007;
const LL mod = 998244353;
const int maxn = 200010;

LL N, K, A, f[maxn], g[maxn], sum = 0, h = 2;
vector<PII>P[maxn];
struct Node {
	LL l, r, dat, lazy;
}tr[maxn * 4];

void pushup(int p)
{
	tr[p].dat = max(tr[lc].dat, tr[rc].dat);
}

void pushdown(int p)
{
	if (tr[p].lazy)
	{
		tr[lc].dat += tr[p].lazy, tr[rc].dat += tr[p].lazy;
		tr[lc].lazy += tr[p].lazy, tr[rc].lazy += tr[p].lazy;
		tr[p].lazy = 0;
	}
}

void build(int p, int l, int r)
{
	tr[p].l = l, tr[p].r = r, tr[p].dat = 0;
	if (l + 1 == r)
		return;
	int mid = (l + r) >> 1;
	build(lc, l, mid), build(rc, mid, r), pushup(p);
}

void modify(int p, int l, int r, LL x)
{
	if (tr[p].l >= l && tr[p].r <= r)
	{
		tr[p].dat += x, tr[p].lazy += x;
		return;
	}
	pushdown(p);
	int mid = (tr[p].l + tr[p].r) >> 1;
	if (r > mid)
		modify(rc, l, r, x);
	if (l < mid)
		modify(lc, l, r, x);
	pushup(p);
}

LL query(int p, int l, int r)
{
	if (r <= l)
		return 0;
	if (tr[p].l >= l && tr[p].r <= r)
		return tr[p].dat;
	pushdown(p);
	int mid = (tr[p].l + tr[p].r) >> 1;
	if (r <= mid)
		return query(lc, l, r);
	else if (l >= mid)
		return query(rc, l, r);
	else
		return max(query(lc, l, mid), query(rc, mid, r));
}

void solve()
{
	build(1, 1, K + 1 + h);
	for (int i = 0; i <= K; i++)
	{
		f[i + h] = max(f[i - 1 + h], query(1, 1, i - 1 + h)), modify(1, 1, i + h, -A), modify(1, i + h, i + 1 + h, f[i + h]);
		if (i < K)
		{
			for (auto& p : P[K - i - 1])
				modify(1, 1, p.first + h, p.second);
		}
	}
	cout << sum - f[K + h] << endl;
}

int main()
{
	IOS, cin >> N >> K >> A;
	LL x, y, c;
	for (int i = 1; i <= N; i++)
		cin >> x >> y >> c, P[y].push_back(PII(x, c)), sum += c;
	solve();
	return 0;
}

6-10 1842F. Tenzing and Tree

\(\texttt{Difficulty:2500}\)

题意

一棵 \(n(1\le n\le5000)\) 个节点的无根树,树上每个点有黑,白两种颜色,每条边的值为其连接的两个连通块中黑点数量的差,对于所有的 \(k\in[0,n]\) ,求出恰好有 \(k\) 个黑点时所有边权值总和的最大值。

题解

对于值的计算,取任意一个根都是等价的,为了去掉绝对值,我们不妨以所有黑点的重心为根,这样各子树中黑点数量不会超过总黑点数的一半,于是总边权可以写成 \(\sum\limits_{i\neq root}k-2cnt_i\)\(cnt_i\) 为以 \(i\) 为根子树中黑点数量,转化一下变为 \(k\cdot(n-1) - 2\sum\limits_{i\neq root}cnt_i\) ,考虑让 \(\sum\limits_{i\neq root}cnt_i\) 最小即可,每个黑点会对其所有祖先产生贡献,所以我们枚举根,之后按深度顺序依次染色计算答案即可,复杂度 \(\mathcal{O}(n^2)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 5010;

int N, ans[maxn], d[maxn];
vector<int>G[maxn];

void add_edge(int from, int to)
{
	G[from].push_back(to);
	G[to].push_back(from);
}

void solve()
{
	for (int rt = 1; rt <= N; rt++)
	{
		for (int i = 1; i <= N; i++)
			d[i] = -1;
		int k = 0, tmp = k * (N - 1);
		queue<int>que;
		que.push(rt), d[rt] = 0;
		while (!que.empty())
		{
			int v = que.front(); que.pop();
			k++, tmp += (N - 1), tmp -= 2 * d[v], ans[k] = max(ans[k], tmp);
			for (auto& to : G[v])
			{
				if (d[to] != -1)
					continue;
				d[to] = d[v] + 1, que.push(to);
			}
		}
	}
	for (int i = 0; i <= N; i++)
		cout << ans[i] << ' ';
	cout << endl;
}

int main()
{
	IOS, cin >> N;
	int u, v;
	for (int i = 1; i < N; i++)
		cin >> u >> v, add_edge(u, v);
	solve();
	return 0;
}
posted @ 2023-04-29 16:45  Prgl  阅读(78)  评论(0)    收藏  举报