2024.8 - 做题记录与方法总结 - Part5

T2 glass
题目描述

flame 送了mybing n 个具有无限体积的神奇星星瓶。

每个星星瓶里都装了银河中的星光水,mybing想把所有的星光水都倒进他的收藏罐中,但是他只想把不超过 K 个星星瓶的水倒入收藏罐中——于是他决定这么干:把某个星星瓶中的所有星光水倒入另一个星星瓶中,直到还剩下恰好 k 个星星瓶中有星光水。此时他就可以全部导入收藏罐了。

mybing 对这些星星瓶进行编号,他同一时间只能进行一个形如 ”把 i 瓶中的水倒入 j 瓶“ 的操作,因为星星瓶的距离等复杂原因,他执行这一操作所花费的时间是 c_{i,j} ,这一参数受不同的 i,j 影响,但是不受瓶内水量影响。

mybing以最快的速度干完了这件事,然后约了flame去撸猫,临行前他想问你,请问刚刚以最快的方式干完这件事花了多少时间?

输入格式

输入的第一行包含两个正整数 N K(1\leq K\leq N\leq 20)

接下来的 N 行每行 N 个数字,分别表示 C_{i,j}(1≤C_{i,j}≤10^5) 保证所有的 C_{i,i}=0 ,不保证 C_{i,j}=C_{j,i}

输出格式

输出一行一个整数表示最短时间

样例
样例输入 #1
5 2
0 5 4 3 2
7 0 4 4 4
3 3 0 1 2
4 3 1 0 5
4 5 5 5 0
样例输出 #1
5
数据范围与提示

对于其中 40\% 的数据,保证 N\le10

对于其中 100\% 的数据,保证 1≤ K \le N \le 20,1≤C_{i,j}≤10^5

保证所有的 C_{i,i}=0

$ 40% $ 部分分:

我们考虑每一个联通块需要最少需要多少代价

记录连通块,并记录最后倒在了哪个杯子里,进行状态压缩

然后 枚举全集的子集,分成 k k k

O ( 2 2 n n 2 ) \mathcal{O}(2^{2n}n^2) O(22nn2)

TLE-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 22,U = (1<<20) - 1;
int n,k;
int c[N][N],f[1<<20][20],g[1<<20];
int ans = 0x3f3f3f3f3f3f3f3fLL;
bool notIn(int x,int y) {return (x ^ y) & (U ^ x);}
void dfs(int d,int t,int E) {
	if(d == 1) {
		ans = min(ans,t + g[E]);
		return;
	}
	if(t >= ans) return;
	for(int i = 1;i<=E;i++) {
		if(notIn(E,i)) continue;
		if(__builtin_popcount(i) > (n - k + 1)) continue;
		if(__builtin_popcount(i ^ E) < d - 1) continue;
		dfs(d - 1,t + g[i],E ^ i);
	}
}

signed main() {
	memset(f,0x3f,sizeof(f));
	memset(g,0x3f,sizeof(g));
	// freopen("b.in","r",stdin);
	// freopen("b.out","w",stdout);
    n = rd(),k = rd();
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=n;j++)
			c[i][j] = rd();
	if(n == k) {puts("0");return 0;}
	if(k == n - 1) {
		int minn = INT_MAX;
		for(int i = 1;i<=n;i++)
			for(int j = 1;j<=n;j++)
				if(i ^ j)
					minn = min(minn,c[i][j]);
		wt(minn);
		return 0;
	}
	for(int i = 0;i<n;i++)
		for(int j = 0;j<n;j++)
			f[(1 << i) | (1 << j)][j] = c[i + 1][j + 1];
	for(int i = 1;i<(1<<n);i++) {
		if(__builtin_popcount(i) <= 2) continue;
		if(__builtin_popcount(i) > (n - k + 1)) continue;
		for(int j = 1;j<i;j++) {
			if(notIn(i,j)) continue;
			for(int k = 0;k<n;k++)
				if((i >> k) & 1)
					for(int p = 0;p<n;p++)
						if((j >> p) & 1)
							f[i][k] = min(f[i][k],f[i ^ j][k] + f[j][p] + c[p + 1][k + 1]);
		}
	}
	for(int i = 0;i<(1<<n);i++){
		if(__builtin_popcount(i) < 2) {
			g[i] = 0;
			continue;
		}
		for(int j = 0;j<n;j++)
			if((i >> j) & 1)
				g[i] = min(g[i],f[i][j]);
	}
	dfs(k,0,(1<<n) - 1);
	wt(ans);
	return 0;
}

100 100 100 分:

上面的式子显然不能优化,

肯定是状态设计错了

我们要直接推导出答案!

我们直接考虑最终情况,如果对于一个 k k k,ta最后要倒进 t t t 中,那么肯定有一种方式最优

因为杯子不是倒完就消失并且如果需要途径其他的杯子,只会使答案更优(即 需要的 k k k 更小),所以这个最优方式是可以保证的

考虑对邻接矩阵进行 floyd

我们令 f S f_{S} fS 为 最后剩余的杯子为 S S S 01 \TextOrMath{}{01} 01 串),有转移:

f S ⊕ 2 i = min ⁡ 2 j ∈ S ( f S + d i s i , j ) f_{S \oplus 2^i} = \min_{2^j \in S}{(f_{S} + dis_{i,j})} fS2i=2jSmin(fS+disi,j)

然后对 f S f_S fS popcount ⁡ ( S ) ≤ k \operatorname{popcount}(S) \leq k popcount(S)k 的进行更新答案

O ( 2 n n 2 ) \mathcal{O}(2^nn^2) O(2nn2)

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 22,U = (1<<20) - 1;
int c[N][N],f[1<<20];
#define lowbit(x) (x & (-x))

signed main() {
    // freopen("b.in","r",stdin);
    // freopen("b.out","w",stdout);
    memset(f,0x3f,sizeof(f));
    int n = rd(),k = rd();
    for(int i = 0;i<n;i++)
        for(int j = 0;j<n;j++)
            c[i][j] = rd();
    for(int k = 0;k<n;k++)    
        for(int i = 0;i<n;i++)
            for(int j = 0;j<n;j++)
                c[i][j] = min(c[i][j],c[i][k] + c[k][j]);
    int ans = 0x3f3f3f3f3f3f3f3fLL;
    f[(1<<n) - 1] = 0;
    static int st[21] = {},top = 0;
    for(int i = (1<<n) - 1;i;i--) {
        top = 0;
        for(int j = 0;j<n;j++) 
            if((i >> j) & 1) 
                st[++top] = j; 
        for(int x = 1;x <= top;x++)
            for(int y = 1;y <= top;y++){
                if(x == y) continue;
                int k_ = st[x],_k = st[y];
                f[i ^ (1 << k_)] = min(f[i ^ (1 << k_)],f[i] + c[k_][_k]);
            }
        if(__builtin_popcount(i) <= k) 
            ans = min(ans,f[i]);
    }
    wt(ans);
	return 0;
}

2024/08/13

