深圳大学的一些简单题

A

打表,发现是这样的东西:

然后规律很显然,相邻的两个数,一组在左边,另一组在右边,依次循环,偶数的时候是 \(23\) 开头,奇数的时候是 \(12\) 开头,再处理一下 \(1\)\(n\) 就可以,比较简单的分讨

显然规律不止一个

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int a[1000001];
int main()
{
	int t;
	cin >> t;
	while(t--)
	{
		int n;
		cin >> n;
		if(n == 1)
		{
			cout << 1 << endl;
			continue;
		}
		else if(n == 2)
		{
			cout << "1 2" << endl;
			continue;
		}
		if(n % 2 == 1)
		{
			int mid = (n + 1) / 2;
			a[1] = n - 1;
			a[n] = n;
			a[mid] = 1;
			for(int i = 2; i < mid; i++)
			{
				if(i % 2 == 0)
					a[i] = a[i - 1] - 1;
				else
					a[i] = a[i - 1] - 3;
			}
			for(int i = n - 1; i > mid; i--)
			{
				if(i % 2 == 0)
					a[i] = a[i + 1] - 3;
				else
					a[i] = a[i + 1] - 1;
			}
		}
		else
		{
			int mid;
			if(n % 4 == 0)
				mid = n / 2 + 1;
			else
				mid = n / 2;
			a[1] = n - 1;
			a[n] = n;
			a[mid] = 1;
			for(int i = 2; i < mid; i++)
			{
				if(i % 2 == 0)
					a[i] = a[i - 1] - 1;
				else
					a[i] = a[i - 1] - 3;
			}
			for(int i = n - 1; i > mid; i--)
			{
				if(i % 2 == 1)
					a[i] = a[i + 1] - 3;
				else
					a[i] = a[i + 1] - 1;
			}
		}
		for(int i = 1; i <= n; i++)
			cout<<a[i]<<' ';
		cout<<endl;
	}
}

官解:

B

比较典的线段树,题目核心是找最小缺失编号、加/减球、移除最少的球使 \(x\) 是偶数,你可以用两个线段树

  • 一个主线段树(ST_all),作用:维护编号 \(1\)\(n\) 处的球数,支持区间更新,查询最小缺失编号 \(x\)

  • 偶数编号线段树(st_even):只储存偶数编号的球数,和找到最少需要移除的球数,使得缺失的编号为偶数

具体实现:用 ST_all 来二分查询出第一个个数为 \(0\) 的下标 \(j\),没有的话就令 \(j = n+1\);用 ST_even 维护偶数位置,对于一个偶数 \(i\),如果球数是 \(b_i\),那么如果使用魔法取出所有 \(i\) 号球,其代价为 \(b_i\),更新时,由于更新是按原下标区间给出的,我们只对落在 \([l,r]\) 中且下标为偶数的数字进行更新。

判断胜负与代价时,假设现在缺的是 \(j\)

  • \(j\) 是偶数,那么 \(Thephix\) 已经赢了,就是 \(0\)

  • \(j\) 是奇数,让缺失数字变为 \(i\),需要代价是 \(b_i\),显然取成本最低的那个就行,如果区间 \([2,j-1]\) 内没有偶数,就不能获胜,输出 \(-1\),注意如果一开始 \(b_1 = 0\),或第一个缺失数字 \(j=1\),那么无论如何都赢不了,输出 \(-1\)

因为魔法移除只对当前回合起作用,回合结束后恢复原状态,因此我们只在查询时“虚拟地”考虑移除操作,不修改主数据结构

复杂度 \(O(m \log n)\)

