CF2006 div.1

B Iris and the Tree

原题目结点按 序编号,那么每条边只会被两个询问覆盖,暴力修改统计即可。

表示当前边还未全部出现的询问个数, 为当前出现的边权和,则现在的答案为 (考虑满的链直接记录总长,未满的链考虑每条边的贡献整理后得到此式)。

下面我们考虑一般情况,当结点编号任意或者给定树上的 条链作为询问时。

不难发现我们仍可以用原题计算答案的方式,如果我们知道当前还没满的边的数量 ,已出现的边权和 ,已出现边的贡献 ,那么答案为

下面就来考虑如何求

直接考虑每条边的贡献,即每条边被几个询问覆盖。我们可以对每条链求端点的 通过树上差分的方法求出。

由于操作可以离线,我们可以定义边权为这条边出现的时间,通过树上倍增(边权下放点权,求链上最值)等方式就可以求出每条链上最晚出线的边的时间。进而得到每条边出现时会导致多少条链满了。

而且由于直接给出父亲,我们甚至不需要建出这棵树,因为倍增和差分都是自下而上更新的。

复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
const int N = 2e5 + 10;
int T, n, x[N], f[N][18], g[N][18], cf[N], cnt[N], dep[N];
ll w, y[N], S2, S;
void getlca(int x, int y, int &lca, int &mx)
{
if (dep[x] < dep[y])
swap(x, y);
for (int p = 17; p >= 0; p--)
if (dep[f[x][p]] >= dep[y])
mx = max(mx, g[x][p]), x = f[x][p];
if (x == y)
return lca = x, void();
for (int p = 17; p >= 0; p--)
if (f[x][p] != f[y][p])
{
mx = max(mx, max(g[x][p], g[y][p]));
x = f[x][p], y = f[y][p];
}
mx = max(mx, max(g[x][0], g[y][0]));
return lca = f[x][0], void();
}
int main()
{
read(T);
while (T--)
{
read(n), read(w);
for (int i = 1; i <= n; i++)
cf[i] = cnt[i] = 0;
for (int i = 2; i <= n; i++)
read(f[i][0]), dep[i] = dep[f[i][0]] + 1;
for (int j = 1; j <= 17; j++)
for (int i = 1; i <= n; i++)
f[i][j] = f[f[i][j - 1]][j - 1];
for (int i = 2; i <= n; i++)
read(x[i]), read(y[i]), g[x[i]][0] = i;
for (int j = 1; j <= 17; j++)
for (int i = 1; i <= n; i++)
g[i][j] = max(g[f[i][j - 1]][j - 1], g[i][j - 1]);
for (int i = 1, x, y, lca, mx; i <= n; i++)
{
x = i, y = i % n + 1;
cf[x]++;
cf[y]++;
mx = 0;
getlca(x, y, lca, mx);
cf[lca] -= 2;
cnt[mx]++;
}
for (int i = n; i; i--)
cf[f[i][0]] += cf[i];
S = S2 = 0;
for (int i = 2, sum = n; i <= n; i++)
{
sum -= cnt[i];
S += y[i];
S2 += y[i] * cf[x[i]];
write((w - S) * sum + S2, i < n ? ' ' : '\n');
}
}
flushout();
return 0;
}

C Eri and Expanded Sets

题目大意

对于集合 现有操作,任选 中的两个值不相同但奇偶性相同的数 加入

我们称一个集合是‘好的’当且仅当这个集合中出现的数是连续的,即

现给定一个长为 的序列 求有多少个子区间 满足将该区间的值取出作为初始 ,再进行若干次操作后能变为‘好的’集合。

题解

如果两个数的差是偶数,我们就能在这两个数中间加入一个新数(之前不存在)。

手玩几组样例不难发现,经过若干次操作之后最终集合一定是一个公差为奇数的等差数列。

因为首先相邻数的差肯定是奇数,不然显然能继续加新数。

其次如果相邻的两个差不相等,不妨设 那么对 操作得到的数显然不是 也能继续操作。

我们进一步观察相邻差不相等的操做,假设相邻的差分别为 ,那么新增一个数后这两个差将被分成

发现这个操作类似辗转相减法,只是多了个除以 。但由于 都是奇数,所以他们的 也是奇数,故

所以最终的公差就是排序后差分数组的 ,又有排序的 等于乱序的

就是求 的差分数组去除因子 后,区间 等于 的子区间数量。

当我们枚举左端点时,最左端的合法右端点显然是单调不降的,用双指针+线段树/st表维护区间 即可。

除此之外,当区间全相等时也是符合要求的但我们并没有记录,所以还需单独计算所有相等的子区间数量。

复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const int N=4e5+10;
int T,n,a[N],tr[N<<2],d[N];
void build(int rt,int l,int r)
{
if(l==r)return tr[rt]=d[l],void();
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
tr[rt]=__gcd(tr[ls],tr[rs]);
return;
}
int res;
int query(int rt,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return tr[rt];
int mid=(l+r)>>1;
if(R<=mid)return query(ls,l,mid,L,R);
if(mid<L)return query(rs,mid+1,r,L,R);
return __gcd(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));

}
int main()
{
read(T);
while(T--)
{
read(n);
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<n;i++)
{
d[i]=abs(a[i+1]-a[i]);
while(d[i]%2==0&&d[i])d[i]/=2;
}
if(n>1)build(1,1,n-1);
ll ans=n;
for(int i=1,r1=1,r2=0;i<n;i++)
{
r1=max(r1,i);
while(r1<n&&query(1,1,n-1,i,r1)!=1)r1++;
ans+=n-r1;
r2=max(r2,i-1);
while(r2<n-1&&d[r2+1]==0)r2++;
ans+=r2-i+1;
write(ans);
}
flushout();
return 0;
}

D Iris and Adjacent Products

题解

考虑最优排序一定是大的一半倒序,小的一半正序交错排。

所以只要 满足大于 的数的个数比小于 的数少即可。(取等号的细节自行考虑)。

那么一个显然的想法就是直接开个 的数组统计相应数的个数,但被卡空间了。

那么我们可以使用伟大的离线算法‘莫队’,这样我们只需一个 的数组统计个数即可。

也可以离线对每个 都做一次。

复杂度:

代码:咕咕咕。