CPU 监控

题面:

题目描述

Bob 需要一个程序来监视 CPU 使用率。这是一个很繁琐的过程,为了让问题更加简单,Bob 会慢慢列出今天会在用计算机时做什么事。

Bob 会干很多事,除了跑暴力程序看视频之外,还会做出去玩玩和用鼠标乱点之类的事,甚至会一脚踢掉电源……这些事有的会让做这件事的这段时间内 CPU 使用率增加或减少一个值;有的事还会直接让 CPU 使用率变为一个值。

当然 Bob 会询问:在之前给出的事件影响下,CPU 在某段时间内,使用率最高是多少。有时候 Bob 还会好奇地询问,在某段时间内 CPU 曾经的最高使用率是多少。

为了使计算精确,使用率不用百分比而用一个整数表示。

不保证 Bob 的事件列表没有出莫名的问题,使得使用率为负………………

输入格式

第一行一个正整数 T T T,表示 Bob 需要监视 CPU 的总时间。

然后第二行给出 T T T 个数表示在你的监视程序执行之前,Bob 干的事让 CPU 在这段时间内每个时刻的使用率达已经达到了多少。

第三行给出一个整数 E E E,表示 Bob 需要做的事和询问的总数。

接下来 E E E 行每行表示给出一个询问或者列出一条事件:

  • Q X Y:询问从 X X X Y Y Y 这段时间内 CPU 最高使用率。
  • A X Y:询问从 X X X Y Y Y 这段时间内之前列出的事件使 CPU 达到过的最高使用率。
  • P X Y Z:列出一个事件这个事件使得从 X X X Y Y Y 这段时间内 CPU 使用率增加 Z Z Z
  • C X Y Z:列出一个事件这个事件使得从 X X X Y Y Y 这段时间内 CPU 使用率变为 Z Z Z

时间的单位为秒,使用率没有单位。

X X X Y Y Y 均为正整数( X ≤ Y X\le Y XY), Z Z Z 为一个整数。

X X X Y Y Y 这段时间包含第 X X X 秒和第 Y Y Y 秒。

保证必要运算在有符号 32 位整数以内。

输出格式

对于每个询问,输出一行一个整数回答。

样例 #1
样例输入 #1
10
-62 -83 -9 -70 79 -78 -31 40 -18 -5 
20
A 2 7
A 4 4
Q 4 4
P 2 2 -74
P 7 9 -71
P 7 10 -8
A 10 10
A 5 9
C 1 8 10
Q 6 6
Q 8 10
A 1 7
P 9 9 96
A 5 5
P 8 10 -53
P 6 6 5
A 10 10
A 4 4
Q 1 5
P 4 9 -69
样例输出 #1
79
-70
-70
-5
79
10
10
79
79
-5
10
10
提示

数据分布如下:

1 , 2 1,2 1,2 个数据保证 T T T E E E 均小于等于 1 0 3 10^3 103

3 , 4 3,4 3,4 个数据保证只有 Q 类询问。

5 , 6 5,6 5,6 个数据保证只有 C 类事件。

7 , 8 7,8 7,8 个数据保证只有 P 类事件。

对于 100 % 100\% 100% 的数据, 1 ≤ T , E ≤ 1 0 5 1\le T,E\le 10^5 1T,E105 1 ≤ X ≤ Y ≤ T 1\le X\le Y\le T 1XYT − 2 31 ≤ Z < 2 31 -2^{31}\leq Z\lt 2^{31} 231Z<231

我们要维护区间最大值、区间历史最大值,支持区间加、区间赋值

其中,查询函数和区间操作函数正常线段树 没有任何区别

主要在于打标记和传递标记 不喜欢分成多函数式写线段树的同学有喜了

针对区间加,我们要记录 sum区间加tagmax_sum历史区间加tag最大值
(因为中间可能存在一次最大区间加,而后面被区间赋值或区间加上一个负数而被顶掉)

针对区间赋值,我们和记录区间加一样————valmax_val

对于区间赋值和区间加的优先级,我们发现:一旦区间被赋值后,我们就可以只针对赋值的数值做操作区间加可以累加到区间赋值的数据上

vis记录是否被进行过区间赋值

对于打区间加tag,我们观察区间赋值情况

如果 v i s p ∈ T r u e vis_p \in \textcolor{green}{True} vispTrue,那么我们可以更新 m a x _ v a l 、 m a x _ a n s max \_ val、max\_ ans max_valmax_ans,将 v a l 、 a n s ← Δ val、ans \leftarrow \Delta valansΔ

否则,我们就更新 m a x _ s u m 、 m a x _ a n s max\_ sum、max\_ ans max_summax_ans,将 s u m 、 a n s ← Δ sum、ans \leftarrow \Delta sumansΔ

对于区间赋值就比较简单,只要取最值,更新一下就可以了


接下来是push_down部分

因为 区间加 的优先级次于 区间赋值 的优先级

所以,我们先将人畜无害的 区间加 下放

记得将标记清零!

然后看一看是否存在区间赋值操作

再将 区间赋值 下放

记得将标记清零!(还有 vis

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}
void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int N = 1e5+5,inf = 0x3f3f3f3f3f3f3f3fLL;
int T,E,a[N];
int ans[N<<2],history_ans[N<<2];
void getmax(int &a,int b) {if(b > a) a = b;}
namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
int sum[N<<2],val[N<<2];
bool vis[N<<2];
int max_sum[N<<2],max_val[N<<2];
void push_up(int p) {
    ans[p] = max(ans[ls],ans[rs]);
    history_ans[p] = max(history_ans[ls],history_ans[rs]);
}
void push_sum(int p,int k,int maxk) {
    if(vis[p]) {
        getmax(max_val[p],val[p] + maxk);
        getmax(history_ans[p],ans[p] + maxk);
        val[p] += k;
    }else {
        getmax(max_sum[p],sum[p] + maxk);
        getmax(history_ans[p],ans[p] + maxk);
        sum[p] += k;
    }
    ans[p] += k;
}
void push_val(int p,int k,int maxk) {
    if(vis[p]) {
        getmax(max_val[p],maxk);
        getmax(history_ans[p],maxk);
    }else {
        vis[p] = true;
        max_val[p] = maxk;
        getmax(history_ans[p],maxk);
    }
    ans[p] = val[p] = k;
}

void push_down(int p){
    push_sum(ls,sum[p],max_sum[p]);
    push_sum(rs,sum[p],max_sum[p]);
    sum[p] = max_sum[p] = 0;
    if(vis[p]) {
        push_val(ls,val[p],max_val[p]);
        push_val(rs,val[p],max_val[p]);
        vis[p] = 0;
        val[p] = max_val[p] = 0;
    }
}

void build(int p,int pl,int pr) {
    if(pl == pr) {
        history_ans[p] = ans[p] = a[pl];
        return;
    }
    build(ls,pl,mid);
    build(rs,mid+1,pr);
    push_up(p);
}

