$NOIP\ 2017\ Day2$ 模拟考试 题解报告
\(NOIP\ 2017\ Day2\) 模拟考试 题解报告
得分情况
\(T1\ 100\ Pts\)
\(T2\ 15\ Pts\)
\(T3\ 100\ Pts\)
总分: \(215\ Pts\)
考试过程
先看 \(T1\)
读完题先把 \(n = 1\) 的写了 又读了一遍题 感觉可以写搜索 宽搜比较好写的样子 算了一下复杂度 上界 \(O(n^2)\) 写完一遍跑了一下大样例 发现过了 大概二十多分钟左右
然后看 \(T2\)
读题就是 \(dp\) 看数据是状压 看到前 \(20\) 是树 先写了 后面的感觉有点像是最小斯坦纳树 但是不会算贡献
转去看 \(T3\)
分析一下题目 ... \(woc\) 这不是个平衡树的板子? 果断开 开始写发现不对劲 然后发现毒瘤出题人把空间卡掉了... 不得已存区间 写动态开点 魔改分裂函数 调了一个多小时 终于过了大样例 回头看 \(T2\) 发现还有二十可以骗 于是就开始写 写了一半发现前 \(20\) 写的不对 打算写完改 结果没调出来 然后前 \(4\) 个点一个没过 得了 \(15\) 分...
题解
\(T1\) 奶酪
宽搜
先扫一遍 找与底面相交或相切的洞 入队 依次取出 枚举其他所有的洞 能够到达的入队 已经入过队的记一下 不用再入一遍
复杂度上界 \(O(n^2)\)
代码
/*
Time: 6.8
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
#define double long double
/*--------------------------------------头文件*/
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen("cheese.in", "r", stdin);
freopen("cheese.out", "w", stdout);
}
/*----------------------------------------文件*/
int T, n, h, r, t;
struct node {int x, y, z;} a[1010];
std::queue <int> q;
bool vis[1010];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
double Dis(int i, int j) {
return std::sqrt((double)((a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y) + (a[i].z - a[j].z) * (a[i].z - a[j].z)));
}
void work1() {if(a[1].z + r >= h && a[1].z - r <= 0) puts("Yes"); else puts("No");}
bool cmp(node x, node y) {return x.z < y.z;}
void work2() {
std::sort(a + 1, a + 1 + n, cmp); memset(vis, 0, sizeof vis); while(!q.empty()) q.pop();
for(int i = 1; i <= n; i++) if(a[i].z - r <= 0) t = i, q.push(i); else break;
while(!q.empty())
{
int i = q.front(); q.pop();
if(a[i].z + r >= h) {puts("Yes"); return ;}
for(int j = t + 1; j <= n; j++) if(!vis[j])
{
double dis = Dis(i, j);
if(dis <= r * 2) vis[j] = 1, q.push(j);
}
}
puts("No");
}
void work() {
n = read(); h = read(); r = read();
for(int i = 1; i <= n; i++) a[i].x = read(), a[i].y = read(), a[i].z = read();
if(n == 1) work1();
else work2();
}
/*----------------------------------------函数*/
signed main() {
File();
T = read(); while(T--) work();
return 0;
}
/*
注意 long long 和 long double
20 只有一个洞
考虑别的 尝试广搜
排序不亏
广搜的时间复杂度好像能过...
写写试试
一发过大样例
用时不到半个小时
*/
\(T2\) 宝藏
状压
状态: \(f_{i, S}\) 表示树深度为 \(i\) 选的点的集合为 \(S\) 时最小代价
转移: \(f_{i, S} = \min\{f_{i - 1, s} + sum \}\)
枚举选点集合 枚举子集 枚举每一个未在子集中的点 枚举子集中所有点 取两点之间最短的距离 统计 \(sum\) 枚举层数 进行转移
/*
Time: 6.8
Worker: Blank_space
Source: P3959 [NOIP2017 提高组] 宝藏
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
#define Min(x, y) ((x) < (y) ? (x) : (y))
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------文件*/
int n, m, f[13][(1 << 12) + 5], mp[20][20], t[(1 << 12) + 5], ans = INF;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
signed main() {
n = read(); m = read(); memset(mp, 63, sizeof mp); memset(f, 63, sizeof f);
if(n == 1) {puts("0"); return 0;}
for(int i = 1; i <= m; i++)
{
int x = read() - 1, y = read() - 1, z = read();
mp[x][y] = mp[y][x] = Min(mp[x][y], z);
}
for(int i = 0; i < n; i++) mp[i][i] = 0;
for(int S = 1; S < 1 << n; S++) for(int j = 0; j < n; j++) if(S & (1 << j))
for(int k = 0; k < n; k++) if(mp[j][k] < INF) t[S] |= 1 << k;
for(int i = 0; i < n; i++) f[0][1 << i] = 0;
for(int S = 2; S < 1 << n; S++) for(int s = S - 1; s; s = s - 1 & S)
{
int sum = 0, _s = s ^ S;
for(int i = 0, k = INF; i < n; i++, k = INF) if(1 << i & _s)
{
for(int j = 0; j < n; j++) if(1 << j & s) k = Min(k, mp[i][j]);
sum += k;
}
for(int i = 1; i < n; i++) if(f[i - 1][s] < INF) f[i][S] = Min(f[i][S], f[i - 1][s] + sum * i);
}
for(int i = 1; i < n; i++) ans = Min(ans, f[i][(1 << n) - 1]);
printf("%lld\n", ans);
return 0;
}
\(T3\) 列队
平衡树 + 区间合并 + 动态开点
建 \(n + 1\) 棵树 对每一行的前 \(m - 1\) 个建 \(FHQ\) 对第 \(m\) 行单独建 \(FHQ\) 每个点的编号为权值 离队操作的时候 将第 \(x\) 棵树的区间 \([1, m - 1]\) 分裂为三个区间 分别是 \([1, y - 1]\) \(\{y\}\) \([y + 1, m - 1]\) 将 \(y\) 点对应的权值输出 合并到第 \(n + 1\) 棵树后 再将区间 \([1, y - 1]\) 与 区间 \([y + 1, m - 1]\) 直接合并 再 \(n + 1\) 树中以同样的方式取第 \(x\) 个点 合并到第 \(x\) 树后即可
然后准备写 开数组的时候看了一眼数据范围
\(1 \leq n, m, q \leq 3 \times 10^5\)
那没事了
这... 建个树都建不开直接开空间的话必然爆炸 考虑动态开点 用到一个开一个
然后发现朴素的建树时间复杂度就是 \(O(nm)\) 的 直接 \(T\) 飞 不得已直接对区间建树
对于前 \(n\) 棵树 直接对区间 \([1, m - 1]\) 开一个节点 权值为第一个位置的点的编号 每个节点维护区间大小 分裂时直接将一个区间分出来 (区间树我感觉写三分比写二分好写) 然后通过分出来的节点的区间大小计算当前节点的编号 合回去的时候就可以新建节点直接正常合并
代码
/*
Time: 6.8
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen("phalanx.in", "r", stdin);
freopen("phalanx.out", "w", stdout);
}
/*----------------------------------------文件*/
int n, m, q, rt[B << 2], tmp1, tmp2, tmp3;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
int d(int x, int y) {return (x - 1) * m + y;}
namespace FHQ {
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
struct node {int son[2], val, w, siz, rnd;} t[C << 1]; int cnt;
void up_date(int p) {t[p].siz = t[ls(p)].siz + t[rs(p)].siz + t[p].w;}
int add(int val, int w) {if(!w) return 0; int p = ++cnt; t[p].siz = t[p].w = w; t[p].val = val; t[p].rnd = rand(); return p;}
void split(int p, int k, int &x, int &y, int &z) {
if(!p) {x = y = z = 0; return ;}
if(k <= t[ls(p)].siz) z = p, split(ls(p), k, x, y, ls(p));
else if(k <= t[ls(p)].siz + t[p].w) y = p, x = ls(p), z = rs(p), ls(p) = rs(p) = 0;
else x = p, split(rs(p), k - t[ls(p)].siz - t[p].w, rs(p), y, z); up_date(p);
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].rnd < t[y].rnd) {rs(x) = merge(rs(x), y); up_date(x); return x;}
else {ls(y) = merge(x, ls(y)); up_date(y); return y;}
}
void work1(int x, int y) {
split(rt[n + 1], x, tmp1, tmp2, tmp3);
printf("%lld\n", t[tmp2].val);
rt[n + 1] = merge(tmp1, merge(tmp3, tmp2));
}
void work2(int x, int y) {
split(rt[x], y, tmp1, tmp2, tmp3); int k = t[tmp2].val - t[tmp1].siz + y - 1;
printf("%lld\n", k);
rt[n + 1] = merge(rt[n + 1], add(k, 1)); y -= t[tmp1].siz;
tmp2 = merge(add(t[tmp2].val, y - 1), add(t[tmp2].val + y, t[tmp2].w - y));
rt[x] = merge(tmp1, merge(tmp2, tmp3));
split(rt[n + 1], x, tmp1, tmp2, tmp3);
rt[n + 1] = merge(tmp1, tmp3); rt[x] = merge(rt[x], tmp2);
}
}
/*----------------------------------------函数*/
signed main() {
File();
n = read(); m = read(); q = read();
for(int i = 1; i <= n; i++)
rt[i] = FHQ::add(d(i, 1), m - 1), rt[n + 1] = FHQ::merge(rt[n + 1], FHQ::add(d(i, m), 1));
for(int i = 1; i <= q; i++)
{
int x = read(), y = read();
if(y == m) FHQ::work1(x, y);
else FHQ::work2(x, y);
}
return 0;
}
/*
一个人离队影响的只有这一行和最后一列
每一次的操作删 (x, y) 第 x 行左移 m 列前移
删点与插入
数据结构维护
直接开 n 棵树 权值线段树不好写
每一行建平衡树 m 列再建一棵 编号设为节点权值
平衡树删除与插入的板子
数据范围爆了......
那没事了
考虑动态开点 对区间建树
按权值分裂 写成三分的形式
中间的点在分的时候顺便删掉
对每行的 m - 1 个建树 第 n + 1 棵树单独维护 m 列
注意特判 m 列 第 n + 1 棵树不是动态开点
其他的树 将一个区间分裂成三个 操作完再合并成一个完整区间 注意调整区间的节点个数
2 2 3
1 1
2 2
1 2
然后挂了样例...
调了半个小时
取地址符取挂了
过了小样例 挂了大样例
3 3 4
1 1
2 2
1 2
2 1
十几分钟...
注意卡一下空间
调到眼瞎
过大样例
滚去写 T2
*/

浙公网安备 33010602011771号