做题策略的总结
这场比赛吸取了昨天的教训,上来直接把暴力打完了,忍住没去想正解。预估分数是120,但是由于我没有梦想,打了一个特判点却开的是暴力的数据范围,于是少了10分。还有打T3暴力的时候太颓废,没有打k=1的点,白白少了10分。
最终分数110,我还是太菜了。
T1
这是一道非常NOIP的差分。
数据范围能够启发人类智慧(@cyc):
的做法比较简单,可以得到
的好成绩。
如果动动脑子发现
也很简单,可以得到
的好成绩。
但就是这个
可以启发你想出正解。
假如我们的操作变成
加上1,只需要差分一下就可以。
如果操作变成对于每个
,
加上
,相当于把区间里的一段对应加上一个等差数列,我们可以将原数列
的差分数列
再差分一次得到数列
。在
打标记以后求前缀和得到
,再根据
得到
。
上面的分别是加上 和加上 的情况。
那么如果加上的是
?
我们可以把
的差分数列
搞出来,在
上修改,然后得到
,由
得到
,再由
得到
。
由此我们不难发现,如果要应对一般情况,我们只需要维护一个 阶的差分数组,最后从 到 还原上去。
关于什么是 阶差分数组,将 差分得到 , 就是 阶差分数组,将 差分得到 , 就是 阶差分数组,以此类推。
写的很丑压线过去的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难度(如果
还算个p的NOIP啊)。
这题是很经典的递推了,之前听炜哥讲过但并没有认真去想,这次吃亏了。
暴力枚举每一条边选不选,得到了
的good grades.
然而没打
,少
。
首先解决一个问题:求有
个带标号的点的无向连通图数目。
设
表示有
个带标号的点的无向连通图数目。
我们考虑求出不连通的个数,然后用总数去减。
后面那一串的意思是,我选出
个点成为一个新的连通块,使它与另外
个点断开。然后选一个点固定它,在另外
个点里选
个陪它组成大小为
的连通块,上面的递推式就得出来了。
然后再考虑答案怎么求,我们设
表示前
个点组成的无向图,最大连通块大小
的方案数,类似于上面的方法,可以得到:
我们用
的方案数减去
的方案数即可得到刚好等于
的方案数。
很丑的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
数据范围启发人类智慧:
无脑暴力。
整体翻转的数据打主席树就可以get到
的好成绩。
简单的分块算法。
对于整个数列分块,每一块维护一个桶记录块内数的出现次数,并维护一个双端队列,存放当前块内元素。
每次修改,先把最右边的块的元素放到最左边的块对应的位置,由于每一块维护的元素最多只有
个,这个复杂度就是
,再把中间的块每一块最后一个元素放到下一块开头,最多有
个块,这个复杂度也是
。
每次查询,两端多余的部分暴力统计,中间的块用桶记录,这样复杂度也是
,于是总共复杂度就是
,评测时吸氧,可以在
内通过。
这数据结构题码的就是爽。
写得一般般的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;
}
夜深人静了,只剩我在机房写博客。成功的道路总是孤独的, 明天继续加油吧!