点击查看代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;
typedef long long ll;
const ll INF = 1LL << 60;
int a[500001];
// 构造偶数下标数组(下标从1开始)
vector<int> even_idx;
vector<ll> even_a;
// 线段树模板:支持区间加更新和区间最小值查询
struct ST
{
	int n;
	vector<ll> tree, lazy;
	ST(int n) : n(n)
	{
		tree.assign(4 * n, INF);
		lazy.assign(4 * n, 0);
	}
	void build(int idx, int l, int r)
	{
		if(l == r)
		{
			tree[idx] = a[l - 1];
			return;
		}
		int mid = (l + r) / 2;
		build(idx * 2, l, mid);
		build(idx * 2 + 1, mid + 1, r);
		tree[idx] = min(tree[idx * 2], tree[idx * 2 + 1]);
	}
	void push_down(int idx, int l, int r)
	{
		if(lazy[idx] != 0)
		{
			int mid = (l + r) / 2;
			lazy[idx * 2] += lazy[idx];
			lazy[idx * 2 + 1] += lazy[idx];
			tree[idx * 2] += lazy[idx];
			tree[idx * 2 + 1] += lazy[idx];
			lazy[idx] = 0;
		}
	}
	void update(int idx, int l, int r, int ql, int qr, ll val)
	{
		if(ql > r || qr < l)
			return;
		if(ql <= l && r <= qr)
		{
			tree[idx] += val;
			lazy[idx] += val;
			return;
		}
		push_down(idx, l, r);
		int mid = (l + r) / 2;
		update(idx * 2, l, mid, ql, qr, val);
		update(idx * 2 + 1, mid + 1, r, ql, qr, val);
		tree[idx] = min(tree[idx * 2], tree[idx * 2 + 1]);
	}
	ll queryMin(int idx, int l, int r, int ql, int qr)
	{
		if(ql > r || qr < l)
			return INF;
		if(ql <= l && r <= qr)
			return tree[idx];
		push_down(idx, l, r);
		int mid = (l + r) / 2;
		return min(queryMin(idx * 2, l, mid, ql, qr), queryMin(idx * 2 + 1, mid + 1, r, ql, qr));
	}
	// 返回区间[ql,qr]内,第一个值为0的下标,如果不存在返回 -1
	int q_first0(int idx, int l, int r, int ql, int qr)
	{
		if(ql > r || qr < l)
			return -1;
		if(ql <= l && r <= qr)
		{
			if(tree[idx] > 0)
				return -1; // 此区间没有0
			// 否则往下找
			if(l == r)
				return l;
		}
		push_down(idx, l, r);
		int mid = (l + r) / 2;
		int res = q_first0(idx * 2, l, mid, ql, qr);
		if(res != -1)
			return res;
		return q_first0(idx * 2 + 1, mid + 1, r, ql, qr);
	}
};
// 辅助函数:在一个排好序的偶数下标数组 even_idx 中,给定原区间 [L, R],返回在偶数数组中的左右下标区间
pair<int, int> get(int L, int R)
{
	int l = int(lower_bound(even_idx.begin(), even_idx.end(), L) - even_idx.begin());
	int r = int(upper_bound(even_idx.begin(), even_idx.end(), R) - even_idx.begin()) - 1;
	if(l > r)
		return {-1, -1};
	return {l, r};
}
struct ST_e
{
	int n;
	vector<ll> tree, lazy;
	ST_e(int n) : n(n)
	{
		tree.assign(4 * n, INF);
		lazy.assign(4 * n, 0);
	}
	void build(int idx, int l, int r)
	{
		if(l == r)
		{
			tree[idx] = even_a[l - 1];
			return;
		}
		int mid = (l + r) / 2;
		build(idx * 2, l, mid);
		build(idx * 2 + 1, mid + 1, r);
		tree[idx] = min(tree[idx * 2], tree[idx * 2 + 1]);
	}
	void push_down(int idx, int l, int r)
	{
		if(lazy[idx] != 0)
		{
			int mid = (l + r) / 2;
			lazy[idx * 2] += lazy[idx];
			lazy[idx * 2 + 1] += lazy[idx];
			tree[idx * 2] += lazy[idx];
			tree[idx * 2 + 1] += lazy[idx];
			lazy[idx] = 0;
		}
	}
	void update(int idx, int l, int r, int ql, int qr, ll val)
	{
		if(ql > r || qr < l)
			return;
		if(ql <= l && r <= qr)
		{
			tree[idx] += val;
			lazy[idx] += val;
			return;
		}
		push_down(idx, l, r);
		int mid = (l + r) / 2;
		update(idx * 2, l, mid, ql, qr, val);
		update(idx * 2 + 1, mid + 1, r, ql, qr, val);
		tree[idx] = min(tree[idx * 2], tree[idx * 2 + 1]);
	}
	ll queryMin(int idx, int l, int r, int ql, int qr)
	{
		if(ql > r || qr < l)
			return INF;
		if(ql <= l && r <= qr)
			return tree[idx];
		push_down(idx, l, r);
		int mid = (l + r) / 2;
		return min(queryMin(idx * 2, l, mid, ql, qr), queryMin(idx * 2 + 1, mid + 1, r, ql, qr));
	}
};
int n,m;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int n, m;
	cin >> n >> m;
	for(int i = 0; i < n; i++)
		cin >> a[i];
	// 建立两个线段树
	ST ST_all(n);
	ST_all.build(1, 1, n);
	for(int i = 1; i <= n; i++)
	{
		if(i % 2 == 0)
		{
			even_idx.push_back(i);
			even_a.push_back(a[i - 1]);
		}
	}
	int e_cnt = even_idx.size();
	ST_e ST_even(e_cnt);
	if(e_cnt > 0)
		ST_even.build(1, 1, e_cnt);
	// 每回合操作
	// 注意:每回合更新都是对当前状态累积的
	while(m--)
	{
		int l, r;
		ll k;
		cin >> l >> r >> k;
		// 更新主线段树
		ST_all.update(1, 1, n, l, r, k);
		// 更新偶数下标树:更新所有偶数 i in [l, r]
		if(e_cnt > 0)
		{
			// 在 even_idx 中寻找落在 [l, r] 的下标位置
			auto pr = get(l, r);
			if(pr.first != -1)
			{
				ST_even.update(1, 1, e_cnt, pr.first + 1, pr.second + 1, k);
			}
		}
		// 查询第一个值为0的下标
		int first0 = ST_all.q_first0(1, 1, n, 1, n);
		int j; // j为自然状态下箱子中最小缺失数字,下标从1开始,如果都非0,则 j = n+1
		if(first0 == -1)
		{
			j = n + 1;
		}
		else
		{
			j = first0;
		}
		// 如果 j == 1,则必定 b1==0,无论如何都无法获得胜利
		if(j == 1)
		{
			cout << -1 << endl;
			continue;
		}
		// 自然状态下,缺失数字为 j
		// 如果 j 为偶数,则 Thephix 获胜,代价为0
		if(j % 2 == 0)
		{
			cout << 0 << endl;
			continue;
		}
		// j 为奇数,则需要利用魔法取出某个偶数 i (< j) 的球,代价为 b_i
		// 在区间[1, j-1]中偶数下标集合:实际上就是偶数在区间 [2, j-1]
		if(j - 1 < 2)
		{
			// 区间中没有偶数
			cout << -1 << endl;
			continue;
		}
		// 在 even_idx 中查找属于 [2, j-1] 的下标
		auto pr = get(2, j - 1);
		if(pr.first == -1)
		{
			cout << -1 << endl;
			continue;
		}
		ll ans = ST_even.queryMin(1, 1, e_cnt, pr.first + 1, pr.second + 1);
		// ans 为区间中最小的球数,也即移除该偶数所需代价
		cout << ans << endl;
	}
	cout << "\n";
	return 0;
}