int query(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return ans[p];
    push_down(p);
    int res = -inf;
    if(l <= mid) res = max(res,query(ls,pl,mid,l,r));
    if(r > mid)  res = max(res,query(rs,mid+1,pr,l,r));
    return res;
}

int queryHistoryAns(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return history_ans[p];
    push_down(p);
    int res = -inf;
    if(l <= mid) getmax(res,queryHistoryAns(ls,pl,mid,l,r));
    if(r > mid) getmax(res,queryHistoryAns(rs,mid+1,pr,l,r));
    return res;
}

void add(int p,int pl,int pr,int l,int r,int k) {
    if(l <= pl && pr <= r) {
        push_sum(p,k,k);
        return;
    }
    push_down(p);
    if(l <= mid) add(ls,pl,mid,l,r,k);
    if(r > mid) add(rs,mid+1,pr,l,r,k);
    push_up(p);
}

void assign(int p,int pl,int pr,int l,int r,int k) {
    if(l <= pl && pr <= r) {
        push_val(p,k,k);
        return;
    }
    push_down(p);
    if(l <= mid) assign(ls,pl,mid,l,r,k);
    if(r > mid) assign(rs,mid+1,pr,l,r,k);
    push_up(p);
}

}

void nowMax() {
    int x = rd(),y = rd();
    wt(sgt::query(1,1,T,x,y));
    putchar('\n');
}

void historyAns() {
    int x = rd(),y = rd();
    wt(sgt::queryHistoryAns(1,1,T,x,y));
    putchar('\n');
}

void getadd() {
    int x = rd(),y = rd(),z = rd();
    sgt::add(1,1,T,x,y,z);
}

void getcov() {
    int x = rd(),y = rd(),z = rd();
    sgt::assign(1,1,T,x,y,z);
}

signed main() {
    T = rd();
    for(int i = 1;i<=T;i++)
        a[i] = rd();
    sgt::build(1,1,T);
    E = rd();
    while(E--) {
        char opt = getchar();
        while(opt == ' ' ||opt == '\n') opt = getchar();
        switch(opt) {
            case 'Q':
                nowMax();
                break;
            case 'A':
                historyAns();
                break;
            case 'P':
                getadd();
                break;
            case 'C':
                getcov();
                break;
            default:
                break;
        }
    }
	return 0;
}

2024/08/19

终于回学校了,北京的景点真没什么好玩的

除了烤鸭和炸酱面,其他的都不算好吃

P2303 [SDOI2012] Longge 的问题

题面:

题目背景

Longge 的数学成绩非常好,并且他非常乐于挑战高难度的数学问题。

题目描述

现在问题来了:给定一个整数 n n n,你需要求出 ∑ i = 1 n gcd ⁡ ( i , n ) \sum\limits_{i=1}^n \gcd(i, n) i=1ngcd(i,n),其中 gcd ⁡ ( i , n ) \gcd(i, n) gcd(i,n) 表示 i i i n n n 的最大公因数。

输入格式

输入只有一行一个整数,表示 n n n

输出格式

输出一行一个整数表示答案。

样例 #1
样例输入 #1
6
样例输出 #1
15
提示
数据规模与约定
  • 对于 $60% $ 的数据,保证 n ≤ 2 16 n\leq 2^{16} n216
  • 对于 $100% $ 的数据,保证 1 ≤ n < 2 32 1\leq n< 2^{32} 1n<232

万恶之源: gcd ⁡ \operatorname{gcd} gcd

经典变形:
∑ i = 1 n gcd ⁡ ( i , n ) = ∑ i = 1 n i ∑ j = 1 n [ gcd ⁡ ( j , n ) = i ] \sum\limits_{i = 1}^n\operatorname{gcd}(i,n) = \sum_{i = 1}^n i \sum\limits_{j = 1}^n[\operatorname{gcd}(j,n) = i] i=1ngcd(i,n)=i=1nij=1n[gcd(j,n)=i]

对于每一个约数,我们分开进行计算

一个约数 i i i,成为公因数,有 ϕ ( n i ) \phi(\frac{n}{i}) ϕ(in) 种可能,

因为假设 k = i × d ( d ⊥ n ) k = i \times d (d \bot n) k=i×d(dn),那么 gcd ⁡ ( k i , n i ) = gcd ⁡ ( d , n i ) = gcd ⁡ ( d , n ) = 1 \operatorname{gcd}(\frac{k}{i},\frac{n}{i}) = \operatorname{gcd}(d,\frac{n}{i}) = \operatorname{gcd}(d,n) = 1 gcd(ik,in)=gcd(d,in)=gcd(d,n)=1

故对于一个约数 i i i,有 ans ⁡ ← i × ϕ ( n i ) \operatorname{ans} \leftarrow i \times \phi(\frac{n}{i}) ansi×ϕ(in)

然后做好实现就可以了

时间复杂度: O ( n ϕ ( n ) ) \mathcal{O}(\sqrt{n}\phi(n)) O(n ϕ(n))

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

namespace work{

int phi(int n) {
	int r = n;
	for(int i = 2;i * i <= n;i++) {
		if(n % i == 0) {
			r = r * (i - 1) / i;
			while(n % i == 0) n /= i;
		}
	}
	if(n > 1) r = r * (n - 1) / n;
	return r;
}

int getans(int n) {
	int ans = 0;
	for(int i = 1;i * i <= n;i ++) 
		if(n % i == 0) 
			if(i * i != n)
				ans = (ans + i * phi(n / i) + (n / i) * phi(i));
			else 
				ans = (ans + i * phi(n / i));
	return ans;
}

}

signed main() {
	int n = rd();
	wt(work::getans(n));
	return 0;
}

P3861 拆分

暂留

CF911G Mass Change Queries

题面:

题面翻译

给出一个数列,有q个操作,每种操作是把区间[l,r]中等于x的数改成y.输出q步操作完的数列.

题目描述

You are given an array $ a $ consisting of $ n $ integers. You have to process $ q $ queries to this array; each query is given as four numbers $ l $ , $ r $ , $ x $ and $ y $ , denoting that for every $ i $ such that $ l<=i<=r $ and $ a_{i}=x $ you have to set $ a_{i} $ equal to $ y $ .

Print the array after all queries are processed.

输入格式

The first line contains one integer $ n $ ( $ 1<=n<=200000 $ ) — the size of array $ a $ .

The second line contains $ n $ integers $ a_{1} $ , $ a_{2} $ , …, $ a_{n} $ ( $ 1<=a_{i}<=100 $ ) — the elements of array $ a $ .

The third line contains one integer $ q $ ( $ 1<=q<=200000 $ ) — the number of queries you have to process.

Then $ q $ lines follow. $ i $ -th line contains four integers $ l $ , $ r $ , $ x $ and $ y $ denoting $ i $ -th query ( $ 1<=l<=r<=n $ , $ 1<=x,y<=100 $ ).

输出格式

Print $ n $ integers — elements of array $ a $ after all changes are made.

