是 zzq 神仙出的题。
而且看到了 zzq 神仙本人啦!
T1
奇妙的构造,有些眼熟但应该没见过。那一道是环上交换前缀和的毒瘤题,不会。
这个一开始想分成若干段,长度为 20 ,然后每一段分别使用最优解法,发现答案并不是很优,只有 30pts 的样子。
考虑如果这段的开头是 0 ,那么把整段后移,不会更劣,似乎确实优了一点点,大概能有个 30 多分。
想把 20 变成更大一点的数,在时间复杂度允许的情况下大概是不到 40 分。
然后乱搞,对于每一个 1 ,向后枚举 100 个位置,看是否出现了符合要求的三个 1 ,然后把它们一起删掉。
发现优化的非常明显,可以直接分数拿满。
zzq说我们吊打了 IOI AKer和国家队(
正解就是找当前这个 1 有没有办法给他和另外两个一起删掉,否则使用奇怪的方法把它删掉。
T2
神仙题,仅对于我来说,毕竟我菜。
首先想到一定是树形结构,组成了一个森林,如果使用 LCT 没准不错可惜我不会 LCT。朴素想法使用 (O(n^3)) 暴力,可惜没有分。思考平方做法,或者带log。我们每次将一个点改变父亲的时候,把它的儿子的 fa 倍增数组给重构出来,可以在 (O(n^2logn)) 的时间复杂度内修改,在 (O(n^2logn)) 的复杂度内查询。然后发现我干嘛构建倍增数组,直接维护根就完了。时间复杂度 (O(n^2))
考虑如何解决随机数据。可以发现修改的时间消耗大概不会很多,期望应该是 (O(logn)) 重构一次。看怎么处理询问。给每个点维护出应该的颜色,然后使用分块维护一下,可以 (sqrt{n}) 进行询问。
正解也有分块。离线做法,将询问分块,发现每块内父亲改变的点只有 (sqrt{n}) 个,维护这些点,修改的时候暴力重构即可,复杂度是单次 (O(sqrt{n})) 。考虑询问。我们认为这些特殊点有各自的颜色,这个可以维护。找被一个特殊点统帅的点有哪些,对于一个特殊点,分块维护每一块里的被统帅点个数,然后前缀和预处理一下。查询的时候对于整块,枚举特殊点查询,对于散点,查看他的被哪个特殊点统帅,计入贡献。对于那些不被统帅的非特殊点。随便用个数据结构比如说树状数组维护一下就好了。最终复杂度可以 (O(nsqrt{n}))。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
int read()
{
int a = 0,x = 1;char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') x = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {a = a*10 + ch-'0';ch = getchar();}
return a*x;
}
const int N=2e5+7;
int n,fa[N],a[N],q,b,head[N],go[N],nxt[N],cnt,anc[N];
struct node{int op,x,y;}arr[N];
void add(int u,int v)
{
go[++cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
int _fa[N],_head[N],_go[N],_nxt[N],_cnt,_a[N],s[N][400],vis[N],col[N];
vector<int>g;
void _add(int u,int v)
{
_go[++_cnt] = v;
_nxt[_cnt] = _head[u];
_head[u] = _cnt;
}
void dfs(int u,int h)
{
int ret = 1;
for(int e = head[u];e;e = nxt[e]) {
int v = go[e];a[v] = a[u];
dfs(v,vis[u] ? u : h);
}
_fa[u] = h;if(vis[u]) h = u,ret = 0,_a[u] = a[u],g.push_back(u);anc[u] = h;
if(anc[u] == u && _fa[u]) _add(_fa[u],u);
s[h][col[u]] ++;
}
void update(int u)
{
for(int e = _head[u];e;e = _nxt[e]) {
int v = _go[e];if(_fa[v] != u || _a[v] == _a[u]) continue;
_a[v] = _a[u];update(v);
}
}
int S[N];
void Add(int p,int x)
{
for(int i = p;i <= n;i += i&-i) S[i] += x;
}
int query(int p)
{
int ret = 0;
for(int i = p;i >= 1;i -= i&-i) ret += S[i];
return ret;
}
int query(int l,int r)
{
int ret = 0;
if(col[r] > col[l]) for(auto o : g) {
ret += (s[o][col[r]-1] - s[o][col[l]])*_a[o];
}
for(int i = l;i <= r && col[l] == col[i];i ++) {
ret += anc[i] ? _a[anc[i]] : 0;
}
if(col[l] != col[r]) for(int i = r;i >= l && col[i] == col[r];i --) {
ret += _a[anc[i]];
}
return ret + query(r) - query(l-1);
}
void solve(int l,int r)
{
for(int i = l;i <= r;i ++) {
if(arr[i].op <= 2) vis[arr[i].x] = 1;
}
for(int i = 1;i <= n;i ++) head[i] = _head[i] = S[i] = 0;_cnt = cnt = 0;
for(int i = 1;i <= n;i ++) {
if(fa[i] != i) add(fa[i],i);
}g.clear();
for(int i = 1;i <= n;i ++) {
if(fa[i] == i) {dfs(i,0);}
}
for(auto o : g) {
for(int i = 1;i <= col[n];i ++) s[o][i] += s[o][i-1];
}
for(int i = 1;i <= n;i ++) {
if(anc[i] == 0) Add(i,a[i]);
}
for(int i = l;i <= r;i ++) {
if(arr[i].op == 1) {
_fa[arr[i].x] = arr[i].x;_a[arr[i].x] = arr[i].y;
update(arr[i].x);
} else if(arr[i].op == 2) {
_fa[arr[i].x] = anc[arr[i].y],_a[arr[i].x] = anc[arr[i].y] ? _a[anc[arr[i].y]] : a[arr[i].y];
if(_fa[arr[i].x]) _add(_fa[arr[i].x],arr[i].x);
update(arr[i].x);
} else {
printf("%d
",query(arr[i].x,arr[i].y));
}
}
for(auto o : g) {
for(int i = 1;i <= col[n];i ++) s[o][i] = 0;
}
for(int i = l;i <= r;i ++) {
if(arr[i].op <= 2) vis[arr[i].x] = 0;
}
}
int main()
{
freopen("cells.in","r",stdin);freopen("cells.out","w",stdout);
n = read(),q = read();b = n;
for(int i = 1;i <= n;i ++) a[i] = read(),fa[i] = i,col[i] = (i-1)/b+1;
for(int i = 1;i <= q;i ++) {
arr[i].op = read(),arr[i].x = read(),arr[i].y = read();
}
for(int i = 1;i <= q;i ++) {
if(i%b == 1) solve(i,min(q,i+b-1));
if(arr[i].op == 1) a[arr[i].x] = arr[i].y,fa[arr[i].x] = arr[i].x;
else if(arr[i].op == 2) fa[arr[i].x] = arr[i].y;
}
}
T3
神仙构造,并不会做。
对于 (m = 1) 的时候,只有 (n = 1) 时有解。
对于每一个铜丝全都小于等于 2 的,把长度为 2 的贴边放剩下的空位填 1 就好了。
不小于 2 的,把所有大于 2 的左端弯折一下,然后剩下的空位用 2 补齐。长度为 n 的铜丝需要 n-3 个 2。
忘记输出 yes
少了 40pts 的痛……