我现在怀疑我莫队的奇偶性排序赛时是不是就没写对过
学长们因为觉得szs出的题太难了,所以打算送一场温暖,然后……成功反杀了szs(szs ak ioi的彩蛋不会有人没发现吧)。
T1.Rubyonly is always here
观察到两种操作都不会改变数对的和\(sum=a+b\),所以有解首先要满足\(a+b\equiv c+d\ (mod\ p)\)。把原数对转化为\((a, sum-a)\),再把\(a、b、c、d\)分别除以\(sum\),得到新的\((a, 1-a)\),这时两个数对只由其中一个的取值决定,所以只需要看\(a\)变化后什么时候到达\(c\)。手磨一下,发现\(a\)经过\(k\)次变换后的取值范围是\([2^ka-(2^k-1), 2^ka]\),所以只需要从\(0\)开始暴力枚举\(k\),每次判断\(c\)是否在这个范围里。但是注意因为取模的存在,所以这个区间并不精确,要考虑\(r< l\)的情况,而且注意到\(k\)的上界是\(\lceil log_2p \rceil\),因为这时候所有区间都将被取到,由于上界无法被判断出来,所以当我们确定它有解时,如果一直没有判断出解,就直接输出上界。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long
using namespace std; int wrt[20], TP;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }
sandom main()
{
int p = read(), q = read(), lim = ceil(log2(p));
while (q--)
{
int a = read(), b = read(), c = read(), d = read(), s = qpow((a + b) % p, p - 2, p);
if ((a + b) % p != (c + d) % p) { puts("-1"); continue; }//无解
a = a * s % p, c = c * s % p;//除以sum,把b和d消掉
for (re k = 0; k <= lim; k++)
{
int tmp = qpow(2, k, p), l = ((tmp * a - tmp + 1) % p + p) % p, r = l + tmp - 1;
if (c >= l && c <= r) { write(k); break; }
if (c + p >= l && c + p <= r) { write(k); break; }
if (k == lim) write(k);//判不出来直接输出上界
}
}
return 0;
}
T2.Su_Zipei is always here
良心数据结构,部分分很多80,但是因为莫队排序挂了,所以只拿了30。因为强制在线,并且主席树也许是不能做的,所以上分块。\(sum[i][j]\)表示前\(i\)块颜色为\(j\)的数量;\(tot[i][j][k]\)表示第\(i\)块到第\(j\)块出现次数大于等于\(k\)的颜色个数,\(pos[i][j]\)表示第\(i\)个颜色前\(j\)个位置的出现次数。因为\(tot\)的转移是\(O((\sqrt{n})^4)\),所以我们根号分治,对于出现次数超过\(\sqrt{n}\)的颜色,一定不超过\(\sqrt{n}\)个,所以对这部分暴力扫,也就是\(pos\),对其他的维护\(sum\)和\(tot\)。在合并的时候,只有当第一次总数为\(k\)时,统计答案。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 1e5 + 10; const int M = 350;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, m, q, opt, ans;
int a[Z], bel[Z], L[M], R[M];
int sum[M][Z], tot[M][M][M], cnt[Z];
int mrk[Z], pos[M][Z];
inline void solve()//根号分治
{
for (re i = 1; i <= n; i++) cnt[a[i]]++;
for (re i = 1; i <= n; i++)
if (cnt[i] > m) mrk[i] = ++mrk[0];
for (re i = 1; i <= n; i++)
if (mrk[a[i]]) pos[mrk[a[i]]][i]++;
for (re i = 1; i <= mrk[0]; i++)
for (re j = 1; j <= n; j++) pos[i][j] += pos[i][j - 1];
memset(cnt, 0, sizeof(cnt));
}
inline void init()//初始化
{
m = sqrt(n);
for (re i = 1; i <= m; i++)
{
L[i] = R[i - 1] + 1;
R[i] = L[i] + m - 1;
if (i == m) R[i] = n;
for (re j = L[i]; j <= R[i]; j++) bel[j] = i;
}
solve();
for (re i = 1; i <= m; i++)
{
for (re j = L[i]; j <= R[i]; j++) if (!mrk[a[j]]) sum[i][a[j]]++;//第i块颜色为j的数量
for (re j = 1; j <= n; j++) sum[i][j] += sum[i - 1][j];//前i块颜色为j的数量
}
for (re i = 1; i <= m; i++)//[i, j]块出现k次的颜色数量
{
for (re k = 1; k <= n; k++) cnt[k] = 0;//桶
for (re j = i; j <= m; j++)
{
for (re k = 1; k <= m; k++) tot[i][j][k] = tot[i][j - 1][k];
for (re k = L[j]; k <= R[j]; k++)
if (!mrk[a[k]])
{
tot[i][j][cnt[a[k]]]--;
cnt[a[k]]++;
tot[i][j][cnt[a[k]]]++;
}
}
}
for (re i = 1; i <= m; i++)
for (re j = i; j <= m; j++)//后缀和
for (re k = m; k >= 1; k--) tot[i][j][k] += tot[i][j][k + 1];
memset(cnt, 0, sizeof(cnt));
}
inline void del(int l, int r) { for (re i = l; i <= r; i++) cnt[a[i]] = 0; }
int ask(int l, int r, int k)
{
int ll = bel[l], rr = bel[r], res = 0;
if (ll == rr)
{
for (re i = l; i <= r; i++)
{
cnt[a[i]]++;
if (cnt[a[i]] == k) res++;//当这个颜色第一次超过时计入答案
}
del(l, r);
}
else
{
if (k <= m) res = tot[ll + 1][rr - 1][k];
for (re i = l; i <= R[ll]; i++)
if (!mrk[a[i]])
{
cnt[a[i]]++;
if (cnt[a[i]] + sum[rr - 1][a[i]] - sum[ll][a[i]] == k) res++;
}
for (re i = L[rr]; i <= r; i++)
if (!mrk[a[i]])
{
cnt[a[i]]++;
if (cnt[a[i]] + sum[rr - 1][a[i]] - sum[ll][a[i]] == k) res++;
}
del(l, R[ll]), del(L[rr], r);
for (re i = 1; i <= mrk[0]; i++)
if (pos[i][r] - pos[i][l - 1] >= k) res++;
}
return res;
}
sandom main()
{
n = read(), q = read(), opt = read();
for (re i = 1; i <= n; i++) a[i] = read();
init();
while (q--)
{
int l = (read() + ans * opt - 1) % n + 1, r = (read() + ans * opt - 1) % n + 1, k = (read() + ans * opt - 1) % n + 1;
if (r < l) swap(l, r);
ans = ask(l, r, k);
write(ans);
}
return 0;
}
T3.Kaiser_Kell is always here
基本上和自然数的做法一样,而且还不需要区间求和,可以说比那个题还简单,只需要区间取\(min\)即可。唯一一点就是需要把询问离线,多记录一下所有区间的\(mex\)的\(mex\),这个等扫完之后指针跳就行。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 1e6 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar(' '); }
inline int min(int a, int b) { return a < b ? a : b; }
int n, m, q, ans[Z];
int a[Z], pos[Z], nxt[Z];
struct rose
{
int l, r, id;
friend bool operator <(rose A, rose B) { return A.l < B.l; }
}; rose que[Z];
bool in[Z], vs[Z];
#define lk (rt << 1)
#define rk (rt << 1 | 1)
#define mid (L + R >> 1)
int mex [Z << 2];
inline void change(int rt, int v) { mex[rt] = min(mex[rt], v); }
inline void pushdown(int rt) { change(lk, mex[rt]), change(rk, mex[rt]); }
void update(int rt, int L, int R, int l, int r, int v)
{
if (l <= L && r >= R) { change(rt, v); return; }
pushdown(rt);
if (l <= mid) update(lk, L, mid, l, r, v);
if (r > mid) update(rk, mid + 1, R, l, r, v);
}
int query(int rt, int L, int R, int pos)
{
if (L == R) return mex[rt];
pushdown(rt);
if (pos <= mid) return query(lk, L, mid, pos);
else return query(rk, mid + 1, R, pos);
}
sandom main()
{
n = read();
for (re i = 1; i <= n; i++)
{
a[i] = read();
nxt[pos[a[i]]] = i;
pos[a[i]] = i;
}
for (re i = 1; i <= n; i++) nxt[pos[i]] = n + 1;
memset(mex, 63, sizeof(mex));
int num = 1;
for (re i = 1; i <= n; i++)//预处理处所有【1,i】的mex
{
in[a[i]] = 1;
while (in[num]) num++;
vs[num] = 1;
update(1, 1, n, i, i, num);
}
m = read();
for (re i = 1; i <= m; i++) que[i].l = read(), que[i].r = read(), que[i].id = i;
sort(que + 1, que + 1 + m); int k = 1;
for (re i = 1; i <= n; i++)
{
while (que[k].l == i) ans[que[k].id] = query(1, 1, n, que[k].r), k++;
update(1, 1, n, i, nxt[i] - 1, a[i]);//区间取min
int j = query(1, 1, n, nxt[i] - 1);
if (j >= a[i]) vs[a[i]] = 1;//所有区间的mex打上标记
}
num = 1; while (vs[num]) num++; write(num), putchar('\n');
for (re i = 1; i <= m; i++) write(ans[i]);
return 0;
}
T4.Pl_er is always here
只能说感谢学长的国庆馈赠。
\(\lim\limits_{n \to +\infty} \sum\limits_{i=1}^n\frac{(_k^i)^2}{2^i} (mod\ 1e9 + 7)\)