官解:

C

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int n;
	int t;
	cin >> t;
	long long k;
	while(t--)
	{
		cin >> n >> k;
		string s;
		cin >> s;
		if(k >= n)
		{
			cout << 0 << "\n";
			continue;
		}
		int ans = 0;
		for(int r = 0; r < k && r < n; r++)
		{
			map<int,int> mp;
			int cnt = 0;
			for(int i = r; i < n; i += k)
			{
				mp[s[i] - 'a']++;
				cnt++;
			}
			int a = 0;
			for(int j = 0; j < 26; j++)
			{
				a = max(a, mp[j]);
			}
			ans += (cnt - a);
		}
		cout << ans << "\n";
	}
	return 0;
}

D

不会

E

不会

F

很好的题,但是我不会做

G

不会

H

显然可以二分,然后模拟一下就可以了

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node
{
	int a, b;
} a[1000001];
int n;
bool check(ll t, ll p)
{
	ll ma = 0;
	ll nw = 0;
	for(int i = 1; i <= n; i++)
	{
		ll aa = a[i].a;
		ll b = a[i].b;
		if(b <= t)
		{
			nw += aa;
			if(nw < 0)
			{
				nw = 0;
			}
			ma = max(ma, nw);
		}
		else
		{
			nw = 0;
		}
		if(ma >= p)
		{
			return true;
		}
	}
	return ma >= p;
}
ll find(ll p)
{
	ll l = 1;
	ll r = 1e18;
	ll res = -1;
	while(l <= r)
	{
		ll mid = l + (r - l) / 2;
		if(check(mid, p))
		{
			res = mid;
			r = mid - 1;
		}
		else
		{
			l = mid + 1;
		}
	}
	return res;
}
int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		ll p;
		cin >> n >> p;
		for(int i = 1; i <= n; ++i)
		{
			cin >> a[i].a >> a[i].b;
		}
		ll mi = find(p);
		cout << mi << endl;
	}
	return 0;
}