样例 #1
样例输入 #1
5
1 2 3 4 5
3
3 5 3 5
1 5 5 1
1 5 1 5
样例输出 #1
5 2 5 4 5

线段树合并的进阶:部分合并线段树

我们每次合并线段树,都把整个线段树都合并到一起

在这道题,我们因为权值只有 1 ∼ 100 1 \sim 100 1100 ,非常的小

然后,因为序列长度是有限的,所以用动态开点线段树来对 1 ∼ 100 1 \sim 100 1100 的权值所在的位置建点

因为我们只需要知道这个位置有没有点就可以了,所以不需要什么push_up函数

然后,对于变换操作,我们想到这个题目,有兴趣的可以看一看



题面:

题目描述

Farer John 拥有 n 个牛圈,初始每个牛圈恰好有一头奶牛。奶牛有铜、银、金三种颜色,为了方便,我们分别用数字 123 表示它们。

i 个牛圈的奶牛的初始颜色为 a_i ,Farer John 将 m 次进行以下三种操作之一:

  • 1 l r x y,表示同时将 [l,r] 区间内所有颜色为 x 的奶牛的颜色改为 y ,所有颜色为 y 的奶牛的颜色改为 x

  • 2 l r x y,表示将 [l,r] 区间内所有颜色为 x 的奶牛的颜色改为 y

  • 3 l r x,表示将 [l,r] 区间内所有颜色为 x 的奶牛分裂出一头新的与本体颜色和所处牛圈均完全相同的奶牛。

奶牛 Bassie 只会通过数手指统计各种颜色的奶牛的数量,但奶牛的数量可能会非常庞大,她只好向你求助,你需要在每次操作后分别告诉她三种颜色的奶牛的个数对 10^9+7 取模的值。

输入格式

第一行两个正整数,表示 n,m

第二行 n 个正整数,表示 a

3\sim m+2 行,每行若干个正整数,表示一次操作。

输出格式

m 行,每行三个正整数,分别表示颜色为铜、银、金的奶牛的总数对 10^9+7 取模后的值。

样例
输入 #1
5 3
1 1 2 3 1
1 1 3 1 2
2 2 3 1 2
3 2 4 2
输出 #1
2 2 1
1 3 1
1 5 1
输入 #2
17 12
3 3 2 2 1 1 1 3 3 3 3 3 1 3 2 3 2
1 1 16 1 3
2 10 15 1 2
3 10 12 1
2 7 8 3 1
2 4 14 3 2
1 5 5 1 2
1 10 14 1 2
1 8 9 3 2
3 8 11 3
2 1 14 3 1
1 2 6 2 3
3 7 14 1
输出 #2
9 4 4
5 8 4
5 8 4
6 8 3
6 11 0
7 10 0
12 5 0
12 5 0
12 5 0
12 5 0
12 2 3
20 2 3
数据范围与提示

对于 10\% 的数据,保证 n,m\le 10

对于 20\% 的数据,保证 n,m\le 10^3

对于另外 40\% 的数据,保证不存在 3 操作。

对于 100\% 的数据,保证 1\le l\le r\le n\le 10^5 1\le m\le 10^5 1\le a_i,x,y\le 3 x\ne y



我们去维护这 100 100 100 个数…………吗?

这显然不行,对于这个 2 × 1 0 6 2\times 10^6 2×106 如果用一颗线段树,就得开 n log ⁡ n n\log n nlogn 个点,每个点还得维护转化信息 10 0 2 100^2 1002

但是我们用 100 100 100 颗合并线段树,对于每个权值开一颗线段树,空间复杂度完全可以接受

对于让区间 l , r l,r l,r x → y x \rightarrow y xy,我们就可以让 x x x 线段树上 [ l , r ] [l,r] [l,r] 的部分与 y y y 线段树上 [ l , r ] [l,r] [l,r] 的部分合并

实现:

#define mid ((pl + pr) >> 1)
int lson[N * 50],rson[N * 50],cnt;
#define ls lson[p]
#define rs rson[p]
void update(int &p,int pl,int pr,int k) {
	if(!p) p = ++cnt;
	if(pl == pr) return;
	if(k <= mid) update(ls,pl,mid,k);
	if(k > mid) update(rs,mid+1,pr,k);
	return;
}

int merge(int x,int y,int pl,int pr) {
	if(!x || !y) return x + y;
	if(pl == pr) return x;
	lson[x] = merge(lson[x],lson[y],pl,mid);
	rson[x] = merge(rson[x],rson[y],mid+1,pr);
	return x;
}

void change(int &x,int &y,int pl,int pr,int l,int r) {
	if(!y) return;
	if(l <= pl && pr <= r) {
		x = merge(x,y,pl,pr);
		y = 0;
	}
	if(!x) x = ++cnt;
	if(l <= mid) change(lson[x],lson[y],pl,mid,l,r);
	if(r > mid) change(rson[x],rson[y],mid+1,pr,l,r);
	return;
}

最后对每个线段树上的点进行统计即可
AC-code:

#include<bits/stdc++.h>
using namespace std;
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 200006;
int n,rt[101],ans[N];
namespace sgt{
#define mid ((pl + pr) >> 1)
int lson[N * 50],rson[N * 50],cnt;
#define ls lson[p]
#define rs rson[p]
void update(int &p,int pl,int pr,int k) {
	if(!p) p = ++cnt;
	if(pl == pr) return;
	if(k <= mid) update(ls,pl,mid,k);
	if(k > mid) update(rs,mid+1,pr,k);
	return;
}

int merge(int x,int y,int pl,int pr) {
	if(!x || !y) return x + y;
	if(pl == pr) return x;
	lson[x] = merge(lson[x],lson[y],pl,mid);
	rson[x] = merge(rson[x],rson[y],mid+1,pr);
	return x;
}

void change(int &x,int &y,int pl,int pr,int l,int r) {
	if(!y) return;
	if(l <= pl && pr <= r) {
		x = merge(x,y,pl,pr);
		y = 0;
	}
	if(!x) x = ++cnt;
	if(l <= mid) change(lson[x],lson[y],pl,mid,l,r);
	if(r > mid) change(rson[x],rson[y],mid+1,pr,l,r);
	return;
}

void getId(int p,int pl,int pr,int d) {
	if(!p) return;
	if(pl == pr) {
		ans[pl] = d;
		return;
	}
	getId(ls,pl,mid,d);
	getId(rs,mid+1,pr,d);
}

};

signed main() {
	n = rd();
	for(int i = 1;i <= n;i++) 
		sgt::update(rt[rd()],1,n,i);
	int q = rd();
	while(q--) {
		int l = rd(),r = rd(),x = rd(),y = rd();
		if(x == y) continue;
		sgt::change(rt[y],rt[x],1,n,l,r);
	}
	for(int i = 1;i<=100;i++) 
		sgt::getId(rt[i],1,n,i);
	for(int i = 1;i<=n;i++)
		wt(ans[i]),putchar(' ');

	return 0;
}

CF865D Buy Low Sell High

题面:

题面翻译

