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;
}