|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 柿子饼同学 于 2024-8-21 22:58 编辑
总结与反思
C - 换根题通常想分类, 然后对于每个点求一个子树的答案, 一个子树外的答案, 树上问题一定要画图, 分类 D - dfs填数独实际上是对于一个点的队列来填, 只需要安排好搜索顺序,
带着全局变量的搜索搜到答案一定要给下一个赋值为现在这个, 因为回溯不一定是从头开始
警惕排序等会让位置变化的东西, 位置会变化导致无法按照原来的编号查询
B - 加号
给定一个长度为 n 的数字字符串,每个字符是'0'~'9'。
现在有k个加号,可以插入到字符串中,每个加号只能在两个数字之间。
每一种插入方案,都对应一个表达式,可以计算出一个值。
求所有方案的值之和。
插入加号的具体不好算, 考虑计算每个数字作为个位/十位...的总贡献, 这样就使得她后面的几个位置不能填 + , 其他位置随便
所以预处理组合数, 上前缀和// sum[i] 表示 1 为第 i 位的贡献
// 垒前缀和即可 O(1) 计算
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1];
sum[i] = sum[i] + math.p10[i - 1] % P * math.C(n - i - 1, k - 1) % P;
}
// 注意考虑 i 这个位置后面不放 +, 要单独算
for (int i = 1; i <= n; i++) {
ans = (ans + (s[i] * (math.p10[n - i] * math.C(i - 1, k) % P + sum[n - i]) % P)) % P;
}
cout << ans;
C - 疯狂动态树
换根 + 查询子树内的点到根的距离和 + 查询链上的点到根的距离和
分类讨论, 对于子树问题分类 : 根在 u 上, u 的子树内, u 的子树外
对于链, 求出根到链的最短路和接入根的点, 然后路径分成两半求个等差数列
对于点到链的两端加上这个链, 三个路径的交点是三个点的 lca 中去掉两个相同的值
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int ID, n, m, rt;
vector<int> e[N];
// up, dn 分别代表除了子树, 子树内的节点到 x 的距离和
int siz[N], up[N], dn[N], dep[N], dfn[N], tot;
int f[N][18];
void dfs1(int u, int pa) {
dfn[u] = ++tot;
siz[u] = 1;
f[u][0] = pa;
dep[u] = dep[pa] + 1;
for (int i = 1; i < 18; i++) {
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (auto v : e[u]) {
if (v == pa)
continue;
dfs1(v, u);
siz[u] += siz[v];
dn[u] += siz[v] + dn[v];
}
}
void dfs2(int u, int pa) {
// pa 头顶上的 up[pa] 每个点都要走 pa-> u 一次, 总共 n - siz[pa] 次
// pa 先去掉 u 子树的答案, 再加上除了 u 子树的点从 pa -> u 的次数, 即
// up[u] = (up[pa] + (n - siz[pa])) + (dn[pa] - dn[u] - siz[u] + siz[pa] - siz[u]);
if (u != 1)
up[u] = (n - siz[u]) + up[pa] + dn[pa] - dn[u] - siz[u];
for (auto v : e[u]) {
if (v == pa)
continue;
dfs2(v, u);
}
}
int Up(int x, int d) {
for (int i = 17; i >= 0; i--) {
if ((d >> i) & 1)
x = f[x][i];
}
return x;
}
int lca(int u, int v) {
if (dep[u] < dep[v])
swap(u, v);
u = Up(u, dep[u] - dep[v]);
if (u == v)
return u;
for (int i = 17; i >= 0; i--) {
if (f[u][i] == f[v][i])
continue;
u = f[u][i];
v = f[v][i];
}
return f[u][0];
}
int dist(int u, int v) { return (dep[u] + dep[v] - dep[lca(u, v)] * 2); }
int sigma(int x) { return ((1 + x) * x / 2); }
bool issub(int x, int y) { return (dfn[y] >= dfn[x] && dfn[y] < dfn[x] + siz[x]); }
int Query_path(int u, int v) {
// cut 意为 rt 到路径的最近点
int cut = lca(u, rt) ^ lca(v, rt) ^ lca(u, v);
int res = 0;
res += sigma(dist(u, cut)) + sigma(dist(v, cut));
res += dist(rt, cut) * (dist(u, v) + 1);
return res;
}
int Query_subt(int u) {
if (rt == u) {
return (up[u] + dn[u]);
}
if (issub(u, rt)) {
int c = Up(rt, dep[rt] - dep[u] - 1);
return (up[u] + dn[u] - dn[c] - siz[c] + dist(rt, u) * (n - siz[c]));
}
return (dn[u] + dist(rt, u) * siz[u]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
freopen("crazy.in", "r", stdin);
freopen("crazy.out", "w", stdout);
rt = 1;
cin >> ID >> n >> m;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs1(1, 0);
dfs2(1, 0);
while (m--) {
int op, x, y;
cin >> op >> x;
if (op == 0) {
rt = x;
} else if (op == 1) {
cin >> y;
cout << Query_path(x, y) << '\n';
} else {
cout << Query_subt(x) << '\n';
}
}
return 0;
}
D - 聪明格
n*n 的棋盘, 每个格子填 1-n 的数, 每行每列没有重复数字, 满足给出的连通块中的数字乘积是连通块的值(mp上的值)
求字典序最小的解
搜索优化: 必须满足每行每列不冲突, 按照连通块排序(从好填到不好填, 按照连通块大小和乘积因数个数排序)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 12;
const int M = 305;
const int dx[] = { -1, 0, 1, 0 };
const int dy[] = { 0, 1, 0, -1 };
int mp[N][N], n;
// 首先给连通块排序, 重新分配每个点的搜索顺序, 确定每个点属于的连通块编号
struct Block {
int siz, facs;
vector<pii> node;
} blk[N * N];
int cnt_blk, id[N][N];
// 注意 blk 是要排序的, 后面没法直接查询 siz, 所以这里记录一下
int siz[N * N];
int Factors(int x) {
int res = 0;
for (int i = 1; i * i <= x; i++) {
if (!(x % i))
res++;
}
return res;
}
bool Check(int x, int y) { return (x > 0 && x <= n && y > 0 && y <= n && !id[x][y]); }
void Explore(int x, int y) {
id[x][y] = cnt_blk;
blk[cnt_blk].siz++;
blk[cnt_blk].node.emplace_back(x, y);
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (Check(nx, ny) && mp[x][y] == mp[nx][ny]) {
Explore(nx, ny);
}
}
}
vector<pii> q;
int m;
void Assign_points() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (!id[i][j]) {
cnt_blk++;
blk[cnt_blk].facs = Factors(mp[i][j]);
Explore(i, j);
siz[cnt_blk] = blk[cnt_blk].siz;
}
}
}
sort(blk + 1, blk + cnt_blk + 1, [](Block& x, Block& y) {
if (x.siz == y.siz)
return x.facs < y.facs;
return x.siz < y.siz;
});
for (int i = 1; i <= cnt_blk; i++) {
for (auto x : blk[i].node) {
q.emplace_back(x);
}
}
m = q.size();
}
// 现在开始 dfs 填数, 记录答案编号方便回答字典序的要求
bitset<N> visx[N], visy[N];
int cnt[N * N], mul[N * N];
int ans[M][N][N], sum = 0;
set<int> st;
bool Check(int x, int y, int a) { return (!visx[x][a] && !visy[y][a] && !((mp[x][y] / mul[id[x][y]]) % a)); }
void dfs(int pos) {
if (pos == m) {
st.emplace(sum++);
// 一定要注意这里, 当前走成功了之后要给接下来的图赋值
// 因为下一次走成功不一定是从头开始走的, 可能某一个小分支就又有了
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ans[sum][i][j] = ans[sum - 1][i][j];
}
}
return;
}
int x = q[pos].first, y = q[pos].second;
int c = id[x][y];
cnt[c]++;
for (int i = 1; i <= n; i++) {
// 能取吗?
if (Check(x, y, i)) {
mul[c] *= i;
// 真能取吗?
if (cnt[c] < siz[c] || (cnt[c] == siz[c] && mul[c] == mp[x][y])) {
ans[sum][x][y] = i;
visx[x][i] = 1, visy[y][i] = 1;
dfs(pos + 1);
visx[x][i] = 0, visy[y][i] = 0;
}
mul[c] /= i;
}
}
cnt[c]--;
}
void Solve() {
for (int i = 0; i <= n * n; i++) {
mul[i] = 1;
}
dfs(0);
}
int Select() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
int minv = 1e9;
for (auto v : st) {
minv = min(minv, ans[v][i][j]);
}
for (auto it = st.begin(); it != st.end();) {
if (ans[*it][i][j] > minv) {
it = st.erase(it);
} else {
it++;
}
}
}
}
return *st.begin();
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
freopen("smart.in", "r", stdin);
freopen("smart.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> mp[i][j];
}
}
Assign_points();
Solve();
int best = Select();
cout << sum << '\n';
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << ans[best][i][j] << ' ';
}
cout << '\n';
}
return 0;
}
|
评分
-
查看全部评分
|