题目:
已知接下来N天的股票价格,每天你可以买进一股股票,卖出一股股票,或者什么也不做.N天之后你拥有的股票应为0,当然,希望这N天内能够赚足够多的钱.
输入:
第一行一个整数天数N(2<=N<=300000).
第二行N个数字p1,p2…pN(1<=pi<=10^6),表示每天的价格.
输出: N天结束后能获得的最大利润.
样例解释:
样例1:分别在价格为5,4,2的时候买入,分别在价格为9,12,10的时候卖出,总利润为 − 5 − 4 + 9 + 12 − 2 + 10 = 20 -5-4+9+12-2+10=20 54+9+122+10=20 .
翻译贡献者UID:36080

题目描述

You can perfectly predict the price of a certain stock for the next $ N $ days. You would like to profit on this knowledge, but only want to transact one share of stock per day. That is, each day you will either buy one share, sell one share, or do nothing. Initially you own zero shares, and you cannot sell shares when you don’t own any. At the end of the $ N $ days you would like to again own zero shares, but want to have as much money as possible.

输入格式

Input begins with an integer $ N $ $ (2<=N<=3·10^{5}) $ , the number of days.

Following this is a line with exactly $ N $ integers $ p_{1},p_{2},…,p_{N} $ $ (1<=p_{i}<=10^{6}) $ . The price of one share of stock on the $ i $ -th day is given by $ p_{i} $ .

输出格式

Print the maximum amount of money you can end up with at the end of $ N $ days.

样例 #1
样例输入 #1
9
10 5 4 7 9 12 6 2 10
样例输出 #1
20
样例 #2
样例输入 #2
20
3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4
样例输出 #2
41
提示

In the first example, buy a share at $ 5 $ , buy another at $ 4 $ , sell one at $ 9 $ and another at $ 12 $ . Then buy at $ 2 $ and sell at $ 10 $ . The total profit is $ -5-4+9+12-2+10=20 $ .

反悔贪心

本质就是:抵消选择

比如你选择 i → j i \rightarrow j ij 然后发现 i → k i \rightarrow k ik 更优,那么我们就让 j → k j \rightarrow k jk

只需要在 ans ⁡ ( j − i ) ← k − j ⇒ ans ⁡ ( k − i ) \operatorname{ans}(j - i) \leftarrow k - j \Rightarrow \operatorname{ans}(k - i) ans(ji)kjans(ki)

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

priority_queue<int,vector<int>,greater<int>> q;

signed main() {
	int n = rd(),ans = 0;
	for(int i = 1;i<=n;i++) {
		int c = rd();
		if(!q.empty() && q.top() < c) ans += c - q.top(),q.pop(),q.push(c);
		q.push(c);
	}
	wt(ans);
	return 0;
}

2024/08/20

推荐看番顺序:

凉宫春日的忧郁 → \rightarrow 凉宫春日的消失 → \rightarrow 长门有希酱的消失 → \rightarrow 幸运星

推荐指数: ⋆ ⋆ ⋆ ⋆ ⋆ \star \star \star \star \star

P8868 [NOIP2022] 比赛

题面:

题目描述

小 N 和小 O 会在 2022 年 11 月参加一场盛大的程序设计大赛 NOIP!小 P 会作为裁判主持竞赛。小 N 和小 O 各自率领了一支 n n n 个人的队伍,选手在每支队伍内都是从 1 1 1 n n n 编号。每一个选手都有相应的程序设计水平。具体的,小 N 率领的队伍中,编号为 i i i 1 ≤ i ≤ n 1 \leq i \leq n 1in)的选手的程序设计水平为 a i a _ i ai;小 O 率领的队伍中,编号为 i i i 1 ≤ i ≤ n 1 \leq i \leq n 1in)的选手的程序设计水平为 b i b _ i bi。特别地, { a i } \{a _ i\} {ai} { b i } \{b _ i\} {bi} 还分别构成了从 1 1 1 n n n 的排列。

每场比赛前,考虑到路途距离,选手连续参加比赛等因素,小 P 会选择两个参数 l , r l, r l,r 1 ≤ l ≤ r ≤ n 1 \leq l \leq r \leq n 1lrn),表示这一场比赛会邀请两队中编号属于 [ l , r ] [l, r] [l,r] 的所有选手来到现场准备比赛。在比赛现场,小 N 和小 O 会以掷骰子的方式挑选出参数 p , q p, q p,q l ≤ p ≤ q ≤ r l \leq p \leq q \leq r lpqr),只有编号属于 [ p , q ] [p, q] [p,q] 的选手才能参赛。为了给观众以最精彩的比赛,两队都会派出编号在 [ p , q ] [p, q] [p,q] 内的、程序设计水平值最大的选手参加比赛。假定小 N 派出的选手水平为 m a m _ a ma,小 O 派出的选手水平为 m b m _ b mb,则比赛的精彩程度为 m a × m b m _ a \times m _ b ma×mb

NOIP 总共有 Q Q Q 场比赛,每场比赛的参数 l , r l, r l,r 都已经确定,但是 p , q p, q p,q 还没有抽取。小 P 想知道,对于每一场比赛,在其所有可能的 p , q p, q p,q l ≤ p ≤ q ≤ r l \leq p \leq q \leq r lpqr)参数下的比赛的精彩程度之和。由于答案可能非常之大,你只需要对每一场答案输出结果对 2 64 2 ^ {64} 264 取模的结果即可。

输入格式

第一行包含两个正整数 T , n T, n T,n,分别表示测试点编号和参赛人数。如果数据为样例则保证 T = 0 T = 0 T=0

第二行包含 n n n 个正整数,第 i i i 个正整数为 a i a _ i ai,表示小 N 队伍中编号为 i i i 的选手的程序设计水平。

第三行包含 n n n 个正整数,第 i i i 个正整数为 b i b _ i bi,表示小 O 队伍中编号为 i i i 的选手的程序设计水平。

第四行包含一个正整数 Q Q Q,表示比赛场数。

接下来的 Q Q Q 行,第 i i i 行包含两个正整数 l i , r i l _ i, r _ i li,ri,表示第 i i i 场比赛的参数 l , r l, r l,r

输出格式

输出 Q Q Q 行,第 i i i 行包含一个非负整数,表示第 i i i 场比赛中所有可能的比赛的精彩程度之和对 2 64 2 ^ {64} 264 取模的结果。

样例 #1
样例输入 #1
0 2
2 1
1 2
1
1 2
样例输出 #1
8
样例 #2
样例输入 #2
见附件下的 match/match2.in。
样例输出 #2
见附件下的 match/match2.ans。
样例 #3
样例输入 #3
见附件下的 match/match3.in。
样例输出 #3
见附件下的 match/match3.ans。
提示

【样例 1 解释】

p = 1 , q = 2 p = 1, q = 2 p=1,q=2 的时候,小 N 会派出 1 1 1 号选手,小 O 会派出 2 2 2 号选手,比赛精彩程度为 2 × 2 = 4 2 \times 2 = 4 2×2=4

