【NOIP2018模拟10.27】总结
真是一场养生比赛。
不得不说我识别水题的能力还是比较强的,T3一道裸的主席树秒切了,T2暴力分十分良心,T1暴力只有10分。还是很后悔,这种结论题我总是懒得去推,结果少了别人90,以后还是要保持冷静思考吧。
T1
首先你得把题看懂。
对于一个的排列,它的贡献就是将它交换有序的最少次数。
我们可以设表示前个数所有方案的贡献,那么考虑放在哪一位。
直接放在第位,无需交换,只用加上。
如果不放在第位,我们就要把换到第位,贡献一次。不放第位就有种放法,另外个数的排列是,所以一共有种方案贡献是,此外另外个数也要交换,产生贡献。
于是得到递推式:
然后求逆元除以就行了。
预处理阶乘和,复杂度,回答每次。
Code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
const int P = 998244353, N = 1e7 + 7;
int pow(int a, int b)
{
int ret = 1;
while (b)
{
if (b & 1) ret = 1ll * ret * a % P;
a = 1ll * a * a % P, b >>= 1;
}
return ret;
}
int T, n, f[N], fac[N];
int main()
{
//freopen("inverse.in", "r", stdin);
//freopen("inverse.out", "w", stdout);
f[1] = 0, fac[1] = 1;
for (int i = 2; i <= N - 7; i++)
f[i] = (1ll * i * f[i - 1] % P + 1ll * (i - 1) * fac[i - 1] % P) % P, fac[i] = 1ll * fac[i - 1] * i % P;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
printf("%d\n", 1ll * f[n] * pow(fac[n], P - 2) % P);
}
fclose(stdin);
fclose(stdout);
return 0;
}
T2
这题真™暴力。
数据范围是亮点。
的数据(莫名其妙拿了,出题人人真好):
考虑到只有,只有,容易想到一种的优秀 做法。
首先预处理出每个点两两之间的最短路,Then对于每个询问,在桶里标记一下满足条件的点,就OK了。
的数据,要用bitset
这个自带常数的黑科技。bitset
可以理解为一个每一位只能是0或1的数组,但是因为它经过压位,时空复杂度都是,其中是数组位数。我们可以利用这个黑科技做这题。
开一个bitset<N> b[N][N]
,其中b[i][j]
存的是与i
距离<=j
的点集。由于与i
距离<=j
的点集不太方便求,我们可以先考虑求与i
距离=j
的点集,然后用前缀和的方式求出与i
距离<=j
的点集。
然后每次询问把所有点对应的bitset
取个并集,集合大小就是答案了。
求最短路的过程用bfs
复杂度是。吸了氧跑的还挺快,总的询问是,一次询问复杂度是,时间复杂度就是,空间复杂度是,再加上O2
对STL
的定制优化,就过了。
Tips:由于重边较多,使用vector
存边貌似能更快。
Code:真的暴力
#include <bitset>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
const int N = 1e3 + 7;
inline int read()
{
int x = 0, f = 0;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
return f ? -x : x;
}
int n, m, q;
vector<int> edge[N];
void add(int u, int v) { edge[u].push_back(v); }
int head, tail, que[N], d[N];
bitset<N> b[N][N], tmp;
void init()
{
n = read(), m = read(), q = read();
for (int i = 1, u, v; i <= m; i++) u = read(), v = read(), add(u, v), add(v, u);
for (int i = 1; i <= n; i++)
{
memset(d, 0, sizeof(d));
d[i] = 1, head = 1, que[tail = 1] = i;
while (head <= tail)
{
int u = que[head++], siz = edge[u].size();
for (int i = 0; i < siz; i++)
if (!d[edge[u][i]])
d[edge[u][i]] = d[u] + 1, que[++tail] = edge[u][i];
}
for (int j = 1; j <= n; j++) if (d[j]) b[i][d[j] - 1][j] = 1;
for (int j = 1; j <= n; j++) b[i][j] |= b[i][j - 1];
}
}
void solve()
{
for (int i = 1; i <= q; i++)
{
int k = read();
tmp.reset();
for (int j = 1, x, y; j <= k; j++) x = read(), y = read(), tmp |= b[x][y];
printf("%d\n", tmp.count());
}
}
int main()
{
//freopen("input", "r", stdin);
//freopen("center.in", "r", stdin);
//freopen("center.out", "w", stdout);
init();
solve();
fclose(stdin);
fclose(stdout);
return 0;
}
T3
这题没穿衣服(真裸)。
看到区间第大,首先会想到主席树。可是看到新增与删除操作,就做不了了吗?不,伟大的shehui主义主席树是无所不能的 。
由于是在序列左端修改,我们可以考虑倒着建主席树,对于删除操作,我们只需要把该位置对应的主席树全部清空掉,由于一棵主席树新增的节点最多就个,这个操作复杂度是的。插入操作就新建一棵主席树,查询操作就开个指针记录一下我插入到了哪里,找到实际要查询的区间求行了。
我的清空方法比较暴力,就是树上所有的节点左儿子右儿子都赋为0。最好离散化一下,这样时间空间都会小很多,总复杂度
Code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int N = 4e5 + 7, Q = 2e5 + 7;
inline int read()
{
int x = 0, f = 0;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
return f ? -x : x;
}
int n, q, pt, a[N], tmp[N];
int opt[Q], L[Q], R[Q], K[Q];
int len, arr[N];
int tot, root[N], lson[N * 50], rson[N * 50], sum[N * 50];
void clear(int rt, int l, int r, int po)
{
sum[rt]--; //这句貌似可以不加
if (l == r) return;
int mid = l + r >> 1;
if (po <= mid) clear(lson[rt], l, mid, po);
else clear(rson[rt], mid + 1, r, po);
lson[rt] = rson[rt] = 0;
}
void insert(int &rt, int fa, int l, int r, int po)
{
if (!rt) rt = ++tot;
sum[rt] = sum[fa] + 1;
if (l == r) return;
int mid = l + r >> 1;
if (po <= mid) rson[rt] = rson[fa], insert(lson[rt], lson[fa], l, mid, po);
else lson[rt] = lson[fa], insert(rson[rt], rson[fa], mid + 1, r, po);
}
int getkth(int rt, int fa, int l, int r, int k)
{
if (l == r) return l;
int mid = l + r >> 1, siz = sum[lson[rt]] - sum[lson[fa]];
if (k <= siz) return getkth(lson[rt], lson[fa], l, mid, k);
else return getkth(rson[rt], rson[fa], mid + 1, r, k - siz);
}
void init()
{
n = read(), q = read(), pt = q;
for (int i = 1; i <= n; i++) a[i] = read(), arr[++len] = a[i];
for (int i = 1; i <= q; i++)
{
opt[i] = read();
if (opt[i] == 2) K[i] = read(), arr[++len] = K[i];
else if (opt[i] == 3) L[i] = read(), R[i] = read(), K[i] = read();
}
sort(arr + 1, arr + len + 1);
len = unique(arr + 1, arr + len + 1) - arr - 1;
}
void solve()
{
for (int i = n; i >= 1; i--)
{
a[i] = lower_bound(arr + 1, arr + len + 1, a[i]) - arr;
insert(root[i + q], root[i + q + 1], 1, len, a[i]);
}
for (int i = 1; i <= n; i++) tmp[i] = a[i], a[i] = 0;
for (int i = 1; i <= n; i++) a[i + q] = tmp[i];
for (int i = 1; i <= q; i++)
{
if (opt[i] == 1) ++pt, clear(root[pt], 1, len, a[pt]);
else if (opt[i] == 2)
{
K[i] = lower_bound(arr + 1, arr + len + 1, K[i]) - arr;
insert(root[pt], root[pt + 1], 1, len, K[i]);
a[pt--] = K[i];
}
else
{
int po = getkth(root[pt + L[i]], root[pt + R[i] + 1], 1, len, K[i]);
printf("%d\n", arr[po]);
}
}
}
int main()
{
//freopen("input", "r", stdin);
//freopen("output", "w", stdout);
//freopen("pigeon.in", "r", stdin);
//freopen("pigeon.out", "w", stdout);
init();
solve();
fclose(stdin);
fclose(stdout);
return 0;
}
这是这一周最放松的比赛了吧。