做题策略的总结
这场比赛吸取了昨天的教训,上来直接把暴力打完了,忍住没去想正解。预估分数是120,但是由于我没有梦想,打了一个特判点却开的是暴力的数据范围,于是少了10分。还有打T3暴力的时候太颓废,没有打k=1的点,白白少了10分。
最终分数110,我还是太菜了。
T1
这是一道非常NOIP的差分。
数据范围能够启发人类智慧(@cyc):
O
(
n
2
)
O(n^2)
O(n2)的做法比较简单,可以得到
30
p
t
s
30pts
30pts的好成绩。
如果动动脑子发现
k
=
1
k=1
k=1也很简单,可以得到
40
p
t
s
40pts
40pts的好成绩。
但就是这个
k
=
1
k=1
k=1可以启发你想出正解。
假如我们的操作变成
[
l
,
r
]
[l,r]
[l,r]加上1,只需要差分一下就可以。
如果操作变成对于每个
l
≤
i
≤
r
l \le i \le r
l≤i≤r,
a
i
a_i
ai加上
i
−
l
i-l
i−l,相当于把区间里的一段对应加上一个等差数列,我们可以将原数列
a
a
a的差分数列
d
d
d再差分一次得到数列
d
1
d1
d1。在
d
1
d1
d1打标记以后求前缀和得到
d
d
d,再根据
d
d
d得到
a
a
a。
上面的分别是加上 C i − l 0 C_{i-l}^{0} Ci−l0和加上 C i − l + 1 1 C_{i-l+1}^{1} Ci−l+11的情况。
那么如果加上的是
C
i
−
l
+
2
2
C_{i-l+2}^{2}
Ci−l+22?
我们可以把
d
1
d1
d1的差分数列
d
2
d2
d2搞出来,在
d
2
d2
d2上修改,然后得到
d
1
d1
d1,由
d
1
d1
d1得到
d
d
d,再由
d
d
d得到
a
a
a。
由此我们不难发现,如果要应对一般情况,我们只需要维护一个 k k k阶的差分数组,最后从 d [ k ] d[k] d[k]到 d [ 0 ] d[0] d[0]还原上去。
关于什么是 k k k阶差分数组,将 a a a差分得到 d 1 d1 d1, d 1 d1 d1就是 1 1 1阶差分数组,将 d 2 d2 d2差分得到 d 2 d2 d2, d 2 d2 d2就是 2 2 2阶差分数组,以此类推。
写的很丑压线过去的Code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
typedef long long ll;
const int N = 5e5 + 27, K = 27;
const ll P = 1e9 + 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;
ll d[N][K], C[N][K], sum[K][N];
int main()
{
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = read(), m = read();
C[0][0] = 1;
for (int i = 1; i <= n + 20; i++)
{
C[i][0] = 1;
for (int j = 1; j <= 20; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
}
for (int k = 0; k <= 20; k++)
for (int i = 1; i <= n + 20; i++)
sum[k][i] = (sum[k][i - 1] + C[i][k]) % P;
for (int i = 1, l, r, k; i <= m; i++)
{
l = read(), r = read(), k = read();
d[l][k + 1] = (d[l][k + 1] + 1) % P, d[r + 1][k + 1] = (d[r + 1][k + 1] - 1 + P) % P;
for (int j = k - 1; j >= 0; j--)
{
int tmp = k - j - 1;
d[r + 1][j + 1] = (d[r + 1][j + 1] - (sum[tmp][r - l + tmp] - sum[tmp][tmp < 1 ? 0 : tmp - 1] + (tmp < 1 ? 1 : 0) + P) % P + P) % P;
}
}
for (int k = 20; k >= 0; k--)
{
ll sum = 0;
for (int i = 1; i <= n; i++) sum = (sum + d[i][k + 1]) % P, d[i][k] = (d[i][k] + sum) % P;
}
for (int i = 1; i <= n; i++) printf("%lld\n", d[i][0]);
fclose(stdin);
fclose(stdout);
return 0;
}
T2:
数据范围决定了这题是NOIP难度(如果
n
≤
1
0
5
n \le 10^5
n≤105还算个p的NOIP啊)。
这题是很经典的递推了,之前听炜哥讲过但并没有认真去想,这次吃亏了。
n
≤
5
n \le 5
n≤5暴力枚举每一条边选不选,得到了
30
p
t
s
30pts
30pts的good grades.
然而没打
k
=
1
k=1
k=1,少
10
p
t
s
10pts
10pts。
首先解决一个问题:求有
n
n
n个带标号的点的无向连通图数目。
设
g
i
g_i
gi表示有
i
i
i个带标号的点的无向连通图数目。
我们考虑求出不连通的个数,然后用总数去减。
g
i
=
2
C
i
2
−
∑
j
=
1
i
−
1
2
C
i
−
j
2
∗
C
i
−
1
j
−
1
∗
g
j
g_i=2^{C_{i}^{2}}-\sum_{j=1}^{i-1}2^{C_{i-j}^{2}}*C_{i-1}^{j-1}*g_j
gi=2Ci2−∑j=1i−12Ci−j2∗Ci−1j−1∗gj
后面那一串的意思是,我选出
j
j
j个点成为一个新的连通块,使它与另外
i
−
j
i-j
i−j个点断开。然后选一个点固定它,在另外
i
−
1
i-1
i−1个点里选
j
−
1
j-1
j−1个陪它组成大小为
j
j
j的连通块,上面的递推式就得出来了。
然后再考虑答案怎么求,我们设
f
i
f_i
fi表示前
i
i
i个点组成的无向图,最大连通块大小
≤
k
\le k
≤k的方案数,类似于上面的方法,可以得到:
f
i
=
∑
j
=
1
m
i
n
(
i
,
k
)
f
i
−
j
∗
C
i
−
1
j
−
1
∗
g
j
f_i=\sum_{j=1}^{min(i,k)}f_{i-j}*C_{i-1}^{j-1}*g_j
fi=∑j=1min(i,k)fi−j∗Ci−1j−1∗gj
我们用
≤
k
\le k
≤k的方案数减去
≤
k
−
1
\le k - 1
≤k−1的方案数即可得到刚好等于
k
k
k的方案数。
很丑的Code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
typedef long long ll;
const int N = 2e3 + 7;
const ll P = 998244353;
int n, k;
ll f[N], g[N], pow[N * N], C[N][N];
ll doit(int a)
{
memset(f, 0, sizeof(f));
f[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i && j <= a; j++)
f[i] = (f[i] + f[i - j] * C[i - 1][j - 1] % P * g[j] % P) % P;
return f[n];
}
int main()
{
freopen("bomb.in", "r", stdin);
freopen("bomb.out", "w", stdout);
scanf("%d%d", &n, &k);
pow[0] = 1; for (int i = 1; i <= (n * (n - 1) / 2); i++) pow[i] = pow[i - 1] * 2 % P;
C[0][0] = 1;
for (int i = 1; i <= n; i++)
{
C[i][0] = 1;
for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
}
g[1] = 1;
for (int i = 2; i <= n; i++)
{
g[i] = pow[i * (i - 1) / 2];
ll sum = 0;
for (int j = 1; j <= i - 1; j++) sum = (sum + pow[(i - j) * (i - j - 1) / 2] * C[i - 1][j - 1] % P * g[j] % P) % P;
g[i] = (g[i] - sum + P) % P;
}
printf("%lld\n", (doit(k) - doit(k - 1) + P) % P);
fclose(stdin);
fclose(stdout);
return 0;
}
T3
数据范围启发人类智慧:
n
≤
2000
n \le 2000
n≤2000无脑暴力。
整体翻转的数据打主席树就可以get到
40
p
t
s
40pts
40pts 的好成绩。
简单的分块算法。
对于整个数列分块,每一块维护一个桶记录块内数的出现次数,并维护一个双端队列,存放当前块内元素。
每次修改,先把最右边的块的元素放到最左边的块对应的位置,由于每一块维护的元素最多只有
n
\sqrt{n}
n个,这个复杂度就是
O
(
n
)
O(\sqrt{n})
O(n),再把中间的块每一块最后一个元素放到下一块开头,最多有
n
\sqrt{n}
n个块,这个复杂度也是
O
(
n
)
O(\sqrt{n})
O(n)。
每次查询,两端多余的部分暴力统计,中间的块用桶记录,这样复杂度也是
O
(
n
)
O(\sqrt{n})
O(n),于是总共复杂度就是
O
(
q
n
)
O(q\sqrt{n})
O(qn),评测时吸氧,可以在
1
s
1s
1s内通过。
这数据结构题码的就是爽。
写得一般般的Code:
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
const int N = 1e5 + 7, RT = 320;
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 min(int a, int b) { return a < b ? a : b; }
int n, m, a[N];
int len, block, be[N];
int lef[RT], rig[RT], buc[RT][N];
deque<int> lis[RT];
void init()
{
for (int i = 1; i <= n; i++) a[i] = read();
len = sqrt(n), block = n / len;
if (n % len) block++;
for (int i = 1; i <= block; i++) lef[i] = (i - 1) * len + 1, rig[i] = min(i * len, n);
for (int i = 1; i <= n; i++)
{
be[i] = (i - 1) / len + 1;
buc[be[i]][a[i]]++, lis[be[i]].push_back(a[i]);
}
}
void solve()
{
while (m--)
{
int opt = read(), l = read(), r = read();
if (opt == 1)
{
int firb = be[l], lasb = be[r];
int tmp = lis[lasb].at(r - lef[be[r]]);
lis[lasb].erase(lis[lasb].begin() + r - lef[be[r]]);
buc[lasb][tmp]--;
lis[firb].insert(lis[firb].begin() + l - lef[be[l]], tmp);
buc[firb][tmp]++;
for (int i = firb; i <= lasb - 1; i++)
{
int tmp = lis[i].back();
lis[i].pop_back(), buc[i][tmp]--;
lis[i + 1].push_front(tmp), buc[i + 1][tmp]++;
}
}
else
{
int firb = be[l], lasb = be[r], cnt = 0, k = read();
if (firb == lasb)
{
int p = lef[firb];
deque<int>::iterator it = lis[firb].begin();
while (p < l) it++, p++;
while (p <= r) cnt += (*it == k), it++, p++;
}
else
{
int p = rig[firb];
deque<int>::iterator it = lis[firb].end(); it--;
while (p >= l) cnt += (*it == k), it--, p--;
p = lef[lasb], it = lis[lasb].begin();
while (p <= r) cnt += (*it == k), it++, p++;
for (int i = firb + 1; i <= lasb - 1; i++) cnt += buc[i][k];
}
printf("%d\n", cnt);
}
}
}
int main()
{
freopen("queue.in", "r", stdin);
freopen("queue.out", "w", stdout);
n = read(), m = read();
if (!n) return 0;
init();
solve();
fclose(stdin);
fclose(stdout);
return 0;
}
夜深人静了,只剩我在机房写博客。成功的道路总是孤独的, 明天继续加油吧!