p = 1 , q = 1 p = 1, q = 1 p=1,q=1 的时候,小 N 会派出 1 1 1 号选手,小 O 会派出 1 1 1 号选手,比赛精彩程度为 2 × 1 = 2 2 \times 1 = 2 2×1=2

p = 2 , q = 2 p = 2, q = 2 p=2,q=2 的时候,小 N 会派出 2 2 2 号选手,小 O 会派出 2 2 2 号选手,比赛精彩程度为 1 × 2 = 2 1 \times 2 = 2 1×2=2

【样例 2】

该样例满足测试点 1 ∼ 2 1 \sim 2 12 的限制。

【样例 3】

该样例满足测试点 3 ∼ 5 3 \sim 5 35 的限制。

【数据范围】

对于所有数据,保证: 1 ≤ n , Q ≤ 2.5 × 1 0 5 1 \leq n, Q \leq 2.5 \times 10 ^ 5 1n,Q2.5×105 1 ≤ l i ≤ r i ≤ n 1 \leq l _ i \leq r _ i \leq n 1lirin 1 ≤ a i , b i ≤ n 1 \leq a _ i, b _ i \leq n 1ai,bin { a i } \{a _ i\} {ai} { b i } \{b _ i\} {bi} 分别构成了>从 1 1 1 n n n 的排列。

测试点 n n n Q Q Q特殊性质 A特殊性质 B
1 , 2 1, 2 1,2 ≤ 30 \leq 30 30 ≤ 30 \leq 30 30
3 , 4 , 5 3, 4, 5 3,4,5 ≤ 3 , 000 \leq 3,000 3,000 ≤ 3 , 000 \leq 3,000 3,000
6 , 7 6, 7 6,7 ≤ 1 0 5 \leq 10 ^ 5 105 ≤ 5 \leq 5 5
8 , 9 8, 9 8,9 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105 ≤ 5 \leq 5 5
10 , 11 10, 11 10,11 ≤ 1 0 5 \leq 10 ^ 5 105 ≤ 5 \leq 5 5
12 , 13 12, 13 12,13 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105 ≤ 5 \leq 5 5
14 , 15 14, 15 14,15 ≤ 1 0 5 \leq 10 ^ 5 105 ≤ 1 0 5 \leq 10 ^ 5 105
16 , 17 16, 17 16,17 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105
18 , 19 18, 19 18,19 ≤ 1 0 5 \leq 10 ^ 5 105 ≤ 1 0 5 \leq 10 ^ 5 105
20 , 21 20, 21 20,21 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105
22 , 23 22, 23 22,23 ≤ 1 0 5 \leq 10 ^ 5 105 ≤ 1 0 5 \leq 10 ^ 5 105
24 , 25 24, 25 24,25 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105 ≤ 2.5 × 1 0 5 \leq 2.5 \times 10 ^ 5 2.5×105

特殊性质 A:保证 a a a 是均匀随机生成的 1 ∼ n 1 \sim n 1n 的排列。

特殊性质 B:保证 b b b 是均匀随机生成的 1 ∼ n 1 \sim n 1n 的排列。

历史版本和线段树(吉司机线段树——SegmentTreeBeats!)

对于每个区间 [ p , q ] ∈ [ l , r ] [p,q] \in [l,r] [p,q][l,r],查询 max ⁡ i = p q a i × max ⁡ j = p q b j \max_{i = p}^q a_i \times \max_{j = p}^q b_j maxi=pqai×maxj=pqbj 并累加 ( mod ⁡ 2 64 ) (\operatorname{mod} 2^{64}) (mod264)

形式化题意为

给定数组 a , b a,b a,b ,对于每一个询问 l , r l,r l,r,返回 ∑ i = l r ∑ j = i r max ⁡ p = i j a p × max ⁡ q = i j b q ( mod ⁡   2 64 ) \sum\limits_{i = l}^r \sum\limits_{j = i}^r \max_{p = i}^j a_p \times \max_{q = i}^j b_q (\operatorname{mod}\ 2^{64}) i=lrj=irmaxp=ijap×maxq=ijbq(mod 264)

首先,我们发现序列最大值会影响到一段区间,我们用单调栈去维护这些区间

然后使用扫描线从 1 1 1 扫到 n n n

