]]>洛谷P3674 P3674 小清新人渣的本愿
1操作化简以后bitset统计答案左移x可以看成当前数+x , 2操作注意要另加一个bitset,这是因为可能有负数存不了,3操作直接枚举因子。
树上差分模板题,注意点和边的差分有一点区别
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<bitset>
#include<cmath>
using namespace std;
const int N = 1e5 + 10;
const int up = 18;
#define lowbit(x) (x&(-x))
typedef long long ll;
struct Edge{
int to, next;
}edge[N << 1];
int head[N], tot, dep[N];
ll ev[N], nv[N];
int fat[N][20];
int n, m, k;
void addedge(int from, int to)
{
edge[tot].to = to;
edge[tot].next = head[from];
head[from] = tot++;
}
void dfs(int u, int fa)
{
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == fa) continue;
dep[v] = dep[u] + 1;
fat[v][0] = u;
dfs(v, u);
}
}
void Dfs(int u, int fa)
{
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == fa) continue;
Dfs(v, u);
ev[u] += ev[v];
nv[u] += nv[v];
}
}
void dp()
{
for (int j = 1; j <= up; j++)
{
for (int i = 1; i <= n; i++)
{
fat[i][j] = fat[fat[i][j - 1]][j - 1];
}
}
}
int lca(int u, int v)
{
if (dep[u] < dep[v]) swap(u, v);
for (int j = up; j >= 0; j--)
{
if ((dep[u] - dep[v]) >> j & 1){
u = fat[u][j];
}
}
if (u == v) return u;
for (int j = up; j >= 0; j--)
{
if (fat[u][j] != fat[v][j]){
u = fat[u][j];
v = fat[v][j];
}
}
return fat[u][0];
}
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
memset(ev, 0, sizeof(ev));
memset(nv, 0, sizeof(nv));
}
void add1(int u, int v, int k)
{
int t = lca(u, v);
if (t == u){
nv[v] += k;
}
else if (t == v){
nv[u] += k;
}
else{
nv[t] -= k;
nv[u] += k;
nv[v] += k;
}
nv[fat[t][0]] -= k;
}
void add2(int u, int v, int k)
{
int t = lca(u, v);
ev[t] -= 2 * k;
ev[u] += k;
ev[v] += k;
}
int main()
{
int t;
scanf("%d", &t);
for (int j = 1; j <= t; j++)
{
scanf("%d%d", &n, &m);
init();
for (int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dep[1] = 1;
fat[1][0] = 0;
dfs(1, -1);
dp();
char op[10];
int u, v, k;
for (int i = 1; i <= m; i++)
{
scanf("%s%d%d%d", op, &u, &v, &k);
if (op[3] == '1'){
add1(u, v, k);
}
else{
add2(u, v, k);
}
}
Dfs(1, -1);
printf("Case #%d:\n", j);
for (int i = 1; i <= n; i++)
{
if (i - 1) putchar(' ');
printf("%lld", nv[i]);
}
puts("");
for (int i = 0; i < n - 1; i++)
{
u = edge[i << 1].to, v = edge[i << 1 | 1].to;
if (dep[u] < dep[v]) swap(u, v);
if (i) putchar(' ');
printf("%lld", ev[u]);
}
puts("");
}
return 0;
}
]]>正式由于每个月饼可以保存T小时,转化为滑动窗口,不超过T(这里我认为是T+1,即第i小时生产的月饼,i+T小时仍然可以用,所以T+1个小时)的窗口的最小值。
队列的初始化很重要,wa了好久。
#include <iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int M[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int a[N],time[N],b[N];
int S, T;
int n, m;
int q[N];
int MON(char *m){
if (strcmp(m, "Jan") == 0)return 1;
if (strcmp(m, "Feb") == 0)return 2;
if (strcmp(m, "Mar") == 0)return 3;
if (strcmp(m, "Apr") == 0)return 4;
if (strcmp(m, "May") == 0)return 5;
if (strcmp(m, "Jun") == 0)return 6;
if (strcmp(m, "Jul") == 0)return 7;
if (strcmp(m, "Aug") == 0)return 8;
if (strcmp(m, "Sep") == 0)return 9;
if (strcmp(m, "Oct") == 0)return 10;
if (strcmp(m, "Nov") == 0)return 11;
return 12;
}
bool LeapYear(int &year){
return year % 4 == 0 && year % 100 || year % 400 == 0;
}
int Time(int year, int Mon, int d, int h){
int t = 0;
for (int i = 2000; i<year; ++i){
if (LeapYear(i))t += 366;
else t += 365;
}
bool flag = LeapYear(year);
for (int i = 1; i<Mon; ++i){
if (flag && i == 2)t += 29;
else t += M[i];
}
t += d - 1;
return t * 24 + h;
}
int main()
{
while (scanf("%d%d", &n, &m)!=EOF)
{
if (!n&&!m) break;
for (int i = 0; i < n; i++)
{
int year, day, t;
char mon[10];
scanf("%s%d%d%d%d", &mon, &day, &year, &t, &a[i]);
time[i] = Time(year, MON(mon), day, t);
}
scanf("%d%d", &T, &S);
int l = 1, r = 0,idx=0;
ll ans = 0;
for (int i = 0; i < m; i++)
{
scanf("%d", &b[i]);
while (l <= r && (b[i] <= b[q[r]] + (i - q[r])*S)) r--;
q[++r] = i;
while (l <= r&&i - q[l]>T) l++;
while (idx < n&&i == time[idx]){
ans += a[idx]*(b[q[l]] + (i - q[l])*S);
idx++;
}
}
printf("%I64d\n", ans);
}
return 0;
}
]]>统计所有环的个数
状压DP
从一个点出发经过若干个点后能再次回到这个点,那么这个就是环。同时我们不关注如何到达这个点,只关心到这个点有多少种方法。
按照正常状压的思路,我们枚举所有状态。
表示到达状态为且当前在点的方案数。然后我们用j点去更新其他点。就是能够到达的点,且状态未经过,则此时我们就可以从走到,并且让 加上,如果状态经过了,则此时就构成了一个环。
为了避免重复计数,我们认为编号小的为环的起点,也就是说对于这个环,我们只把当作起点,即。由于是无向图,计算的时候也会计算,即计算了两次,同时每条边的两个顶点也会被当成环,如,但不会被计算两次,最后处理一下即可。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include<iostream>
using namespace std;
const int N = 20;
typedef long long ll;
struct E{
int to, next;
}edge[1005];
int head[N], tot;
int n, m;
void addedge(int from, int to)
{
edge[++tot].to = to;
edge[tot].next = head[from];
head[from] = tot;
}
ll dp[1<<N][N];
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u - 1, v - 1);
addedge(v - 1, u - 1);
}
for (int i = 0; i <= n; i++)
dp[1 << i][i] = 1;
ll ans = 0;
for (int i = 1; i < (1<<n); i++)
{
for (int j = 0; j < n; j++)
{
if (!dp[i][j]) continue;
for (int k = head[j]; k != -1; k = edge[k].next){
int v = edge[k].to;//从j->v,由于不能重复计算,所以起点固定为小的开始,这个就是处理的方法
if ((i&(-i))>(1 << v)) continue;
if ((i >> v) & 1){
if ((i&(-i)) == (1 << v)) ans += dp[i][j];
}
else{
dp[i | (1 << v)][v] += dp[i][j];
}
}
}
}
printf("%lld\n", (ans - m) / 2);
return 0;
}
]]>每个格子都有蘑菇,且具有生长速度,没走一个格子就可以获得当前蘑菇的价值。把所有格子走一遍(每个格子只走一次),求得到的最大值
仔细发现,终点只有一下几种情况
对于终点在第一行,也就是偶数列,我们可以这样走
对于终点在第二行,也就是奇数列,我们可以这样走
这是有规律的,所以对于一个终点来说,他能得到的最大值分别是左边得到的最大值加上右边的得到的最大值。左边的最大值不难求,我们只需使用前缀和就可以的到。右边的怎么求呢? 不一样的时间经过的点不一样,也就是价值不一样。虽然说到达每个点的时间不一样,但是可以发现一但我们把当前的点当作终点时,每个点到达的时间的差是一样的,也就是偏移量一样,我们假设开始都是从最左边开始走,到达终点时的所获得价值为,我们再计算到达这个终点的当前时间,拿这个当前时间减去我们刚开始认为它到达终点的时间,这个就是时间偏移量,当前价值既是这个终点右边所得到的值。
这个蘑菇生长的速度和可以用后缀和表示,因为右边是往右跑,因此是后缀和。
注意:我们认为终点所在列的左边是左边得到的价值,这个终点所在列不包括在左边价值里,而是包括在右边价值里
#include <iostream>
#include <algorithm>
#define MAXN 300010
using namespace std;
typedef long long ll;
int n;
ll a[MAXN], b[MAXN]; // 原矩阵的两行
ll sum[MAXN]; // sum[i] 存储了第i列到第n列所有a[i],b[i]的和,也即是所说的后缀和
ll sum_s[2 * MAXN]; // 顺时针跑。sum_s[i] 当i在1~n的时候,存储的是a[1]~a[i]的和
// 当i在n+1 ~ 2*n的时候,存储的是a[1]~a[n]的和再加上b[n]~b[2*n - i - 1]的和
ll sum_n[2 * MAXN]; // 逆时针跑。sum_n[i] 当i在1~n的时候,存储的是b[1]~b[i]的和
// 当i在n+1 ~ 2*n的时候,存储的是b[1]~b[n]的和再加上a[n]~a[2*n - i - 1]的和
ll sumr[MAXN]; // 终点列右部最大值
ll suml[MAXN]; // 终点列左部最大值
void table() {
for (int i = n; i >= 1; i--)
sum[i] = sum[i + 1] + a[i] + b[i];
for (int i = 1; i <= n; i++)
{
sum_s[i] = sum_s[i - 1] + a[i] * (i - 1);
sum_n[i] = sum_n[i - 1] + b[i] * (i - 1);//初始假定都是从0时间开始走
}
/*
这里要说一下sum_n[1],这里可以是0,可以是b[1],如果设为b[1],后面的时间偏移量就减一即可
*/
for (int i = n; i >= 1; i--)
{
sum_s[2 * n - i + 1] = sum_s[2 * n - i] + b[i] * (2 * n - i);
sum_n[2 * n - i + 1] = sum_n[2 * n - i] + a[i] * (2 * n - i);
}
for (int i = 1; i <= n; i++)
{
if (i & 1)
{
sumr[i] = sum_s[2 * n - i + 1] - sum_s[i - 1] + sum[i] * (i-1);//这个(i-1)就是时间偏移量
suml[i] = suml[i - 1] + a[i-1] * (2 * i - 3) + b[i-1] * (2 * i - 4);//左边价值前缀和递推即可,注意经过每个点时间
}
else
{
sumr[i] = sum_n[2 * n - i + 1] - sum_n[i - 1] + sum[i] * (i - 1);
suml[i] = suml[i - 1] + a[i-1] * (2 * i - 4) + b[i-1] * (2 * i - 3);
}
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
table();
ll ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, sumr[i] + suml[i]);
}
cout << ans << endl;
return 0;
}
题目链接 Permutation Cycle
的值要么是,要么是。也就是至少一个数要转换次或次。如果有一个长度为的环,环上一点从当前位置走次,必定回到原位。这样想的话,这题答案就出来了。按照题意,并且这个位置的前一个位置的值一定是自己。这样的话,我们构造出若干个长度为的环和长度为的环即可。开始要判断 是否有整数解,这个直接枚举判断即可。
#include <iostream>
#include <algorithm>
const int N = 2e5 + 10;
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
int n, a, b;
int main()
{
cin >> n >> a >> b;
int cnta = 0, cntb = 0;
for (int i = 0; i*a <= n; i++)
{
int val = n - i*a;
if (val%b == 0){
cnta = i;
cntb = val / b;
break;
}
}
if (cnta*a + cntb*b != n) {
puts("-1");
return 0;
}
int lst = 1, cur = 1;
for (int i = 0; i < cnta; i++)
{
for (int j = 1; j < a; j++)
{
printf("%d ", ++cur);
}
printf("%d ", lst);
lst = ++cur;
}
for (int i = 0; i < cntb; i++)
{
for (int j = 1; j < b; j++)
{
printf("%d ", ++cur);
}
printf("%d ", lst);
lst = ++cur;
}
return 0;
}
数组有个数, 每次将数组中的添加到序列末尾,然后从数组中任意删除一个数,最后使得得到的序列字典序最大
由于相邻两项为,所以就要去除相邻的数。因此最优的是开始先把所有奇数删去。删除所有的奇数以后剩下 : 2 4 6 8 10 12 14 16.
,这就和刚开始一样了,同时变为.
于是每次把数组长度减半,然后删去奇数位上的值,.
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 6;
int n;
int main(){
cin >> n;
int mul = 1;
while (n)
{
if (n == 1){
printf("%d\n", mul);
break;
}
else if (n == 2){
printf("%d %d\n", mul, mul * 2);
break;
}
else if (n == 3){
printf("%d %d %d\n", mul, mul, mul * 3);
break;
}
for (int i = 1; i <= (n + 1) / 2; i++)
{
printf("%d ", mul);
}
n /= 2;
mul *= 2;
}
return 0;
}
题目链接 Enlarge GCD
删除尽量少的数使变大
先把每个数除于它们的,留下来的数的共同质因数一定是最多的,我们可以枚举每个数的质因数,然后取出现最多的那个质因数的个数,这个个数就代表留下来最多的个数,删除的数一定是不具有这个出现最多质因数的。
#include <bits/stdc++.h>
using namespace std;
const int N = 15000000 +5;
int prime[N], st[N], cnt,num[N];
int n, a[N];
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a%b);
}
void seive(int n)
{
for (int i = 2; i <= n; i++)
{
if (!prime[i])
{
prime[i] = i;
st[cnt++] = i;
}
for (int j = 0; j < cnt&&i*st[j] <= n; j++)
{
prime[i*st[j]] = st[j];
if (i%st[j] == 0) break;
}
}
}
int main()
{
seive(15000000);
scanf("%d", &n);
int g = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
g = gcd(a[i], g);
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
a[i] /= g;
for (int j = a[i]; j > 1;)
{
int t = prime[j];
num[t]++;
ans = max(ans, num[t]);
while (j > 1 && j%t == 0)
{
j /= t;
}
}
}
ans = ans ? n-ans : -1;
cout << ans << endl;
return 0;
}
做了cf的题,发现自己的思维还是很差,多刷题训练。
]]>最近做的3题倍增+dp题,大致思想都是预处理出,即位置跳步能到达的最远距离,和求差不多。
Codeforce 1175 EMinimal Segment Cover
表示位置往右选择个区间能到达的最远位置,根据输入处理出,,然后预处理出数组。询问的时候贪心即可。
#include<bits/stdc++.h>
using namespace std;
const int mx = 20;
const int N = 5e5 + 10;
int dp[N][mx];
int n, m,mxdis;
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
{
int l, r;
scanf("%d%d", &l, &r);
dp[l][0] = max(dp[l][0], r);
mxdis = max(mxdis, r);
}
for (int i = 1; i <= mxdis; i++)
{
dp[i][0] = max(dp[i][0], max(i,dp[i - 1][0]));
}
for (int j = 1; j < mx; j++)
{
for (int i = 0; i <= mxdis; i++)
{
dp[i][j] = dp[dp[i][j - 1]][j - 1];
}
}
while (m--)
{
int l, r;
scanf("%d%d", &l, &r);
int ans = 0;
for (int j = mx - 1; j >= 0; j--)
{
if (dp[l][j] < r){
ans += (1 << j);
l = dp[l][j];
}
}
l = dp[l][0];
if (dp[l][0] < r){
puts("-1");
continue;
}
printf("%d\n", ans + 1);
}
return 0;
}
题目链接: 牛客 区间的连续段
现利用前缀和二分得到dp[i][0],即i往右跳一步能到达的最远的位置r,我们使用upper_bound,注意这个位置r是不包含在我们一步所能到达的区间内的,即我们一步只能得到区间 . 这个r代表下一步的起点,仔细想想很巧妙。
注意要设为.因为处理的时候可能返回
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, m, k;
ll a[N];
int dp[N][25];
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i <= n; i++)
a[i] += a[i - 1];
for (int i = 0; i <= 20; i++)
dp[n+1][i] = n + 1;
for (int i = 1; i <= n; i++)
{
dp[i][0] = upper_bound(a + 1, a + n + 1, a[i - 1] + k) - a;
//cout << dp[i][0] << endl;
}
for (int j = 1; j <= 20; j++)
for (int i = 1; i <= n; i++)
dp[i][j] = dp[dp[i][j - 1]][j - 1];
while (m--)
{
int l, r;
scanf("%d%d", &l, &r);
int res = 0;
for (int j = 20; j >= 0; j--)
if (dp[l][j] <= r) res += (1 << j), l = dp[l][j];
if (dp[l][0] <= r) puts("Chtholly");
else printf("%d\n", res + 1);
}
return 0;
}
题目链接: Codeforce 932 D
树上倍增
表示节点往上走的第个父亲,注意这里的父节点不是普通的父节点,而是满足条件的父节,即父节点的权值比子节点大,如果没有点权值比当前节点大,那父节点就是0. 表示i往上走个节点得到的权值和, 要设为,因为节点就不能往上走了,设为的话节点可以继续往上走,使得答案增加。是不包含节点权值的,它只保存了往上走所经历的权值和。
每加一个点处理一次,我们就处理一次这个点往上走的最大距离以及所能达到的父节点。询问的就和前面类似了。直接查询,满足的话,往上走,往上走的点都是满足权值比当前节点大的。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 4e5 + 10;
const int up = 20;
const ll inf = 1e18;
int tot,m;
ll last,p,q;
ll w[N], s[N][25];
int fat[N][25];
void add(int p, int q)
{
w[++tot] = q;
if (w[tot] <= w[p]){
fat[tot][0] = p;
}
else{
for (int j = up; j >= 0; j--)
{
if (w[fat[p][j]] < w[tot]) p= fat[p][j];
}
fat[tot][0] = fat[p][0];
}
if (fat[tot][0] == 0){
s[tot][0] = inf;
}
else{
s[tot][0] = w[fat[tot][0]];
}
for (int j = 1; j <= 20; j++)
{
fat[tot][j] = fat[fat[tot][j - 1]][j - 1];
s[tot][j] = fat[tot][j] == 0 ? inf : s[tot][j - 1] + s[fat[tot][j - 1]][j - 1];
}
}
ll query(int v, ll sum)
{
ll ans = 0;
if (sum >= w[v])
{
sum -= w[v];
ans++;
for (int j = 20; j >= 0; j--)
{
if (s[v][j] <= sum){
ans += (1 << j);
sum -= s[v][j];
v = fat[v][j];
}
}
}
return ans;
}
int main()
{
w[0] = inf;
tot = 1;
scanf("%d", &m);
memset(s[1], 0x7f, sizeof(s[1]));
while (m--)
{
int op;
scanf("%d%lld%lld", &op, &p, &q);
p ^= last,q ^= last;
if (op==1) add(p, q);
else printf("%d\n", last = query(p, q));
}
return 0;
}
]]>题目链接:CH#56C 异象石
题意:选择一条最短路将树上标记过的点连起来,并且点的个数是动态变化的。
如果我们按照序将这些点排序,即序小的在前面,构成一个环。我们可以发现这个环中相邻两个点得距离之和就是答案的二倍。我是这样理解的,既然按照序将这些点排列起来(构成环),按照序将这几个点走完得到的路程是答案的两倍。因为每两个点之间的边必然递归一次,然后回溯一次,即每个边经过两次。
基于这样的思想,我们可以求解本题,但是如果暴力去做的话,单次操作的复杂度最高会变成 ,由于点是动态变化的,且每次只变一个点,我们可以用来维护这些点,将它们的序放入中。设为两个点之间的距离(利用很容易求得),为答案的二倍,
每次插入一个点时.找到这个点的前驱和后继.(注意是环)
这样单词操作为,删除也是同理
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<set>
#include<cmath>
using namespace std;
const int N = 8e5 + 10;
const int up = 20;
#define lowbit(x) (x&(-x))
typedef long long ll;
struct Edge{
int to, w, next;
}edge[N];
int head[N], tot, in[N], fat[N][22], dep[N], top, vs[N];
int n, m;
set<int> s;
ll dis[N];
void addedge(int from, int to, int w)
{
edge[++tot].to = to;
edge[tot].w = w;
edge[tot].next = head[from];
head[from] = tot;
}
void dfs(int u)
{
in[u] = ++top;
vs[top] = u;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to, w = edge[i].w;
if (dep[v]) continue;
dep[v] = dep[u] + 1;
dis[v] = dis[u] + w;
fat[v][0] = u;
dfs(v);
}
}
void dp()
{
for (int j = 1; j <= up; j++)
{
for (int i = 1; i <= n; i++)
{
fat[i][j] = fat[fat[i][j - 1]][j - 1];
}
}
}
int lca(int u, int v)
{
if (dep[u] < dep[v]) swap(u, v);
for (int j = up; j >= 0; j--)
{
if ((dep[u] - dep[v]) >> j & 1) u = fat[u][j];
}
if (u == v) return v;
for (int j = up; j >= 0; j--)
{
if (fat[u][j] != fat[v][j]){
u = fat[u][j];
v = fat[v][j];
}
}
return fat[u][0];
}
void init()
{
top = tot = 0;
for (int i = 0; i <= n; i++)
{
head[i] = -1;
fat[i][0] = i;
dep[i] = 0;
dis[i] = 0;
}
}
ll dist(int u, int v)
{
return dis[u] + dis[v] - 2 * dis[lca(u, v)];
}
int main()
{
scanf("%d", &n);
memset(head, -1, sizeof(head));
//init();
for (int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dep[1] = 1;
dfs(1);
dp();
scanf("%d", &m);
ll ans = 0;
while (m--)
{
char op[5];
int x;
scanf("%s", op);
if (op[0] == '?')
{
printf("%lld\n", ans / 2);
}
else if (op[0] == '+')
{
scanf("%d", &x);
if (s.empty())
{
s.insert(in[x]);
continue;
}
else
{
auto it = s.lower_bound(in[x]);
if (it == s.end()) it = s.begin();
auto itt = it == s.begin() ? prev(s.end()) : prev(it);
ans += dist(vs[*itt], x) + dist(x, vs[*it]) - dist(vs[*itt], vs[*it]);
s.insert(in[x]);
}
}
else
{
scanf("%d", &x);
if (!s.count(in[x])) continue;
auto it = s.lower_bound(in[x]);
auto pre = it == s.begin() ? prev(s.end()) : prev(it);
auto lst = next(it) == s.end() ? s.begin() : next(it);
ans = ans - dist(vs[*pre], x) - dist(vs[*lst], x) + dist(vs[*pre], vs[*lst]);
s.erase(it);
}
}
return 0;
}
]]>由于至多只有1个出现奇数次的树,那我们可以用所有数的异或来的得到这个出现奇数次数,用线段树维护到根节点的异或和,询问的时候询问节点,LCA在这个过程中被异或了两次,再异或一下LCA消除影响,注意:询问的是单个节点,即线段树的叶子节点。
还有就是原数字可能出现0,我们统一+1处理,询问的时候再减一,显然这样不影响答案。
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<bitset>
#include<cmath>
#include<map>
using namespace std;
const int N = 1e5 + 10;
const int up = 18;
#define lowbit(x) (x&(-x))
typedef long long ll;
struct Edge{
int to, next;
}edge[N << 1];
int head[N], tot, dep[N],a[N],in[N],out[N],top;
int fat[N][20];
int c[N << 2], add[N << 2];
int n, m;
void addedge(int from, int to)
{
edge[tot].to = to;
edge[tot].next = head[from];
head[from] = tot++;
}
void dfs(int u, int fa)
{
in[u] = ++top;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == fa) continue;
dep[v] = dep[u] + 1;
fat[v][0] = u;
dfs(v, u);
}
out[u] = top;
}
void pushdown(int rt)
{
if (c[rt]){
c[rt << 1] ^= c[rt];
c[rt <<1|1] ^= c[rt];
c[rt] = 0;
}
}
void change(int rt, int l, int r, int x, int y, int val)
{
if (x <= l&&r <= y){
c[rt] ^= val;
return;
}
pushdown(rt);
int mid = (l + r) >> 1;
if (x <= mid) change(rt << 1, l, mid, x, y, val);
if (y > mid) change(rt << 1 | 1, mid + 1, r, x, y, val);
}
int query(int rt, int l, int r, int pos)
{
if (l==r){
return c[rt];
}
pushdown(rt);
int mid = (l + r) >> 1;
int ans1 = 0,ans2=0;
if (pos<= mid) return query(rt << 1, l, mid, pos);
else return query(rt << 1 | 1, mid + 1, r,pos);
return ans1^ans2;
}
void dp()
{
for (int j = 1; j <= up; j++)
{
for (int i = 1; i <= n; i++)
{
fat[i][j] = fat[fat[i][j - 1]][j - 1];
}
}
}
int lca(int u, int v)
{
if (dep[u] < dep[v]) swap(u, v);
for (int j = up; j >= 0; j--)
{
if ((dep[u] - dep[v]) >> j & 1){
u = fat[u][j];
}
}
if (u == v) return u;
for (int j = up; j >= 0; j--)
{
if (fat[u][j] != fat[v][j]){
u = fat[u][j];
v = fat[v][j];
}
}
return fat[u][0];
}
void init()
{
tot = 0;
top = 0;
memset(head, -1, sizeof(head));
memset(c, 0, sizeof(c));
}
void solve()
{
dep[1] = 1;
dfs(1, -1);
dp();
}
int main()
{
int t;
scanf("%d", &t);
for (int j = 1; j <= t; j++)
{
scanf("%d%d", &n, &m);
init();
for (int i = 1; i < n; i++)
{
int u ,v ;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
solve();
for (int i = 1; i <= n; i++){
scanf("%d", &a[i]);
a[i]++;
change(1, 1, n, in[i], out[i], a[i]);
}
while (m--)
{
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
if (op == 1){
int Lca = lca(x, y);
int ans = query(1, 1, n, in[x]);
ans ^= query(1, 1, n, in[y]);
ans ^= a[Lca];
printf("%d\n", ans - 1);
}
else{
y++;
change(1, 1, n, in[x], out[x], a[x] ^ y);
a[x] = y;
}
}
}
return 0;
}
]]>> 题目链接 :POJ 2763 Housewife Wind
如果不加修改,拿这就是一个裸LCA的题,表示根节点到节点的距离,询问 的距离就是 ,增加修改怎么考虑呢?
可以发现改变 (假设的深度大于 )的权值会影响根节点到以及的整个子树的距离,即改变 ,改变子树可以转化为序上的区间修改。我们不可能暴力的对区间经行修改。我们可以用树状数组完成区间修改,树状数组维护一个差分数组表示数组的变化,这个差分数组前 项求和代表历史上的修改对第 个位置带来的影响(即数组的变化)。
最后答案就是
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<bitset>
#include<cmath>
using namespace std;
const int N = 1e6 + 100;
const int up = 20;
#define lowbit(x) (x&(-x))
typedef long long ll;
struct Edge{
int to, w, next;
}edge[N];
int head[N], tot,in[N],out[N],fat[N][22],dep[N],dis[N],top;
int n, q, s,c[N],we[N];
void addedge(int from, int to, int w)
{
edge[++tot].to = to;
edge[tot].w = w;
edge[tot].next = head[from];
head[from] = tot;
}
void dfs(int u)
{
in[u] = ++top;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to, w = edge[i].w;
if (dep[v]) continue;
dep[v] = dep[u] + 1;
dis[v] = dis[u] + w;
fat[v][0] = u;
dfs(v);
}
out[u] = ++top;
}
void dp()
{
for (int j = 1; j <= up; j++)
{
for (int i = 1; i <= n; i++)
{
fat[i][j] = fat[fat[i][j - 1]][j - 1];
}
}
}
int lca(int u, int v)
{
if (dep[u] < dep[v]) swap(u, v);
for (int j = up; j >= 0; j--)
{
if ((dep[u] - dep[v]) >> j & 1) u = fat[u][j];
}
if (u == v) return v;
for (int j = up; j >= 0; j--)
{
if (fat[u][j] != fat[v][j]){
u = fat[u][j];
v = fat[v][j];
}
}
return fat[u][0];
}
void add(int x,int val)
{
while (x <= top)
{
c[x] += val;
x += lowbit(x);
}
}
int ask(int x)
{
int res = 0;
while (x)
{
res += c[x];
x -= lowbit(x);
}
return res;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d%d", &n, &q, &s);
for (int i = 1; i < n ; i++)
{
int u, v;
scanf("%d%d%d", &u, &v, &we[i]);
addedge(u, v, we[i]);
addedge(v, u, we[i]);
}
dep[1] = 1;
fat[1][0] = 1;
dfs(1);
dp();
for (int i = 0; i < q; i++)
{
int op, x, y;
scanf("%d", &op);
if (op == 0){
scanf("%d", &x);
int Lca = lca(x, s);
printf("%d\n", dis[s] + dis[x] + ask(in[x]) + ask(in[s]) - 2 * (dis[Lca] + ask(in[Lca])));
s = x;
}
else
{
scanf("%d%d", &x, &y);
int u = edge[2*(x-1)+1].to, v = edge[x << 1].to;
if (dep[u] < dep[v]) swap(u, v);
add(in[u], y - we[x]);
add(out[u] + 1, we[x] - y);
we[x] = y;
}
}
return 0;
}
我们也可以不适用dis数组。
for (int i = 1; i < n; i++)
{
scanf("%d%d%d", &a[i], &b[i], &we[i]);
addedge(a[i], b[i], we[i]);
addedge(b[i], a[i], we[i]);
}
for (int i = 1; i < n; i++)
{
if (dep[a[i]] < dep[b[i]]) swap(a[i], b[i]);
add(in[a[i]], we[i]);//直接插入,这也可以理解为和修改操作一样,不断修改边权的过程。
add(out[a[i]] + 1, -we[i]);
}
printf("%d\n", ask(in[x]) + ask(in[s]) - 2 * (ask(in[Lca])));
]]>题目意思很明确,从u->v
]]>