官解:

I

不会

J

普通情况:如果至少有一个 \(a_i >1\),那么就是普通的 Nim 博宇,判断是否 \(nim=0\) 即可

所有堆都是 \(1\) 时:显然谁赢取决于 \(n\) 的奇偶性

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int a[1000001];
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(nullptr);
	int t;
	cin >> t;
	while(t--)
	{
		int n;
		cin >> n;
		int nim = 0;
		bool all1 = 1;
		for(int i = 0; i < n; i++)
		{
			cin >> a[i];
			nim ^= a[i];
			if(a[i] != 1)
				all1 = 0;
		}
		bool win = 0;
		if(all1)
		{
			// 如果所有堆都是1,先手获胜当堆数为偶数
			win = (n % 2 == 0);
		}
		else
		{
			// 否则按照正常nim(异或不为0则获胜)
			win = (nim != 0);
		}
		cout << (!win ? "Jiuhui" : "Akie") << "\n";
	}
	return 0;
}

官解:

K

不会

L

不会

M

不会

记录 \(uni\) 表示 \(i\) 波次的土包面积,\(uni_0 = 0,uni_{1<<i}\) 是第 \(i\) 个土包的面积

对于某个序列(例如我们选定集合 \(S\) 作为某一部分),我们“选定一个基波次作为首项”,比如设为 \(i\),序列价值就可以写成

\[k_i \times uni_{1<<i} + dp(1,0,A) \]

其中 \(A\) 是允许加入的波次集合,\(cur\) 是表示当前已经加入的允许波次(二进制位表示),那么当前的联合集合就是

\[(1<<i) \cup cur \]

然后接下来加入的波次 \(j\),新增面积是

\[uni_{(1<<i)\cup cur \cup (i<<j)}-uni_{(1<<i) \cup cur} \]

他的贡献为 \(k_i\) 乘上这个增量,状压 DP 一下,枚举所有加入顺序,转移状态是:

\[dp(i,cur,A) = \max_{j\in A} k_j\times K+dp(i,cur\cup j,A) \]

其中 \(K = uni_{(1<<i)\cup cur \cup (i<<j)}-uni_{(1<<i) \cup cur}\)

根据题意,最后答案是 \(\max best(s)+best(T)\)\(best(S) = \max_{i\in S} k_i \times uni_{1<<i} + dp(0)\),就是 \(cur = 0\)

复杂度高得离谱,肯定过不了,考虑优化

这一部分的思路没写代码,让 ds 仿照赛时代码写了一个,比较方便理解

