思路:树的重心,当以树的重心为根节点时,其任一子树的节点数不大于 n / 2,
所以当只有一个重心时,谁先到达重心谁就能赢,所以我们以树的重心为根节点,求出每个节点的深度,只要先手玩家的深度 >= 后手玩家,那么先手玩家必赢,所以当只有一个重心时,所以ans = n * (n - 1) / 2, 因为当深度相同时, 玩家位置可以任意交换, 所以ans 还加上深度相同时的匹配的点数量(具体看代码)
当有两个重心时, 第二个到达重心的玩家才能赢, 而且两个重心必定相邻, 所以我们分别以两个重心为根节点求出每个点深度(一个点只会被遍历一次,因为每个点所属的重心不同),然后减去位于不同重心深度却相同的匹配情况就好了
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e6 + 50;
LL n;
struct Edge
{
int to, next;
} edge[maxn * 2];
int k, head[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
vector<int> vec;
int getcn(int u, int pre){ // 找重心
int flag = 1;
int s = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(to == pre){
continue;
}
int t = getcn(to, u);
if(t > (n >> 1)){
flag = 0;
}
s += t;
}
if(n - s > (n >> 1)){
flag = 0;
}
if(flag){
vec.push_back(u);
}
return s;
}
int dp[maxn];
int gr[maxn]; // 记录所属的重心,只有一个时,全都属于一个重心
void dfs(int u, int pre, int g, int d){
dp[u] = d;
gr[u] = g;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(to == pre){
continue;
}
dfs(to, u, g, d + 1);
}
}
LL cnt[2][maxn];
int main() {
int t;
scanf("%d", &t);
while(t--){
k = 0;
vec.clear();
scanf("%I64d", &n);
for(int i = 0; i <= n; i++){
head[i] = -1;
dp[i] = 0;
cnt[0][i] = cnt[1][i] = 0;
gr[i] = 0;
}
for(int i = 2; i <= n; i++){
int to;
scanf("%d", &to);
add(i, to);
add(to, i);
}
getcn(1, -1);
LL ans = n * (n - 1) / 2;
if(vec.size() == 1){
dfs(vec[0], -1, 0, 0);
for(int i = 1; i <= n; i++){
cnt[0][dp[i]]++;
}
for(int i = 0; i <= n; i++){
ans += cnt[0][i] * (cnt[0][i] - 1) / 2;
}
} else{
dfs(vec[0], vec[1], 0, 0); // 因为不会经过父节点,所以不会访问对方的子节点
dfs(vec[1], vec[0], 1, 0);
for(int i = 1; i <= n; i++){
cnt[gr[i]][dp[i]]++; // 求出每个深度的点的个数
}
for(int i = 0; i <= n; i++){
ans += cnt[0][i] * (cnt[0][i] - 1) / 2;
ans += cnt[1][i] * (cnt[1][i] - 1) / 2;
ans -= cnt[0][i] * cnt[1][i]; // 位于不同重心,深度相同
}
}
printf("%I64d\n", ans);
}
}
B - Rectangles Gym - 101968B
思路:若能找到符合的方案,则一定存在一条垂直的线和水平的线是的垂直的线两边点数相等以及竖直的线两边点数相等,且不存在点在这两条线上,两条线会把区域分为四个格子,左下和右上匹配,左上和右下匹配,就是答案了,左上和右下的点数一定是相同的,左下和右上点数相同
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 60;
typedef long long LL;
struct date
{
int x, y;
} a[maxn], b[maxn];
bool cmp1(date A, date B){
return A.x < B.x;
}
bool cmp2(date A, date B){
return A.y < B.y;
}
LL mod = 1e9 + 7;
LL num[maxn];
int main(){
int t;
scanf("%d", &t);
num[1] = 1;
for(LL i = 2; i < maxn; i++){
num[i] = num[i - 1] * i % mod;
}
while(t--){
int n;
scanf("%d", &n);
for(int i = 1; i <= 2 * n; i++){
scanf("%d%d", &a[i].x, &a[i].y);
b[i].x = a[i].x;
b[i].y = a[i].y;
}
sort(a + 1, a + 2 * n + 1, cmp1);
int flag = 1;
if(a[n].x == a[n + 1].x){
flag = 0;
}
sort(b + 1, b + 2 * n + 1, cmp2);
if(b[n].y == b[n + 1].y){
flag = 0;
}
if(flag == 0){
printf("0\n");
continue;
}
double x = 1.0 * (a[n].x + a[n + 1].x) / 2;
double y = 1.0 * (b[n].y + b[n + 1].y) / 2;
LL cnt1 = 0;
for(int i = 1; i <= 2 * n; i++){ // 找出坐下的点数
if(a[i].x < x && a[i].y < y){
cnt1++;
}
}
LL cnt2 = n - cnt1; // n - cnt1就是左上的题目
LL ans = 1;
if(cnt2){
ans = ans * num[cnt2] % mod;
}
if(cnt1){
ans = ans * num[cnt1] % mod;
}
printf("%I64d\n", ans);
}
}
C - Function Gym - 101968C
思路:直接打表找规律,打表找出在一定的n的情况下,每个数被加了几次,发现第 i 个数被加 C(n + 1, i) - 1 次
这是打表代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 50;
int INF = 1e9;
int n, m;
int cnt[maxn];
void dfs(int le, int ri){
if(le == ri){
cnt[le]++;
return ;
}
for(int i = le; i <= ri; i++){
cnt[i]++;
}
dfs(le, ri - 1);
dfs(le + 1, ri);
}
int main(int argc, char const *argv[])
{
while(1){
memset(cnt, 0, sizeof(cnt));
scanf("%d", &n);
dfs(1, n);
for(int i = 1; i <= n; i++){
printf("%d ", cnt[i]);
}
printf("\n");
}
return 0;
}
这是AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 60;
typedef long long LL;
LL fac[maxn], inv[maxn];
LL n;
LL mod = 1e9 + 7;
LL pow(LL x, LL k){
LL ans = 1;
while(k){
if(k & 1){
ans = ans * x % mod;
}
x = x * x % mod;
k >>= 1;
}
return ans;
}
void init(){
fac[0] = inv[0] = 1;
for(int i = 1; i <= n; i++){
fac[i] = fac[i - 1] * i % mod;
}
inv[n] = pow(fac[n], mod - 2);
for(int i = n - 1; i >= 1; i--){
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL Get(LL a, LL b){
return fac[a] * inv[b] % mod * inv[a - b] % mod;
}
int ar[maxn];
int main(){
n = 1e6 + 10;
init();
int t;
scanf("%d", &t);
while(t--){
scanf("%I64d", &n);
LL ans = 0;
for(int i = 1; i <= n; i++){
LL x;
scanf("%I64d", &x);
ans = (ans + x * (Get(n + 1, i) - 1)) % mod;
}
printf("%I64d\n", ans);
}
return 0;
}
D - Two Sequences Gym - 101968D
思路:输入的时候用两个map分别记录每个序列所拥有的某个数的个数,将两个序列具有的数全都加到 lsh 数组里,然后unique该数组,然后枚举每一个数(因为unique过了,所以相同的数只会被枚举一次),比较两个序列所拥有的该数的个数,若不同,则记录一下,最后在分类讨论输出就好了
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int INF = 0x3f3f3f3f;
int a[maxn];
int b[maxn];
int lsh[maxn];
map<int, int> mmap1, mmap2;
int main(int argc, char const *argv[])
{
int t;
scanf("%d", &t);
while(t--){
mmap1.clear();
mmap2.clear();
int n, k;
int cnt = 0;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
mmap1[a[i]]++;
lsh[++cnt] = a[i];
}
for(int i = 1; i <= n; i++){
scanf("%d", &b[i]);
mmap2[b[i]]++;
lsh[++cnt] = b[i];
}
sort(lsh + 1, lsh + cnt + 1);
cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
int res = 0;
int numa = -1, numb = -1;
int flag = 1;
for(int i = 1; i <= cnt; i++){
if(mmap1[lsh[i]] != mmap2[lsh[i]]){
res++;
if(res > 2){
flag = 0;
break;
}
if(mmap1[lsh[i]] > mmap2[lsh[i]]){
if(numa != -1){
flag = 0;
break;
}
numa = lsh[i];
} else{
if(numb != -1){
flag = 0;
break;
}
numb = lsh[i];
}
}
}
if(flag == 0){
printf("NO\n");
continue;
}
if(res == 0){
printf("YES\n");
} else{
if(abs(numa - numb) <= k){
printf("YES\n");
} else{
printf("NO\n");
}
}
}
return 0;
}
E - Connecting Components Gym - 101968E
不会
F - Mirror Gym - 101968F
不会
G - TeddyBearsDay Gym - 101968G
思路: 分析一下容易看出,当子树(父节点是谁都无所谓)内部的供需不平衡时,才会从外部获取或者输出对应的差值,这样是可以保证费用最小(一开始还以为是最小费用流,一看数据范围。。。有点大。。。)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 60;
#define fi first
#define se second
typedef long long LL;
typedef pair<int, int> pii;
struct Edge
{
int to, next, w;
} edge[maxn * 2];
int k, head[maxn];
void add(int a, int b, int c){
edge[k].to = b;
edge[k].w = c;
edge[k].next = head[a];
head[a] = k++;
}
int in[maxn], out[maxn];
LL ans = 0;
pii dfs(int u, int pre, int w){
pii tmp = pii{in[u], out[u]}; // 用来记录包含这个节点在内的子树的供需
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to, w = edge[i].w;
if(to == pre){
continue;
}
pii p = dfs(to, u, w);
tmp.fi += p.fi, tmp.se += p.se;
}
ans += 1LL * abs(tmp.fi - tmp.se) * w; //该子树需要从父节点获得或者向父节点输出abs(tmp.fi - tmp.se)的泰迪熊
return tmp;
}
int main(int argc, char const *argv[])
{
int t;
scanf("%d", &t);
while(t--){
int n;
scanf("%d", &n);
ans = 0;
k = 0;
for(int i = 1; i <= n; i++){
head[i] = -1;
in[i] = out[i] = 0;
}
for(int i = 1; i < n; i++){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
}
int q;
scanf("%d", &q);
while(q--){
int a, b;
scanf("%d%d", &a, &b);
in[b]++;
out[a]++;
}
dfs(1, -1, 0);
printf("%I64d\n", ans);
}
return 0;
}
H - Win Strategy Gym - 101968H
思路:emmm。。。至今不是很理解这个题意。。。感觉没有很明确的定义时间。。。队友xjb写过了。。。就是一个01背包的变形
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int INF = 0x3f3f3f3f;
int main(int argc, char const *argv[])
{
int t;
scanf("%d", &t);
while(t--){
int dp[2000] = {0};
int n, L;
scanf("%d%d", &n, &L);
for(int i = 1; i <= n; i++){
int a, b;
scanf("%d%d", &a, &b);
for(int j = L; j >= a + b - 1; j--){
dp[j] = max(dp[j], dp[j - b] + 1);
}
}
printf("%d\n", dp[L]);
}
return 0;
}
I - Tours Gym - 101968I
思路:二分答案,然后模拟就好了,只有上一天没有用的车才能转移车站,上一天用过的车不能转移车站
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 60;
#define fi first
#define se second
typedef long long LL;
typedef pair<int, int> pii;
int n, m;
int r;
vector<int> vec[maxn];
int main(int argc, char const *argv[])
{
int t;
scanf("%d", &t);
while(t--){
scanf("%d%d%d", &n, &m, &r);
for(int i = 1; i <= n; i++){
vec[i].clear();
for(int j = 1; j <= m; j++){
int x;
scanf("%d", &x);
if(x % r == 0){ //算出所需的车辆
x /= r;
} else{
x = x / r + 1;
}
vec[i].push_back(x);
}
}
int ans = 0;
int le = 1, ri = 5e5;
while(le <= ri){
int flag = 1;
int mid = (le + ri) >> 1;
int x = mid;
int sum = 0;
for(int i = 1; i <= n; i++){
sum += vec[i][0];
}
if(mid < sum){
le = mid + 1;
continue;
}
x -= sum; // 记录第一天剩下的车
for(int j = 1; j < m; j++){
sum = 0;
int nx = 0;
for(int i = 1; i <= n; i++){
if(vec[i][j] > vec[i][j - 1]){
if(x >= vec[i][j] - vec[i][j - 1]){
x -= (vec[i][j] - vec[i][j - 1]);
} else{
flag = 0;
break;
}
} else{
nx += (vec[i][j - 1] - vec[i][j]); // 记录当天没有用的车
}
}
x += nx; // 记录这天空闲的所有车
if(flag == 0){
break;
}
}
if(flag){
ans = mid;
ri = mid - 1;
} else{
le = mid + 1;
}
}
printf("%d\n", ans);
}
return 0;
}
J - Restricted Vertex Cover Gym - 101968J
思路:一开始xjb染色,怎么染都不对。。。后来看了题解。。2-sat 。。恍然大悟。。。就是个2 - sat板子题,甚至不需要记录路径x代表这个点染色, x + n代表这个点不染色,先标记所有在特殊边上的点(我们叫特殊点),然后我们对每对相邻的(有边连接)特殊点处理,具体看代码, 这里2 - sat 的模板就是白书上的
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int n, m;
struct Edge
{
int to, next;
} edge[maxn * 2], edge2[maxn * 2];
int k, head[maxn];
int k2, head2[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
edge2[k2].to = a;
edge2[k2].next = head2[b];
head2[b] = k2++;
}
int vis[maxn];
struct date
{
int a, b, c;
} arr[maxn];
vector<int> vec;
void dfs(int u){
vis[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(vis[to]){
continue;
}
dfs(to);
}
vec.push_back(u);
}
int cmp[maxn];
void dfs2(int u, int x){
vis[u] = 1;
cmp[u] = x;
for(int i = head2[u]; i != -1; i = edge2[i].next){
int to = edge2[i].to;
if(!vis[to]){
dfs2(to, x);
}
}
}
int scc(){
memset(vis, 0, sizeof(vis));
vec.clear();
for(int i = 1; i <= 2 * n; i++){
if(!vis[i]){
dfs(i);
}
}
memset(vis, 0, sizeof(vis));
int num = 0;
int len = vec.size();
for(int i = len - 1; i >= 0; i--){
if(!vis[vec[i]]){
dfs2(vec[i], ++num);
}
}
return num;
}
int main(int argc, char const *argv[])
{
int t;
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
k = 0, k2 = 0;
memset(head, -1, sizeof(head));
memset(head2, -1, sizeof(head2));
for(int i = 0; i < m; i++){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(c){
vis[a] = vis[b] = 1;
}
arr[i].a = a;
arr[i].b = b;
arr[i].c = c;
}
for(int i = 0; i < m; i++){
int a, b, c;
a = arr[i].a;
b = arr[i].b;
c = arr[i].c;
if(vis[a] && vis[b]){
if(c){ // 特殊边
add(a, b + n);
add(b + n, a);
add(b, a + n);
add(a + n, b);
} else{ //非特殊边
add(a + n, b);
add(b + n, a);
}
}
}
scc();
int flag = 1;
for(int i = 1; i <= n; i++){
if(cmp[i] == cmp[i + n]){
flag = 0;
break;
}
}
if(flag){
printf("YES\n");
} else{
printf("NO\n");
}
}
return 0;
}