本题就转化成维护四个操作:
{ 1.   a l ∼ r ← + x 2.   b l ∼ r ← + x 3.   History ⁡ a n s l ∼ r ← + max ⁡ i = l r a i × ( max ⁡ j = l r b j = x ) ⋁ ( max ⁡ i = l r a i = x ) × max ⁡ j = l r b j 4.  查询 ∑ i = l r ∑ j = i r History ⁡ a n s i ∼ j \begin{cases} 1.\ a_{l\sim r} \leftarrow +x \\ 2.\ b_{l\sim r} \leftarrow +x \\ 3.\ \operatorname{History}ans_{l\sim r} \leftarrow +\max_{i = l}^r a_i \times (\max_{j = l}^r b_j = x) \bigvee (\max_{i = l}^r a_i = x) \times \max_{j = l}^r b_j \\ 4.\ 查询 \sum\limits_{i = l}^r \sum\limits_{j = i}^r\operatorname{History}ans_{i\sim j} \end{cases} 1. alr+x2. blr+x3. Historyanslr+maxi=lrai×(maxj=lrbj=x)(maxi=lrai=x)×maxj=lrbj4. 查询i=lrj=irHistoryansij

剩下的参考这篇题解

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
int n,q;
const int N = 3e5+5;
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
struct node{
	ull taga,tagb,s,sa,sb,h,ha,hb,upd,ans,len;
}t[N<<2];
ull a[N],b[N],ans[N];

void push_up(int p) {
	t[p].s = t[ls].s + t[rs].s;
	t[p].sa = t[ls].sa + t[rs].sa;
	t[p].sb = t[ls].sb + t[rs].sb;
	t[p].ans = t[ls].ans + t[rs].ans;
}
void addtag(int p,node d) {
	t[p].ans += t[p].s * d.upd + t[p].sa * d.hb + t[p].sb * d.ha + d.h * t[p].len;
	t[p].h += t[p].taga * t[p].tagb * d.upd + t[p].taga * d.hb + t[p].tagb * d.ha + d.h;
	t[p].ha += t[p].taga * d.upd + d.ha;
	t[p].hb += t[p].tagb * d.upd + d.hb;
	t[p].s += t[p].sa * d.tagb + t[p].sb * d.taga + d.taga * d.tagb * t[p].len;
	t[p].sa += d.taga * t[p].len; 
	t[p].sb += d.tagb * t[p].len;
	t[p].upd += d.upd;
	t[p].taga += d.taga;
	t[p].tagb += d.tagb;
}

void push_down(int p) {
	addtag(ls,t[p]);
	addtag(rs,t[p]);
	t[p].h = t[p].ha = t[p].hb = t[p].upd = t[p].taga = t[p].tagb = 0;
}

void build(int p,int pl,int pr) {
	t[p].len = (pr - pl + 1);
	if(pl == pr) return;
	build(ls,pl,mid);
	build(rs,mid+1,pr);
}

void update(int p,int pl,int pr,int l,int r,ull x,int opt) {
	if(l <= pl && pr <= r){
		if(opt) addtag(p,(node){0,x,0,0,0,0,0,0,0,0,pr - pl + 1});
		else addtag(p,(node){x,0,0,0,0,0,0,0,0,0,pr - pl + 1});
		return;
	}
	push_down(p);
	if(l <= mid) update(ls,pl,mid,l,r,x,opt);
	if(r > mid) update(rs,mid+1,pr,l,r,x,opt);
	push_up(p);
}

ull query(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return t[p].ans;
	ull re = 0;
	push_down(p);
	if(l <= mid) re += query(ls,pl,mid,l,r);
	if(r > mid) re += query(rs,mid+1,pr,l,r);
	return re;
}

vector<array<int,2>> Q[N];
int sta[N],topa,stb[N],topb;

signed main() {
	int T;
	scanf("%d %d",&T,&n);
	for(int i = 1;i<=n;i++) scanf("%llu",&a[i]);
	for(int i = 1;i<=n;i++) scanf("%llu",&b[i]);
	scanf("%d",&q);
	for(int i = 1;i<=q;i++) {
		int l,r;
		scanf("%d %d",&l,&r);
		Q[r].emplace_back(array<int,2>{l,i});
	}
	build(1,1,n);
	topa = topb = 1;
	b[0] = a[0] = n + 1;
	for(int i = 1;i<=n;i++) {
		while(a[sta[topa]] < a[i]) {
			update(1,1,n,sta[topa - 1] + 1,sta[topa],-a[sta[topa]],0);
			topa--;
		}
		update(1,1,n,sta[topa] + 1,i,a[i],0);
		sta[++topa] = i;
		while(b[stb[topb]] < b[i]) {
			update(1,1,n,stb[topb - 1] + 1,stb[topb],-b[stb[topb]],1);
			topb--;
		}
		update(1,1,n,stb[topb] + 1,i,b[i],1);
		stb[++topb] = i;
		addtag(1,(node){0,0,0,0,0,0,0,0,1,0,0});
		for(auto k : Q[i]) ans[k[1]] = query(1,1,n,k[0],i);
	}
	for(int i = 1;i<=q;i++) printf("%llu\n",ans[i]);

	return 0;
}

CF960H Santa’s Gift

题面:

题面翻译
  • 给出一棵 n n n 个节点的有根树,根为 1 1 1。点 2 ∼ n 2\sim n 2n 的父亲为 p 2 ∼ p n p_2\sim p_n p2pn。有 m m m 种颜色。点 i i i 的颜色为 f i f_i fi,颜色 i i i 的权值为 c i c_i ci。有两种操作共 q q q 次:

    • 1   x   y \texttt{1 }x\text{ }y x y,将 f x ← y f_x\leftarrow y fxy

    • 2   x \texttt{2 }x x,从树中等概率选取一个点 i i i,得到 ( S i × b x − C ) 2 (S_i\times b_x-C)^2 (Si×bxC)2 的价值。求期望价值。其中 S i S_i Si 表示以 i i i 为根的子树中颜色为 x x x 的节点数。 C C C 是给定的常数。

输入格式

  • 第一行四个自然数 n , m , q , C n,m,q,C n,m,q,C 2 ≤ n ≤ 5 × 1 0 4 2\le n\le 5\times 10^4 2n5×104 1 ≤ m , q ≤ 5 × 1 0 4 1\le m,q\le 5\times 10^4 1m,q5×104 0 ≤ C ≤ 1 0 6 0\le C\le 10^6 0C106)。

  • 第二行 n n n 个正整数 f 1 ∼ f n f_1\sim f_n f1fn 1 ≤ f i ≤ m 1\le f_i\le m 1fim)。

  • 第三行 n − 1 n-1 n1 个正整数为 p 2 ∼ p n p_2\sim p_n p2pn 1 ≤ p i ≤ n 1\le p_i\le n 1pin)。

  • 第四行 m m m 个正整数 b 1 ∼ b m b_1\sim b_m b1bm 1 ≤ c i ≤ 100 1\le c_i\le 100 1ci100)。

  • 接下来 q q q 行每行为一个操作 1   x   y \texttt{1 }x\text{ }y x y 2   x \texttt{2 }x x

输出格式

对于 2 \texttt{2} 2 操作,输出期望。

题目描述

Santa has an infinite number of candies for each of $ m $ flavours. You are given a rooted tree with $ n $ vertices. The root of the tree is the vertex $ 1 $ . Each vertex contains exactly one candy. The $ i $ -th vertex has a candy of flavour $ f_i $ .

Sometimes Santa fears that candies of flavour $ k $ have melted. He chooses any vertex $ x $ randomly and sends the subtree of $ x $ to the Bakers for a replacement. In a replacement, all the candies with flavour $ k $ are replaced with a new candy of the same flavour. The candies which are not of flavour $ k $ are left unchanged. After the replacement, the tree is restored.

The actual cost of replacing one candy of flavour $ k $ is $ c_k $ (given for each $ k $ ). The Baker keeps the price fixed in order to make calculation simple. Every time when a subtree comes for a replacement, the Baker charges $ C $ , no matter which subtree it is and which flavour it is.

Suppose that for a given flavour $ k $ the probability that Santa chooses a vertex for replacement is same for all the vertices. You need to find out the expected value of error in calculating the cost of replacement of flavour $ k $ . The error in calculating the cost is defined as follows.

$ $KaTeX parse error: Can't use function '$' in math mode at position 72: …\ Bakers) ^ 2. $̲ $ </p><p>Note …$.

输入格式

The first line of the input contains four integers $ n $ ( $ 2 \leqslant n \leqslant 5 \cdot 10^4 $ ), $ m $ , $ q $ , $ C $ ( $ 1 \leqslant m, q \leqslant 5 \cdot 10^4 $ , $ 0 \leqslant C \leqslant 10^6 $ ) — the number of nodes, total number of different flavours of candies, the number of queries and the price charged by the Bakers for replacement, respectively.

The second line contains $ n $ integers $ f_1, f_2, \dots, f_n $ ( $ 1 \leqslant f_i \leqslant m $ ), where $ f_i $ is the initial flavour of the candy in the $ i $ -th node.

The third line contains $ n - 1 $ integers $ p_2, p_3, \dots, p_n $ ( $ 1 \leqslant p_i \leqslant n $ ), where $ p_i $ is the parent of the $ i $ -th node.

The next line contains $ m $ integers $ c_1, c_2, \dots c_m $ ( $ 1 \leqslant c_i \leqslant 10^2 $ ), where $ c_i $ is the cost of replacing one candy of >flavour $ i $ .

The next $ q $ lines describe the queries. Each line starts with an integer $ t $ ( $ 1 \leqslant t \leqslant 2 $ ) — the type of the query.