点击查看代码
#include <bits/stdc++.h>
using namespace std;
// ----------------------
// 数据结构与凸包工具
// ----------------------
struct Point
{
	int x, y;
};
// 计算向量 OA 与 OB 的叉积 (O, A, B)
long long cross(const Point &O, const Point &A, const Point &B)
{
	return (long long)(A.x - O.x) * (B.y - O.y) - (long long)(A.y - O.y) * (B.x - O.x);
}
// 用 Andrew算法计算凸包,并返回“叉积和的绝对值”(即2倍面积,保证为整数);若不足3个点则返回0
long long convexHullArea(vector<Point> pts)
{
	int n = pts.size();
	if(n < 3)
		return 0;
	sort(pts.begin(), pts.end(), [](const Point & a, const Point & b)
	{
		return a.x == b.x ? a.y < b.y : a.x < b.x;
	});
	vector<Point> lower, upper;
	for(int i = 0; i < n; i++)
	{
		while(lower.size() >= 2 && cross(lower[lower.size() - 2], lower[lower.size() - 1], pts[i]) <= 0)
			lower.pop_back();
		lower.push_back(pts[i]);
	}
	for(int i = n - 1; i >= 0; i--)
	{
		while(upper.size() >= 2 && cross(upper[upper.size() - 2], upper[upper.size() - 1], pts[i]) <= 0)
			upper.pop_back();
		upper.push_back(pts[i]);
	}
	lower.pop_back();
	upper.pop_back();
	vector<Point> hull;
	for(auto &p : lower)
		hull.push_back(p);
	for(auto &p : upper)
		hull.push_back(p);
	long long area = 0;
	int sz = hull.size();
	for(int i = 0; i < sz; i++)
	{
		int j = (i + 1) % sz;
		area += (long long)hull[i].x * hull[j].y - (long long)hull[i].y * hull[j].x;
	}
	return llabs(area);
}
// ----------------------
// 全局变量与预处理
// ----------------------
int n;
vector<vector<Point>> waves; // waves[i]保存第 i 组点
vector<int> K;             // 每组的权值
vector<long long> unionAreaCache; // 对任一全局波次子集mask, unionAreaCache[mask]为其联合凸包面积(叉积和的绝对值)
// 预先计算每个mask对应的联合凸包面积
// 定义:mask=0时unionArea=0;mask中各bit表示选用了哪些波次
void computeUnionArea()
{
	int total = 1 << n;
	unionAreaCache.assign(total, 0);
	for(int mask = 1; mask < total; mask++)
	{
		vector<Point> pts;
		for(int i = 0; i < n; i++)
		{
			if(mask & (1 << i))
			{
				for(auto &p : waves[i])
					pts.push_back(p);
			}
		}
		long long area = convexHullArea(pts);
		unionAreaCache[mask] = area;
	}
	unionAreaCache[0] = 0;
}
// ----------------------
// DP求序列价值
// ----------------------
//
// 固定序列“首项”为波次 base(用全局索引表示),设允许添加的其他波次集合为A (A为全局mask,不含base)
// 状态 dp(base, cur, A) 表示:当前已添加的波次集合为cur(均是从A中取的),当前联合集合为 ( (1<<base) U cur ) ,
// 接下来可选 j ∈ A\cur,加入时新增面积 = unionArea[ (1<<base) U cur U (1<<j) ] - unionArea[ (1<<base) U cur ] ,
// 对应贡献为 K[j] 乘上该增量。
//
// dp(base, cur, A) = max_{j in A\cur} { K[j] * (Δ) + dp(base, cur∪{j}, A) },若A\cur为空则返回0。
// 为简化,每次调用时固定全局变量 g_base 与 g_allowed = A。
// 状态仅由cur决定。状态cur均为全局mask。
int g_base, g_allowed;
vector<long long> dpMemo; // 下标范围0..(1<<n)-1(但只用到属于g_allowed的子集)
long long dp_func(int cur)
{
	if(dpMemo[cur] != -1)
		return dpMemo[cur];
	long long best = 0;
	int rem = g_allowed & (~cur);
	for(int j = 0; j < n; j++)
	{
		if(rem & (1 << j))
		{
			int nxt = cur | (1 << j);
			int curUnion = cur | (1 << g_base); // 当前联合集合
			int nxtUnion = nxt | (1 << g_base);
			long long delta = unionAreaCache[nxtUnion] - unionAreaCache[curUnion];
			long long candidate = (long long)K[j] * delta + dp_func(nxt);
			best = max(best, candidate);
		}
	}
	dpMemo[cur] = best;
	return best;
}
// 对于给定的波次集合 S(全局mask),计算该部分最佳序列价值:
// best(S)= max_{i in S} { K[i]*unionAreaCache[1<<i] + dp_func(0) },其中dp的允许集合为 S\{i}.
long long bestForSet(int S)
{
	if(S == 0)
		return 0;
	long long bestVal = 0;
	for(int i = 0; i < n; i++)
	{
		if(S & (1 << i))
		{
			int allowed = S & ~(1 << i);
			long long init = (long long)K[i] * unionAreaCache[1 << i];
			g_base = i;
			g_allowed = allowed;
			dpMemo.assign(1 << n, -1);
			long long add = dp_func(0);
			bestVal = max(bestVal, init + add);
		}
	}
	return bestVal;
}
// ----------------------
// 主函数
// ----------------------
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	waves.resize(n);
	K.resize(n);
	// 依次读入每组数据:先读 m 和 k,然后 m 个点
	for(int i = 0; i < n; i++)
	{
		int m, k;
		cin >> m >> k;
		K[i] = k;
		waves[i].resize(m);
		for(int j = 0; j < m; j++)
		{
			cin >> waves[i][j].x >> waves[i][j].y;
		}
	}
	// 预处理:预计算所有波次集合的联合凸包面积
	computeUnionArea();
	// 枚举把 {0,1,...,n-1} 分成两部分:S 与 T(T为 S的补集),计算 bestForSet(S)+bestForSet(T) 的最大值
	int totalMask = (1 << n) - 1;
	long long ans = 0;
	for(int S = 0; S < (1 << n); S++)
	{
		int T = totalMask ^ S;
		long long curVal = bestForSet(S) + bestForSet(T);
		ans = max(ans, curVal);
	}
	cout << ans << "\n";
	return 0;
}

