这场AtCoder的题目以结论题为主
代码都很短,想法也都很巧妙
A - Cookie Exchanges
按照题目描述暴力即可
关键在于时间复杂度的证明
对于三个数
a
,
不妨令减去
对这三个数两两做差求和,变化前的和为
2c
,变化后的和为
c
,这个值缩小了一倍,当这个值为0的时候终止,至多只需要进行
#include <bits/stdc++.h>
using namespace std;
int main() {
int a,b,c,ans = 0;
cin >> a >> b >> c;
for (;;) {
if ((a&1) || (b&1) || (c&1)) { printf("%d\n",ans); break; }
if (a == b && b == c) { puts("-1"); break; }
ans++;
int na = (b + c) >> 1;
int nb = (a + c) >> 1;
int nc = (a + b) >> 1;
a = na, b = nb, c = nc;
}
return 0;
}
B - Unplanned Queries
原问题相当于对树上一条路径的边异或1
注意到是树边而不是点,单次操作(a,b)可以分解为(rt,a),(rt,b)两个操作,rt为树根,这两次异或操作对LCA(a,b)以上的的边的影响可以抵消。
结论:存在一棵符合题意的树当且仅当所有形如(rt,x)的操作中,每个x出现的次数都为偶数
充分性:所有的操作都做了偶数次,第
2i
次操作撤销第
2i−1
次操作,最后这棵树上的边权值一定都为初始值0;
必要性:若存在一种合法的方案,对某个点进行了奇数次操作并且存在一棵合法的树,取所有的被操作了奇数次点中深度最大的点,它的子树中除了它所有的点都被操作了偶数次,而该点被操作了奇数次,那么改点和它父亲的连边被操作了奇数次,矛盾。
最终解法只需要
O(N)
扫一遍判断每个操作是否进行了偶数次即可
#include <bits/stdc++.h>
#define N 1000500
using namespace std;
inline int rd() {int r;scanf("%d",&r);return r;}
int n,m,d[N];
int main() {
n = rd(), m = rd();
for (int i=1;i<=m;i++) d[rd()]++, d[rd()]++;
int ans = 1;
for (int i=1;i<=n;i++) if (d[i]&1) ans = 0;
puts(ans?"YES":"NO");
return 0;
}
C - Closed Rooms
操作相当于先移动K步,然后每次开K个门移动K步
先爆搜出从起点移动K步能到达的所有的点,这一步可以在
O(N∗M)
完成。
后面每次允许开K个门并且移动K步,相当于移动不受地形限制,朝四条边中距离它最近的一条边直线行走一定是最优的方案。对于一开始能移动到的没一个点都判断一次,同样这一步能够在
O(N∗M)
完成。
总时间是
O(N∗M)
#include <bits/stdc++.h>
#define N 1805
using namespace std;
const int fx[] = { 0, 1, 0,-1};
const int fy[] = { 1, 0,-1, 0};
int n,m,k,sx,sy;
char s[N];
bool vis[N][N],mp[N][N],flag;
queue<int> qx,qy,qs;
int main() {
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=n;i++) {
scanf("%s",s+1);
for (int j=1;j<=m;j++) mp[i][j] = s[j]!='#';
for (int j=1;j<=m;j++) if (s[j]=='S') sx=i, sy=j;
}
if (sx == 1 || sx == n || sy == 1 || sy == m) return puts("0"), 0;
vis[sx][sy] = 1;
qx.push(sx), qy.push(sy), qs.push(0);
while (!qx.empty()) {
int ux = qx.front(); qx.pop();
int uy = qy.front(); qy.pop();
int us = qs.front(); qs.pop();
if (us == k) continue;
if (ux == 1 || ux == n || uy == 1 || uy == m) flag = 1;
for (int i=0;i<4;i++) {
int vx = ux + fx[i], vy = uy + fy[i];
if (vx<1 || vx>n || vy<1 || vy>m) continue;
if (!vis[vx][vy] && mp[vx][vy]) {
vis[vx][vy] = 1;
qx.push(vx), qy.push(vy), qs.push(us+1);
}
}
}
if (flag) return puts("1"), 0;
int ans = 2147483647;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) if (vis[i][j]) {
int t = min( min(i-1,j-1) , min(n-i,m-j) );
int cur = (t+k-1) / k;
ans = min(ans, cur+1);
}
cout << ans << endl;
return 0;
}
D - Black and White Tree
结论:后手必胜当且仅当树存在完备匹配
充分性:对于先手每一次操作染色的点
u
,找到它的匹配点
必要性:先手每次找到最深的一个未染色点
x
,它的父亲为
若
若
复杂度取决于判断树是否存在完备匹配,用从叶子结点贪心的方法可以做到
#include <bits/stdc++.h>
#define N 1000500
using namespace std;
inline int rd() {int r;scanf("%d",&r);return r;}
vector<int> e[N];
int p[N],n;
void link(int a,int b) {e[a].push_back(b), e[b].push_back(a);}
void dfs(int u,int f) {
for (int i=0;i<(int)e[u].size();i++) {
int v=e[u][i]; if (v==f) continue;
dfs(v,u);
}
if (!p[u] && !p[f]) p[u] = p[f] = 1;
}
int main() {
n = rd();
for (int i=1;i<n;i++) link(rd(),rd());
p[0] = 1;
dfs(1,0);
int ans = 1;
for (int i=1;i<=n;i++) if (!p[i]) ans = 0;
puts(!ans?"First":"Second");
return 0;
}
E - Blue and Red Tree
若将一条蓝边删去,原来的树将被划分成两个连通块,新连的红边又将连接这两个连通块,并且这条红边是唯一连接这两个连通块的边。
每次选择两个连通块,满足这两个连通块间有且仅有一条蓝边和红边,连接这两个连通块。
用队列维护需要被处理的两个连通块,map维护连通块之间的边数,vector存边。启发式合并这些信息。
启发式合并一个log,map一个log
时间复杂度
O(Nlog22N)
#include <bits/stdc++.h>
#define x first
#define y second
#define N 100050
using namespace std;
typedef pair<int,int> pii;
map<pii,int> mp;
vector<int> e[N];
queue<pii> q;
int fa[N],siz[N],tot,n;
pii MP(int a,int b) {if (a>b) swap(a,b); return make_pair(a,b);}
inline int rd() {int r;scanf("%d",&r);return r;}
void link(int a,int b) {
e[a].push_back(b);
e[b].push_back(a);
mp[MP(a,b)]++;
}
int gf(int u) {return fa[u]==u?u:gf(fa[u]);}
int main() {
n = rd();
for (int i=1;i<=n;i++) fa[i] = i;
for (int i=1;i<=2*n-2;i++) link(rd(), rd());
for (map<pii,int>::iterator it=mp.begin();it!=mp.end();it++)
if (it->y == 2) q.push(it->x);
while (!q.empty()) {
pii u = q.front(); q.pop();
int a = gf(u.x), b = gf(u.y);
if (a == b) continue;
++tot;
if (siz[a] < siz[b]) swap(a, b);
for (int i=0;i<(int)e[b].size();i++) {
int t=gf( e[b][i] );
if (t == a) continue;
e[a].push_back(t);
mp[ MP(b, t) ]--;
mp[ MP(a, t) ]++;
if (mp[ MP(a, t) ] == 2) q.push( MP(a, t) );
}
e[b].clear();
fa[b] = a;
}
puts(tot==n-1?"YES":"NO");
return 0;
}