If $ t = 1 $ , then the line describes a query of the first type. Two integers $ x $ and $ w $ follow ( $ 1 \leqslant  x \leqslant  n $ , $ 1 \leqslant  w \leqslant m $ ), it means that Santa replaces the candy at vertex $ x $ with flavour $ w $ .

Otherwise, if $ t = 2 $ , the line describes a query of the second type and an integer $ k $ ( $ 1 \leqslant k \leqslant m $ ) follows, it means that you should print the expected value of the error in calculating the cost of replacement for a given flavour $ k $ .

The vertices are indexed from $ 1 $ to $ n $ . Vertex $ 1 $ is the root.

输出格式

Output the answer to each query of the second type in a separate line.

Your answer is considered correct if its absolute or relative error does not exceed $ 10^{-6} $ .

Formally, let your answer be $ a $ , and the jury’s answer be $ b $ . The checker program considers your answer correct if and only if $ \frac{|a-b|}{max(1,b)}\leqslant 10^{-6} $ .

样例 #1
样例输入 #1
3 5 5 7
3 1 4
1 1
73 1 48 85 89
2 1
2 3
1 2 3
2 1
2 3
样例输出 #1
2920.333333333333
593.000000000000
49.000000000000
3217.000000000000
提示

For $ 1 $ -st query, the error in calculating the cost of replacement for flavour $ 1 $ if vertex $ 1 $ , $ 2 $ or $ 3 $ is chosen are $ 66^2 $ , $ 66^2 $ and $ (-7)^2 $ respectively. Since the probability of choosing any vertex is same, therefore the expected value of error is $ \frac{662+662+(-7)^2}{3} $ .

Similarly, for $ 2 $ -nd query the expected value of error is $ \frac{412+(-7)2+(-7)^2}{3} $ .

After $ 3 $ -rd query, the flavour at vertex $ 2 $ changes from $ 1 $ to $ 3 $ .

For $ 4 $ -th query, the expected value of error is $ \frac{(-7)2+(-7)2+(-7)^2}{3} $ .

Similarly, for $ 5 $ -th query, the expected value of error is $ \frac{892+412+(-7)^2}{3} $ .

遇到式子不要慌,第一步:

拆式子!
∑ ( S i × b x − C ) 2 n = b x 2 ∑ S i 2 − 2 C b x ∑ S i + n C 2 n = b x 2 ∑ S i 2 − 2 C b x ∑ S i n + C \begin{aligned} \frac{\sum(S_i \times b_x - C)^2}{n} &= \frac{b_x^2 \sum S_i^2 - 2 C b_x \sum S_i + nC^2}{n} \\ &= \frac{b_x^2 \sum S_i^2 - 2 C b_x \sum S_i}{n} + C \\ \end{aligned} n(Si×bxC)2=nbx2Si22CbxSi+nC2=nbx2Si22CbxSi+C

这个 S i S_i Si 怎么办? 将 S i S_i Si 可能影响到的节点全部加上 1 1 1

也就是说,在根节点到 x x x 节点的路径区间加

维护 区间和区间平方和

区间和不在这里讲解

区间平方和如何处理?

因为 ∑ ( x + d ) 2 = ∑ ( x 2 + 2 d x + d 2 ) \sum(x + d)^2 = \sum (x^2 + 2dx + d ^ 2) (x+d)2=(x2+2dx+d2)

所以 Δ = ∑ ( x + d ) 2 − ∑ x 2 = ∑ ( 2 d x + d 2 ) = 2 d ∑ x + d 2 × l e n \Delta = \sum(x + d)^2 - \sum x^2 =\sum (2dx + d ^ 2) = 2d\sum x + d^2 \times len Δ=(x+d)2x2=(2dx+d2)=2dx+d2×len

就解决了,这应该是最好写的ds紫题

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long		
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int N = 5e4 + 5;
int n,m,Q,C,f[N],b[N];
int head[N],nxt[N<<1],to[N<<1],cnt;
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}
int fa[N],top[N],siz[N],dep[N],son[N],num,id[N];
void dfs1(int x,int f) {
	fa[x] = f;
	siz[x] = 1;
	dep[x] = dep[f] + 1;
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ f) {
			dfs1(y,x);
			siz[x] += siz[y];
			if(siz[son[x]] < siz[y]) son[x] = y;
		}
	}
}
int rt[N * 100],ls[N*100],rs[N * 100],tag[N * 100],s[N * 100],v[N * 100];
#define ls (ls[p])
#define rs (rs[p])
#define mid ((pl + pr) >> 1)
void dfs2(int x,int topx) {
	top[x] = topx;
	id[x] = ++num;
	if(!son[x]) return;
	dfs2(son[x],topx);
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ fa[x] && y ^ son[x]) dfs2(y,y);
	} 
}

void push_up(int p) {
	s[p] = s[ls] + s[rs];
	v[p] = v[ls] + v[rs];
}

void addtag(int &p,int pl,int pr,int d) {
	if(!p) p = ++cnt;
	s[p] += 2 * d * v[p] + (pr - pl + 1) * d * d;
	v[p] += d * (pr - pl + 1);
	tag[p] += d;
}

void push_down(int p,int pl,int pr) {
	if(tag[p]) {
		addtag(ls,pl,mid,tag[p]);
		addtag(rs,mid+1,pr,tag[p]);
		tag[p] = 0;
	}
}

void update(int &p,int pl,int pr,int l,int r,int d) {
	if(!p) p = ++cnt;
	if(l <= pl && pr <= r) {addtag(p,pl,pr,d);return;}
	push_down(p,pl,pr);
	if(l <= mid) update(ls,pl,mid,l,r,d);
	if(r > mid) update(rs,mid+1,pr,l,r,d);
	push_up(p);
}

void update(int p,int x,int d,int y = 1) {
	while(top[x] ^ top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		update(rt[p],1,n,id[top[x]],id[x],d);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x,y);
	update(rt[p],1,n,id[x],id[y],d);
}

void update() {
	int x = rd(),y = rd();
	update(f[x],x,-1);
	f[x] = y;
	update(f[x],x,1);
}	

void query() {
	int x = rd();
	printf("%.10lf\n",(double)(b[x] * b[x] * s[rt[x]] - 2 * b[x] * C * v[rt[x]]) / (double)(n) + (double)C * C);

}

signed main() {
	init();
	n = rd(),m = rd(),Q = rd(),C = rd();
	for(int i = 1;i<=n;i++) f[i] = rd();
	for(int i = 2;i<=n;i++) {
		int p = rd();
		add(i,p),add(p,i);
	}
	for(int i = 1;i<=m;i++) b[i] = rd();
	dfs1(1,0);dfs2(1,1);
	for(int i = 1;i<=n;i++) update(f[i],i,1);
	while(Q--) {
		int opt = rd();
		switch(opt) {
			case 1:
				update();
				break;
			case 2:
				query();
				break;
			default:
				break;
		}
	}
	
	return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值