2018.8.16校内训练

今天的题还是挺有价值的,尽管思考难度比较大……

T1

题面

给定一棵树,有两个人轮流在树上取点,假设\(A\)表示第一个人选择的点中连通块的个数,\(B\)表示第二个人的

第一个人想让\(A-B\)尽量大,第二个人反之

问都在最优策略的情况下,\(A-B\)最后是多少

\(n≤10^5\)

解法

挺妙的题

考虑这样一个事实,因为这是一棵树,所以\(A=V_A-E_A\),其中\(V_A\)表示第一个人选择的点的个数,\(E_A\)表示两端都是第一个人取的边的个数

显然,\(B\)同理

那么,最终要使\(A-B\)尽量大,就是\((V_A-E_A)-(V_B-E_B)\)尽量大

把这个式子拆开,就变成\(V_A-V_B+E_B-E_A\)

可以发现,\(V_A-V_B\)是一个定值,我们只要使\(E_B-E_A\)尽量大就可以了

然后考虑每条边边权为2,将边权平均分配在点上

那么可以发现,无论某一条边的两个端点怎么选择,都和选取边的情况是等价的

那么我们就把边上的问题转化成点上的问题了

所以可以发现,一个点如果点权越大,那么最优策略一定是在后面再选择它

发现每一个点的点权就是它度数的2倍,所以我们按照度数从小到大取,然后统计一下即可

时间复杂度:\(O(n\ log\ n)\)

代码

#include <bits/stdc++.h>
#define N 100010
using namespace std;
template <typename node> void read(node &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Edge {
	int next, num;
} e[N * 3];
struct Node {
	int s, id;
	bool operator < (const Node &a) const {
		return s < a.s;
	}
} a[N];
int cnt, s1, s2, col[N];
void add(int x, int y) {
	e[++cnt] = (Edge) {e[x].next, y};
	e[x].next = cnt;
}
void calc(int x, int fa) {
	for (int p = e[x].next; p; p = e[p].next) {
		int k = e[p].num;
		if (k == fa) continue;
		if (col[x] == col[k]) (col[x] == 1) ? s1-- : s2--;
		calc(k, x);
	}
}
int main() {
	freopen("guard.in", "r", stdin);
	freopen("guard.out", "w", stdout);
	int n; read(n); cnt = n;
	for (int i = 1; i <= n; i++) a[i].id = i;
	for (int i = 1; i < n; i++) {
		int x, y; read(x), read(y);
		add(x, y), add(y, x);
		a[x].s++, a[y].s++;
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++) col[a[i].id] = (i % 2 == 1);
	s1 = (n + 1) / 2, s2 = n - s1; calc(1, 0);
	cout << s1 - s2 << "\n";
	return 0;
}

T2

题面

给定\(n\)个位置,每一个位置可以填放\(1-m\)\(m\)种数,问满足不存在任意一段长度为\(m\)的区间\(m\)个数都出现过的数列有多少个

\(m≤100,m≤n≤10^{16}\)

解法

考虑这样一个\(dp\)

\(f_{i,j}\)表示第\(i\)位最后\(j\)位所有数均不相同的数列的个数

那么,我们考虑第\(i+1\)个数是什么样子

如果在之前的\(j\)个数中没有出现过,那么可以转移到\(f_{i+1,j+1}\)

如果已经出现过了,那么就可以转移到\(f_{i+1,k}\)\(k\)为那个数上一次出现的位置

然后可以发现,这个转移是不依赖于\(i\)的,即对于每一个\(i\),所有\(j\)的转移方式都是一样的

那么我们可以构建一个\(m×m\)的矩阵,每一次只要在这个矩阵上转移就行了

然后就可以矩阵快速幂来加速这个过程了

时间复杂度:\(O(m^3\ log\ n)\)

代码

#include <bits/stdc++.h>
#define Mod 1000000007
#define int long long
#define N 110
using namespace std;
struct Matrix {
	int a[N][N];
	void Clear() {memset(a, 0, sizeof(a));}
};
int n, m;
Matrix operator * (Matrix x, Matrix y) {
	Matrix ret; ret.Clear();
	for (int k = 1; k <= m; k++)
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= m; j++)
				ret.a[i][j] = (ret.a[i][j] + x.a[i][k] * y.a[k][j] % Mod) % Mod;
	return ret;
}
main() {
	freopen("intercept.in", "r", stdin);
	freopen("intercept.out", "w", stdout);
	cin >> n >> m;
	Matrix tx; tx.Clear();
	for (int i = 2; i <= m; i++)
		for (int j = i; j <= m; j++)
			tx.a[i][j] = 1;
	for (int i = 1; i <= m; i++) tx.a[i][i - 1] = m - i + 2;
	Matrix ty; ty.Clear();
	for (int i = 1; i <= m; i++) ty.a[i][i] = 1;
	while (n) {
		if (n & 1) ty = ty * tx;
		n >>= 1, tx = tx * tx;
	}
	int ans = 0;
	for (int i = 1; i <= m; i++) ans = (ans + ty.a[i][1]) % Mod;
	cout << ans << "\n";
	return 0;
}
posted @ 2018-08-16 17:09  谜のNOIP  阅读(78)  评论(0编辑  收藏  举报