全力以赴就会闪闪发光
周一
P5306 [COCI2019] Transport(点分治)
这题搞了好久……很精彩
首先这题的路径具有方向性,因此一条路径u到v要拆成两部分,u到lca,lca到v
先看u到lca的部分,多出来的油量很容易算出,关键是判断是否合法
如果要合法的话,显然是以u为起点的路径的油量最小值要大于等于0,就是合法的
这个怎么求呢,用dp求,类似一个序列求最大连续子序列和,dp[i]表示以i为结尾的连续子序列最大值
那么dp[i] = a[i] + min(dp[i - 1], 0)
那么这里类比到树上就可以求出了,dp[u] = a[u] - w + min(dp[fa], 0) w表示u与其父亲那条边的权值,dfs的过程用一个变量记录dp[fa]即可
如果dp[u] >= 0 就把当前可以多提供的油量存到L数组当中
然后看lca到v的部分,这部分简单一些,不用考虑合法性的问题。直接记录从lca到v的过程中最小的油量,这部分油量就是前一半需要提供的油量。注意lca这部分的油量是算到前一半里面的,细节。
因此先遍历当前子树,得到L,R数组,一条路径需要满足的条件是L >= R
因此将L,R排序后双指针即可,但是这里有一个问题,就是起点和终点需要在不同的子树,因此要容斥一下,存L,R数组时同时记录它属于哪颗子树,统计的时候要减去同一个子树的路径。
之前做的点分治题都是统计出当前子树然后马上统计答案,但是这样应用到这道题上会T,这道题统计出全部子树然后一起统计答案。
一般来说,solve函数的复杂度要控制在O(n)或O(nlogn) 这也是点分治的题的难点
最后注意lca本身也要算起点和终点
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
vector<pair<int, int>> g[N];
int a[N], maxp[N], vis[N], siz[N], rt, sum, n, m, s;
vector<pair<ll, int>> L, R;
unordered_map<int, int> cnt;
ll ans;
void getrt(int u, int fa)
{
siz[u] = 1; maxp[u] = 0;
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(v == fa || vis[v]) continue;
getrt(v, u);
siz[u] += siz[v];
maxp[u] = max(maxp[u], siz[v]);
}
maxp[u] = max(maxp[u], sum - siz[u]);
if(maxp[u] < maxp[rt]) rt = u;
}
void get(int u, int fa, ll dis, ll mi, ll rmin, int id) //dis到目前的多余的油量 mi u为起点的最小油量 rmin 到达这个点过程中最低的油量
{
if(mi >= 0) L.push_back({dis, id});
R.push_back({-rmin, id});
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(vis[v] || v == fa) continue;
get(v, u, dis - w + a[v], a[v] - w + min(mi, 0LL), min(rmin, dis - a[s] - w), id);
}
}
void solve(int u)
{
L.clear(); R.clear();
L.push_back({a[u], u}); cnt[u] = 0 //注意u本身为起点终点也要加入
R.push_back({0, u});
s = u;
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(vis[v]) continue;
cnt[v] = 0;
get(v, u, a[u] - w + a[v], a[v] - w, -w, v);
}
sort(L.begin(), L.end());
sort(R.begin(), R.end());
int l = 0, r = -1;
for(; l < L.size(); l++)
{
while(r + 1 < R.size() && R[r + 1].first <= L[l].first)
{
r++;
cnt[R[r].second]++;
}
ans += r + 1 - cnt[L[l].second];
}
}
void divide(int u)
{
vis[u] = 1;
solve(u);
for(auto x: g[u])
{
int v = x.first;
if(vis[v]) continue;
maxp[rt = 0] = sum = siz[v];
getrt(v, 0);
getrt(rt, 0);
divide(rt);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back({v, w});
g[v].push_back({u, w});
}
maxp[rt = 0] = sum = n;
getrt(1, 0);
getrt(rt, 0);
divide(rt);
printf("%lld\n", ans);
return 0;
}
P3369 【模板】普通平衡树(splay)
学一学splay 码量巨大
是一种平衡树,可以维护排名为x的数,x的排名,x的前驱后继
板子用的是OIwiki的
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 1e5 + 10;
int rt, tot, fa[N], ch[N][2], val[N], cnt[N], sz[N];
struct Splay
{
void maintain(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x]; } //重新计算x的sz值
bool get(int x) { return x == ch[fa[x]][1]; } //判断x是不是右儿子
void clear(int x) //销毁x节点
{
ch[x][0] = ch[x][1] = fa[x] = val[x] = sz[x] = cnt[x] = 0;
}
void rotate(int x) //旋转操作 包含左旋和右旋
{
int y = fa[x], z = fa[y], chk = get(x);
ch[y][chk] = ch[x][chk ^ 1];
if(ch[x][chk ^ 1]) fa[ch[x][chk ^ 1]] = y;
ch[x][chk ^ 1] = y;
fa[y] = x; fa[x] = z;
if(z) ch[z][y == ch[z][1]] = x;
maintain(y);
maintain(x);
}
void splay(int x) //将x旋转到根 每次访问x后都要splay
{
for(int f = fa[x]; f = fa[x]; rotate(x))
if(fa[f]) rotate(get(x) == get(f) ? f : x);
rt = x;
}
void ins(int k) //插入操作
{
if(!rt)
{
val[++tot] = k;
cnt[tot]++;
rt = tot;
maintain(rt);
return;
}
int cur = rt, f = 0;
while(1)
{
if(val[cur] == k)
{
cnt[cur]++;
maintain(cur);
maintain(f);
splay(cur);
break;
}
f = cur;
cur = ch[cur][val[cur] < k];
if(!cur)
{
val[++tot] = k;
cnt[tot]++;
fa[tot] = f;
ch[f][val[f] < k] = tot;
maintain(tot);
maintain(f);
splay(tot);
break;
}
}
}
int rk(int k) //获取k的排名
{
int res = 0, cur = rt;
while(1)
{
if(k < val[cur]) cur = ch[cur][0];
else
{
res += sz[ch[cur][0]];
if(k == val[cur])
{
splay(cur);
return res + 1;
}
res += cnt[cur];
cur = ch[cur][1];
}
}
}
int kth(int k) //排名第k的是哪一个数
{
int cur = rt;
while(1)
{
if(ch[cur][0] && k <= sz[ch[cur][0]]) cur = ch[cur][0];
else
{
k -= cnt[cur] + sz[ch[cur][0]];
if(k <= 0)
{
splay(cur);
return val[cur];
}
cur = ch[cur][1];
}
}
}
int pre() //返回k的前驱 注意函数外要先插入再删除
{
int cur = ch[rt][0];
if (!cur) return cur;
while(ch[cur][1]) cur = ch[cur][1];
splay(cur);
return cur;
}
int nxt() //返回k的后继 注意函数外要先插入再删除
{
int cur = ch[rt][1];
if (!cur) return cur;
while(ch[cur][0]) cur = ch[cur][0];
splay(cur);
return cur;
}
void del(int k) //删除k 只删一个
{
rk(k);
if(cnt[rt] > 1)
{
cnt[rt]--;
maintain(rt);
return;
}
if(!ch[rt][0] && !ch[rt][1])
{
clear(rt);
rt = 0;
return;
}
if(!ch[rt][0])
{
int cur = rt;
rt = ch[rt][1];
fa[rt] = 0;
clear(cur);
return;
}
if(!ch[rt][1])
{
int cur = rt;
rt = ch[rt][0];
fa[rt] = 0;
clear(cur);
return;
}
int cur = rt;
int x = pre();
fa[ch[cur][1]] = x;
ch[x][1] = ch[cur][1];
clear(cur);
maintain(rt);
}
}tree;
int main()
{
int n;
scanf("%d", &n);
_for(i, 1, n)
{
int op, x;
scanf("%d%d", &op, &x);
if (op == 1) tree.ins(x);
if (op == 2) tree.del(x);
if (op == 3) printf("%d\n", tree.rk(x));
if (op == 4) printf("%d\n", tree.kth(x));
if (op == 5) tree.ins(x), printf("%d\n", val[tree.pre()]), tree.del(x);
if (op == 6) tree.ins(x), printf("%d\n", val[tree.nxt()]), tree.del(x);
}
return 0;
}
「HNOI2002」营业额统计(set/splay)
这道题就是求前驱和后继,其实这个可以用set,二分一下就可以了,没必要用平衡树
当然平衡树可以处理排名等更多的信息
set 和 map内部都是用平衡树实现的
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
set<int> s;
int n, fi = 1;
ll ans;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
if(fi)
{
ans += x;
fi = 0;
s.insert(x);
continue;
}
auto it = s.lower_bound(x);
int cur = 1e9;
if(it != s.begin())
{
it--;
cur = min(cur, abs(x - *it));
it++;
}
if(it != s.end()) cur = min(cur, abs(x - *it));
ans += cur;
s.insert(x);
}
printf("%lld\n", ans);
return 0;
}
「HNOI2004」宠物收养所(set/splay)
和上一题差不多,求前驱后继即可。用set很方便
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int mod = 1e6;
set<int> s1, s2;
int n, ans;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int op, x;
scanf("%d%d", &op, &x);
if(op == 0)
{
if(s2.empty()) s1.insert(x);
else
{
auto it = s2.lower_bound(x);
int a = 1e9, b = 1e9;
if(it != s2.begin())
{
it--;
a = abs(x - *it);
it++;
}
if(it != s2.end()) b = abs(x - *it);
if(a <= b)
{
ans = (ans + a) % mod;
it--;
s2.erase(it);
}
else
{
ans = (ans + b) % mod;
s2.erase(it);
}
}
}
else
{
if(s1.empty()) s2.insert(x);
else
{
auto it = s1.lower_bound(x);
int a = 1e9, b = 1e9;
if(it != s1.begin())
{
it--;
a = abs(x - *it);
it++;
}
if(it != s1.end()) b = abs(x - *it);
if(a <= b)
{
ans = (ans + a) % mod;
it--;
s1.erase(it);
}
else
{
ans = (ans + b) % mod;
s1.erase(it);
}
}
}
}
printf("%d\n", ans);
return 0;
}
周三
P3391 【模板】文艺平衡树(rope)
rope这个STL可以用来解决一些平衡树的问题
它可以时间区间插入,删除,替换,都是logn
这道题而言就建立两个rope,一个正的一个反的,替换的时候就替换对应区间就可以了
用取子串相加这个操作
#include <bits/stdc++.h>
#include <ext/rope>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
using namespace __gnu_cxx;
rope<int> a, b;
int n, m;
void solve(int l, int r)
{
int ll = n - 1 - r, rr = n - 1 - l;
rope<int> t = a;
a = a.substr(a.begin(), a.begin() + l) + b.substr(b.begin() + ll, b.begin() + rr + 1) + a.substr(a.begin() + r + 1, a.end());
b = b.substr(b.begin(), b.begin() + ll) + t.substr(t.begin() + l, t.begin() + r + 1) + b.substr(b.begin() + rr + 1, b.end());
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
a.push_back(i);
b.push_back(n - i + 1);
}
while(m--)
{
int l, r;
scanf("%d%d", &l, &r);
solve(l - 1, r - 1);
}
for(int x: a) printf("%d ", x);
return 0;
}
P1537 弹珠(bitset优化)
不考虑优化的话,就是一道很裸的01背包,秒杀
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e4 + 10;
int w[N], n, sum;
bool f[N];
int main()
{
int kase = 0;
while(1)
{
sum = n = 0;
_for(i, 1, 6)
{
int x; scanf("%d", &x);
while(x--) w[++n] = i, sum += i;
}
if(!sum) break;
memset(f, 0, sizeof f);
f[0] = 1;
_for(i, 1, n)
for(int j = sum; j >= w[i]; j--)
f[j] |= f[j - w[i]];
printf("Collection #%d:\n", ++kase);
if(sum % 2 == 0 && f[sum / 2]) puts("Can be divided.");
else puts("Can't be divided.");
puts("");
}
return 0;
}
bitset可以优化bool数组的操作
看这行代码
_for(i, 1, n)
for(int j = sum; j >= w[i]; j--)
f[j] |= f[j - w[i]];
如果将f数组看作一个二进制的话 每一位都和前面或,也就是整个和前面或,于是将f左移
等价于
_for(i, 1, n) f |= f << w[i];
bitset优化bool型的01背包比较常见
全部代码
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e4 + 10;
int w[N], n, sum;
bitset<N> f;
int main()
{
int kase = 0;
while(1)
{
sum = n = 0;
_for(i, 1, 6)
{
int x; scanf("%d", &x);
while(x--) w[++n] = i, sum += i;
}
if(!sum) break;
f.reset();
f[0] = 1;
_for(i, 1, n) f |= f << w[i];
printf("Collection #%d:\n", ++kase);
if(sum % 2 == 0 && f[sum / 2]) puts("Can be divided.");
else puts("Can't be divided.");
puts("");
}
return 0;
}
P5020 [NOIP2018 提高组] 货币系统(dp)
这题就是当时noip的题 当时没做出来
现在我读完题就会做了,一发AC……整个过程10分钟不到……
不就是个完全背包吗,就没了……
当时很努力,但是方法不对,总是看题解,很少自己思考,所以很菜。
现在方法好了些,自己思考比较多,比高中进步了很多了。
方法还是很重要,要常常思考怎么提高效率,而不是瞎努力,用蛮力。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int M = 2.5e4 + 10;
const int N = 110;
int w[N], n;
bool f[M];
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &w[i]);
sort(w + 1, w + n + 1);
int ans = 0;
memset(f, 0, sizeof f);
f[0] = 1;
_for(i, 1, n)
{
if(f[w[i]]) continue;
ans++;
_for(j, w[i], w[n])
f[j] |= f[j - w[i]];
}
printf("%d\n", ans);
}
return 0;
}
P3674 小清新人渣的本愿(莫队 + bitset优化)
这题妙啊
当bool数组的一些操作可以表示成位运算时,可以用二进制优化
用莫队来得出哪些数存在
a - b = x 即a = x + b
s储存哪些数存在,那么也就是s[k] = true s[k + x] = true
表示成位运算就是s & (s << x)后至少有一个1 这里左移x和右移x效果是一样的
考虑加法,要做一个转化
a + b = x
a = x - b 即a = -b + x
那么可以储存-b 然后类似前面那样,移动x位
但是bitset显然不能存负数,所以我们需要一个偏置值d,使其为非负
因此等式两边同时+d
d + a = d - b + x
即(d - x) + a = (d - b)
用bitset存d - b 那么就移动d - x位就可以了
操作3直接暴力
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
const int d = 1e5;
bitset<N> s1, s2;
int a[N], ans[N], cnt[N], n, m;
struct query
{
int op, l, r, x, bl, id;
}q[N];
bool cmp(query x, query y)
{
if(x.bl != y.bl) return x.bl < y.bl;
if(x.bl & 1) return x.r < y.r;
return x.r > y.r;
}
void add(int x)
{
x = a[x];
if(++cnt[x] == 1) s1[x] = s2[d - x] = 1;
}
void del(int x)
{
x = a[x];
if(--cnt[x] == 0) s1[x] = s2[d - x] = 0;
}
int main()
{
scanf("%d%d", &n, &m);
int block = sqrt(n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, m)
{
scanf("%d%d%d%d", &q[i].op, &q[i].l, &q[i].r, &q[i].x);
q[i].bl = q[i].l / block;
q[i].id = i;
}
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
_for(i, 1, m)
{
int ll = q[i].l, rr = q[i].r, x = q[i].x, op = q[i].op;
while(l < ll) del(l++);
while(l > ll) add(--l);
while(r < rr) add(++r);
while(r > rr) del(r--);
if(op == 1) ans[q[i].id] = (s1 & (s1 << x)).any();
if(op == 2) ans[q[i].id] = (s1 & (s2 >> (d - x))).any();
if(op == 3)
{
for(int j = 1; j * j <= x; j++)
if(x % j == 0 && s1[j] && s1[x / j])
{
ans[q[i].id] = 1;
break;
}
}
}
_for(i, 1, m) puts(ans[i] ? "hana" : "bi");
return 0;
}
B. Maximum Value(筛法枚举 + 余数)
这题妙啊
关键是怎么枚举
ai % aj 设ai = aj * k + p
p = ai - k * aj 要最大 那么可以枚举k * aj
也就是在1e6的范围枚举每个数和每个数的倍数,这个复杂度是调和级数,nlogn的
对于每一个k * aj 考虑取什么ai可以使得p最大
在k * aj固定的情况下,要p尽可能大,那么ai要尽可能大,但是也要小于(k + 1) * aj
也就是在[1, (k + 1) * aj - 1]里面最大的数就可以了,所以我们可以预处理一个前缀最大值,就可以O(1)求得了
注意(k + 1) * aj - 1可能会爆空间,要和1e6取一个min
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int pre[N], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
pre[x] = x;
}
_for(i, 1, 1e6) pre[i] = max(pre[i], pre[i - 1]);
int ans = 0;
for(int i = 1; i <= 1e6; i++)
if(pre[i] == i)
for(int j = i; j <= 1e6; j += i)
{
int cur = pre[min((int)1e6, j + i - 1)];
if(cur > j) ans = max(ans, cur - j);
}
printf("%d\n", ans);
return 0;
}
F. Ant colony(线段树)
秒切
每次询问一个区间,问有多少个数可以整除所有数
首先一个数可以整除所有数,意味着这个区间的gcd=min 同时还有问个数
所以用线段树维护区间的gcd,最小值,最小值的个数即可
同时维护值和个数用pair,写个合并函数,代码会简洁很多
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef pair<int, int> pa;
const int N = 1e5 + 10;
int tgcd[N << 2], n, m;
pa t[N << 2];
int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
pa merge(pa a, pa b)
{
if(a.first == b.first) return {a.first, a.second + b.second};
if(a.first < b.first) return a;
return b;
}
void up(int k)
{
tgcd[k] = gcd(tgcd[l(k)], tgcd[r(k)]);
t[k] = merge(t[l(k)], t[r(k)]);
}
void build(int k, int l, int r)
{
if(l == r)
{
int x; scanf("%d", &x);
t[k] = {x, 1};
tgcd[k] = x;
return;
}
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
int ask_gcd(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return tgcd[k];
int m = l + r >> 1, res = 0;
if(L <= m) res = gcd(res, ask_gcd(l(k), l, m, L, R));
if(R > m) res = gcd(res, ask_gcd(r(k), m + 1, r, L, R));
return res;
}
pa ask_min(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
int m = l + r >> 1; pa res = {2e9, 0};
if(L <= m) res = merge(res, ask_min(l(k), l, m, L, R));
if(R > m) res = merge(res, ask_min(r(k), m + 1, r, L, R));
return res;
}
int main()
{
scanf("%d", &n);
build(1, 1, n);
scanf("%d", &m);
while(m--)
{
int l, r;
scanf("%d%d", &l, &r);
int g = ask_gcd(1, 1, n, l, r);
pa k = ask_min(1, 1, n, l, r);
if(g == k.first) printf("%d\n", r - l + 1 - k.second);
else printf("%d\n", r - l + 1);
}
return 0;
}
D. Omkar and Circle(观察)
这题我思路错了。我想的是贪心,每次删最小的数,写了优先队列+静态链表,比较麻烦,WA了……
发现原来是我理解错题意了,之后合并的数是不能再删的,因为不会再相邻了
于是删除的数一定是不相邻的,这是关键 用几个数随便删一下,发现剩下的数的和一定是只有一对相邻,然后都不相邻的
所以不论你怎么删,最终的答案是有规律的
于是我们可以枚举这个不相邻的位置,然后预处理出同奇偶的前缀后缀即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
ll a[N], b[N], ans;
int n;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%lld", &a[i]), b[i] = a[i];
if(i >= 3) a[i] += a[i - 2];
}
for(int i = n - 2; i >= 1; i--) b[i] += b[i + 2];
_for(i, 1, n) ans = max(ans, a[i] + b[i + 1]);
printf("%lld\n", ans);
return 0;
}
周四
C. Ciel the Commander(点分治)
妙啊这题,用了点分治的思想
首先考虑A,发现只能放一个,如果再放一个A,那么路径上就没有大于A的
那么在一个点放一个A,就变成了在其每个子树里面放B到Z,显然是一个子问题
那么A给放哪了,显然越中间越好,这样子树的规模就比较小,更容易成功
“最中间”也就是树的重心,树的重心的最大子树最小。
于是就根据重心这样不断分解,也就是点分治的
树的重心的最大子树是小于等于n / 2的,所以每次问题规模都会减少一半,这样的话26个字母对于1e5这个数据是可以放完的,所以是一定可以的。
好好体会这个分治的思想
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 1e5 + 10;
int siz[N], maxp[N], vis[N], n, rt, sum;
vector<int> g[N];
char ans[N];
void getrt(int u, int fa)
{
maxp[u] = 0;
siz[u] = 1;
for(int v: g[u])
{
if(v == fa || vis[v]) continue;
getrt(v, u);
siz[u] += siz[v];
maxp[u] = max(maxp[u], siz[v]);
}
maxp[u] = max(maxp[u], sum - siz[u]);
if(maxp[u] < maxp[rt]) rt = u;
}
void divide(int u, int d)
{
vis[u] = 1;
ans[u] = d + 'A';
for(int v: g[u])
{
if(vis[v]) continue;
maxp[rt = 0] = sum = siz[v];
getrt(v, 0);
getrt(rt, 0);
divide(rt, d + 1);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
maxp[rt = 0] = sum = n;
getrt(1, 0);
getrt(rt, 0);
divide(rt, 0);
_for(i, 1, n) printf("%c\n", ans[i]);
return 0;
}
F. Zero Remainder Sum(dp)
dp就行了,注意一些状态是不合法的,要初始化为最小值
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 100;
int a[N][N], dp[N][N], f[N][N][N], t[N], n, m, k, lim;
int main()
{
scanf("%d%d%d", &n, &m, &k);
_for(i, 1, n)
_for(j, 1, m)
scanf("%d", &a[i][j]);
lim = m / 2;
memset(dp, -0x3f, sizeof dp);
dp[0][0] = 0;
_for(i, 1, n)
{
memset(f, -0x3f, sizeof f);
memset(t, -0x3f, sizeof t);
f[0][0][0] = t[0] = 0;
_for(j, 1, m)
_for(r, 0, lim)
{
_for(p, 0, k - 1) f[j][r][p] = max(f[j][r][p], f[j - 1][r][p]);
if(r > 0)
{
_for(p, 0, k - 1)
{
int cur = (p + a[i][j]) % k;
f[j][r][cur] = max(f[j][r][cur], f[j - 1][r - 1][p] + a[i][j]);
}
}
_for(p, 0, k - 1) t[p] = max(t[p], f[j][r][p]);
}
_for(p1, 0, k - 1)
_for(p2, 0, k - 1)
{
int cur = (p1 + p2) % k;
dp[i][cur] = max(dp[i][cur], dp[i - 1][p1] + t[p2]);
}
}
printf("%d\n", dp[n][0]);
return 0;
}
C1. Errich-Tac-Toe (Easy Version)(棋盘染色)
之前做过把棋盘黑白染色使得没有相同的相邻的值的
这个类似,染三种颜色,这样任意三个相连的都有一种颜色
那么其实把一种颜色全部设为空就可以了
为了使次数小于等于k / 3 就要选择次数最小的颜色
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 300 + 10;
char s[N][N];
int cnt[3], n;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(cnt, 0, sizeof cnt);
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%s", s[i] + 1);
_for(j, 1, n)
if(s[i][j] == 'X')
cnt[(i + j) % 3]++;
}
int t = min_element(cnt, cnt + 3) - cnt;
_for(i, 1, n)
{
_for(j, 1, n)
{
if(s[i][j] == 'X' && (i + j) % 3 == t) putchar('O');
else putchar(s[i][j]);
}
puts("");
}
}
return 0;
}
周五
E. Tree Painting(换根dp)
这题的关键是发现,只要起点定下来了,看作以其为根的树,可以发现接下来无论怎么染答案都是定下的,每次染色的答案一定是子树的个数
因为染当前点,子树肯定没染,父亲肯定染了
那么换根dp即可
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int siz[N], n, s, t;
vector<int> g[N];
ll ans;
void dfs(int u, int fa)
{
siz[u] = 1;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
}
ans += siz[u];
}
void dfs2(int u, int fa, ll cur)
{
ans = max(ans, cur);
for(int v: g[u])
{
if(v == fa) continue;
dfs2(v, u, cur + n - 2 * siz[v]);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
dfs2(1, 0, ans);
printf("%lld\n", ans);
return 0;
}
D. Max Median(二分答案)
这题妙啊
其实我很早就想到了二分答案,但是觉得没有单调性,大的答案成立不一定小的答案成立
但实际上换一种思考方式,就有单调性了
之前的二分答案的每次的check函数是答案为key符不符合
现在的check函数要改成存不存在大于等于key的答案,这样子就有单调性了
具体来说,一个序列,mid为中位数,那么大于等于mid的数的个数 > 小于mid的数的个数
所以当前为key,如果大于等于key的数的个数 > 小于key的数的个数,可以说明一定存在中位数mid >= key
根据这个二分答案
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 2e5 + 10;
int a[N], b[N], n, k;
bool check(int key)
{
_for(i, 1, n)
{
if(a[i] >= key) b[i] = 1;
else b[i] = -1;
}
_for(i, 1, n) b[i] += b[i - 1];
int mi = 0;
_for(i, k, n)
{
if(b[i] - mi > 0) return true;
mi = min(mi, b[i - k + 1]);
}
return false;
}
int main()
{
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%d", &a[i]);
int l = 1, r = n + 1;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) l = m;
else r = m;
}
printf("%d\n", l);
return 0;
}
E. Minimum spanning tree for each edge(最小生成树+lca)
读完题就有一个非常直觉的想法,先跑最小生成树,一条边如果在这个生成树那就是这个答案,否则在原来的生成树上加上这一条边后就成了一个环,那么久删掉这个环上最大的那条边。
寻找这条边其实就是u到lca和v到lca的路径上最大的那条边,在求lca的时候顺便求出即可
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
struct Edge
{
int u, v, w, id, choose;
};
vector<Edge> e;
int f[N], d[N], up[N][20], mx[N][20], n, m;
vector<pair<int, int>> g[N];
ll ans[N];
bool cmp(Edge x, Edge y)
{
return x.w < y.w;
}
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void dfs(int u, int fa, int w)
{
d[u] = d[fa] + 1;
up[u][0] = fa;
mx[u][0] = w;
_for(j, 1, 19)
{
mx[u][j] = max(mx[u][j - 1], mx[up[u][j - 1]][j - 1]);
up[u][j] = up[up[u][j - 1]][j - 1];
}
for(auto x: g[u])
{
if(x.first == fa) continue;
dfs(x.first, u, x.second);
}
}
int lca(int u, int v)
{
int res = 0;
if(d[u] < d[v]) swap(u, v);
for(int j = 19; j >= 0; j--)
if(d[up[u][j]] >= d[v])
{
res = max(res, mx[u][j]);
u = up[u][j];
}
if(u == v) return res;
for(int j = 19; j >= 0; j--)
if(up[u][j] != up[v][j])
{
res = max(res, max(mx[u][j], mx[v][j]));
u = up[u][j]; v = up[v][j];
}
return max(res, max(mx[u][0], mx[v][0]));
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) f[i] = i;
_for(i, 1, m)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
e.push_back({u, v, w, i, 0});
}
sort(e.begin(), e.end(), cmp);
ll sum = 0;
for(auto& x: e)
{
int u = find(x.u), v = find(x.v), w = x.w;
if(u != v)
{
x.choose = 1;
f[u] = v;
sum += w;
g[x.u].push_back({x.v, w});
g[x.v].push_back({x.u, w});
}
}
dfs(1, 0, 2e9);
for(auto x: e)
{
if(x.choose)
{
ans[x.id] = sum;
continue;
}
int t = lca(x.u, x.v);
ans[x.id] = sum - t + x.w;
}
_for(i, 1, m) printf("%lld\n", ans[i]);
return 0;
}
C. Ancient Berland Circus(三角形外接圆)
这题看题解的,计算几何写的很少
关键是从三角形外接圆考虑问题
知道三个边长可以求得外接圆半径
知道半径可以求得每条边对应的圆心角。多边形一定是内接在这个圆里面的
为了使面积小,就要边数小,也就是每个角对应的圆心角大,于是求三个圆心角的最大公约数。
知道了这个角,就可以求出一个三角形的面积,再乘以三角形个数
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const double PI = acos(-1.0);
double fgcd(double a, double b) { return fabs(b) < 0.01 ? a : fgcd(b, fmod(a, b)); }
int main()
{
double x1, y1, x2, y2, x3, y3;
scanf("%lf%lf%lf%lf%lf%lf", &x1, &y1, &x2, &y2, &x3, &y3);
double n1 = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
double n2 = sqrt(pow(x3 - x2, 2) + pow(y3 - y2, 2));
double n3 = sqrt(pow(x1 - x3, 2) + pow(y1 - y3, 2));
if(n1 > n2) swap(n1,n2);
if(n2 > n3) swap(n2,n3);
if(n1 > n2) swap(n1,n2);
double p = (n1 + n2 + n3) / 2;
double S = sqrt(p * (p - n1) * (p - n2) * (p - n3));
double R = n1 * n2 * n3 / (4 * S);
double angle1 = acos(1 - n1 * n1 / (2 * R * R));
double angle2 = acos(1 - n2 * n2 / (2 * R * R));
double angle3 = 2 * PI - angle1 - angle2;
double angle = fgcd(angle1, fgcd(angle2, angle3));
printf("%.6f\n", R * R * sin(angle) / 2 * (2 * PI / angle));
return 0;
}
E. A and B and Lecture Rooms(树上倍增)
分类讨论一下就好了
注意,如果深度相同,那么lca的上方所有节点也是可以的。
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 1e5 + 10;
int siz[N], d[N], up[N][20], n, q;
vector<int> g[N];
void dfs(int u, int fa)
{
d[u] = d[fa] + 1;
siz[u] = 1;
up[u][0] = fa;
_for(j, 1, 19) up[u][j] = up[up[u][j - 1]][j - 1];
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
}
}
int lca(int u, int v)
{
if(d[u] < d[v]) swap(u, v);
for(int j = 19; j >= 0; j--)
if(d[up[u][j]] >= d[v])
u = up[u][j];
if(u == v) return u;
for(int j = 19; j >= 0; j--)
if(up[u][j] != up[v][j])
u = up[u][j], v = up[v][j];
return up[u][0];
}
int jump(int u, int t)
{
for(int j = 19; j >= 0; j--)
if(t & (1 << j))
u = up[u][j];
return u;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
scanf("%d", &q);
while(q--)
{
int u, v;
scanf("%d%d", &u, &v);
int LCA = lca(u, v);
int len = d[u] + d[v] - 2 * d[LCA];
if(len % 2 == 1)
{
puts("0");
continue;
}
int t = len / 2 - 1;
if(d[u] == d[v]) printf("%d\n", siz[LCA] - siz[jump(u, t)] - siz[jump(v, t)] + n - siz[LCA]);
else if(d[u] > d[v]) printf("%d\n", siz[jump(u, t + 1)] - siz[jump(u, t)]);
else printf("%d\n", siz[jump(v, t + 1)] - siz[jump(v, t)]);
}
return 0;
}