智障 ds 写了一年,还人工纠错了一下,AI 还是不能代替人类()

你会发现有很多重复的东西,所以要优化

\(unih_i\) 表示集合 \(mask\) 所有点合并后的土包

对于每个 \(mask\),找到其中最低位的 \(1\)(就是新增的组 \(P_i\)),有

\[unih_{mask} = convex(unih_{mask-i\cup P_i}),uni_{mask} = polyarea(unih_{mask}) \]

然后就预处理完了所有子集的联合土包和面积,直接查表就行

定义 \(f(mask)\) 表示子集 \(mask\) 按照最优顺序入队的最大价值,有

\[f(s)=\max_{i\in S} f(S-i)+k_i\times (uni_S-uni_{S-i}) \]

其中 \(S-i\) 就是 \(S\) 去掉 \(i\) 之后的集合,然后就做完了

点击查看代码
//板子来源:菜菜园子
//你可以dp,分割枚举
//然后t了
//然后你要减少计算,把一些重复的子集吃了
//还有一些更新时的奇技淫巧

#include <bits/stdc++.h>
using namespace std;
struct Point
{
	int x, y;
};
// 叉积
long long cross(const Point &O, const Point &A, const Point &B)
{
	return (long long)(A.x - O.x) * (B.y - O.y) - (long long)(A.y - O.y) * (B.x - O.x);
}
// Andrew 算法计算凸包,返回结果为逆时针顺序的凸包顶点(可能退化成一条线或单点,面积为0)
vector<Point> con(vector<Point>& pts)
{
	int n = pts.size();
	if(n <= 1)
		return pts;
	sort(pts.begin(), pts.end(), [](const Point & a, const Point & b)
		{
			return a.x == b.x ? a.y < b.y : a.x < b.x;
		});
	vector<Point> hull;
	// 下半部分
	for(int i = 0; i < n; i++)
	{
		while(hull.size() >= 2 && cross(hull[hull.size() - 2], hull[hull.size() - 1], pts[i]) <= 0)
			hull.pop_back();
		hull.push_back(pts[i]);
	}
	// 上半部分
	for(int i = n - 2, t = hull.size() + 1; i >= 0; i--)
	{
		while(hull.size() >= t && cross(hull[hull.size() - 2], hull[hull.size() - 1], pts[i]) <= 0)
			hull.pop_back();
		hull.push_back(pts[i]);
	}
	hull.pop_back();
	return hull;
}
// 合并两个凸包:直接合并两凸包所有点,再计算凸包(因为总点数较小)
vector<Point> merge(const vector<Point>& A, const vector<Point>& B)
{
	vector<Point> pts;
	pts.insert(pts.end(), A.begin(), A.end());
	pts.insert(pts.end(), B.begin(), B.end());
	if(pts.empty())
		return pts;
	return con(pts);
}
// 计算凸多边形面积的2倍(整数)
long long pol_area(const vector<Point>& poly)
{
	long long area = 0;
	int n = poly.size();
	for(int i = 0; i < n; i++)
	{
		int j = (i + 1) % n;
		area += (long long) poly[i].x * poly[j].y - (long long) poly[i].y * poly[j].x;
	}
	return abs(area);
}
// 全局变量
int n;
vector<vector<Point>> a;  // 原始点集(每组)
vector<vector<Point>> aaa;    // 每组的凸包(预处理后)
vector<int> K;                      // 权值
// 对于每个ii(非0),存储所有包含组的联合凸包以及对应的面积(2倍面积)
vector<vector<Point>> uni_h;
vector<long long> uni_s;
// 预处理:计算每个组的凸包
void init11()
{
	aaa.resize(n);
	for(int i = 0; i < n; i++)
	{
		vector<Point> pts = a[i];
		aaa[i] = con(pts);
	}
}
// 预处理:状态压缩DP计算所有子集的联合凸包及面积
void init()
{
	int tott = 1 << n;
	uni_h.assign(tott, vector<Point>());
	uni_s.assign(tott, 0);
	// 空集:面积为0
	uni_s[0] = 0;
	for(int ii = 1; ii < tott; ii++)
	{
		// 找最低位i
		int i = __builtin_ctz(ii);
		int prev = ii ^ (1 << i);
		if(prev == 0)
		{
			uni_h[ii] = aaa[i];
		}
		else
		{
			uni_h[ii] = merge(uni_h[prev], aaa[i]);
		}
		uni_s[ii] = pol_area(uni_h[ii]);
	}
}
// DP:对于子集ii,计算最优入队顺序的价值 F(ii)
// 状态转移:F(ii) = max_{i in ii} { F(ii without i) + K[i] * (uni_s[ii] - uni_s[ii without i]) }
vector<long long> dp;
long long computeDP(int tot)
{
	dp.assign(tot, 0);
	dp[0] = 0;
	// 枚举ii,采用按ii递增顺序(保证子状态已算好)
	for(int ii = 1; ii < tot; ii++)
	{
		long long maa = 0;
		// 枚举 ii 中的每个 i
		// 可以用位运算枚举
		int sub = ii;
		while(sub)
		{
			int i = __builtin_ctz(sub);
			int KK = ii ^ (1 << i);
			long long c = dp[KK] + (long long)K[i] * (uni_s[ii] - uni_s[KK]);
			maa = max(maa, c);
			sub &= (sub - 1);
		}
		dp[ii] = maa;
	}
	return dp[tot - 1];
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	a.resize(n);
	K.resize(n);
	for(int i = 0; i < n; i++)
	{
		int m, k;
		cin >> m >> k;
		K[i] = k;
		a[i].resize(m);
		for(int j = 0; j < m; j++)
		{
			cin >> a[i][j].x >> a[i][j].y;
		}
	}
	init11();
	init();
	int tott = 1 << n;
	vector<long long> F(tott, 0);
	F[0] = 0;
	for(int ii = 1; ii < tott; ii++)
	{
		long long maa = 0;
		int sub = ii;
		while(sub)
		{
			int i = __builtin_ctz(sub);
			int KK = ii ^ (1 << i);
			long long c = F[KK] + (long long)K[i] * (uni_s[ii] - uni_s[KK]);
			maa = max(maa, c);
			sub &= (sub - 1);
		}
		F[ii] = maa;
	}
	long long ans = 0;
	for(int i = 0; i < tott; i++)
	{
		int c = (tott - 1) ^ i;
		ans = max(ans, F[i] + F[c]);
	}
	cout << ans << endl;
	return 0;
}

官解:

翻了翻提交记录发现我竟然是最优解???

土包的一些前置芝士

posted @ 2025-03-29 18:17  OoXiao_QioO  阅读(24)  评论(0)    收藏  举报