这周要火力全开,各方面都做好
发现我队友非常勤奋努力,我也要拼起来
保持良好的睡眠和运动,搞起
周一 4.5 (cf杂题)
C. Travelling Salesman Problem(思维 + 观察)
这是一道2200的思维题
当时做的时候只推出来了式子可以化为 cx + max(0, ay - (ax + cx))
以及每个点经过一次
这两个性质,然后就不会了
看了题解,
关键是发现一个性质,当ay < ax的时候, ay - (ax + cx)是一定小于0的
所以我们可以按照a的大小来排序,从大的到小的就是免费的
那么我们只要以最小的花费从a1到an, 然后到an之后,所有比an小的都可以免费过
那么现在我们考虑怎么最小化这个花费
我们要到i的时候,对于所有j < i
显然aj + cj越大越好,大到免费到i,不能免费也最小化花费
所以维护这个最大值就好了
这道题思维量很大,代码却很短
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
int main()
{
int n; scanf("%d", &n);
vector<pair<ll, ll> > ve;
_for(i, 1, n)
{
ll a, c;
scanf("%lld%lld", &a, &c);
ve.push_back({a, c});
}
sort(ve.begin(), ve.end());
ll ans = ve[0].second, mx = ve[0].first + ve[0].second;
REP(i, 1, ve.size())
{
ans += max(0ll, ve[i].first - mx) + ve[i].second;
mx = max(mx, ve[i].first + ve[i].second);
}
printf("%lld\n", ans);
return 0;
}
我上午拉了一场个人训练赛,cf1800的题4道,2个小时
最后做出了3道,剩下一道20分钟,想出了一半
感觉难度有点不够,下次弄1900的
F. Maximum White Subtree(树形dp)
两次树形dp就好了,算是比较简单的树形dp题
注意写完代码后要肉眼查错,检查一些变量有没有写错。
不要省这个时间,不然后来又要调试半天
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int dp[N], ans[N], color[N], n, m;
vector<int> g[N];
void dfs(int u, int fa)
{
dp[u] = color[u] ? 1 : -1;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
dp[u] += max(dp[v], 0);
}
//printf("u: %d %d\n", u, dp[u]);
}
void sum(int u, int fa)
{
ans[u] = dp[u];
if(fa != 0)
{
if(dp[u] <= 0) ans[u] = max(ans[u], ans[fa] + dp[u]);
else ans[u] = max(ans[u], ans[fa]);
}
for(int v: g[u])
{
if(v == fa) continue;
sum(v, u);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &color[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
sum(1, 0);
// _for(i, 1, n) printf("%d ", dp[i]); puts("");
_for(i, 1, n) printf("%d ", ans[i]);
puts("");
return 0;
}
D. Colored Rectangles(线性dp)
一开始写了个贪心,用三个堆维护
然后WA,这个贪心貌似是错的
然后又想到了dp,又看到200的数据范围,果断n^3dp
然后我考虑0的情况WA了两发
循环时考虑0的情况
能转移时才转移
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 200 + 10;
typedef long long ll;
int a[N], b[N], c[N], A, B, C;
ll dp[N][N][N];
int main()
{
scanf("%d%d%d", &A, &B, &C);
_for(i, 1, A) scanf("%d", &a[i]);
_for(i, 1, B) scanf("%d", &b[i]);
_for(i, 1, C) scanf("%d", &c[i]);
sort(a + 1, a + A + 1);
sort(b + 1, b + B + 1);
sort(c + 1, c + C + 1);
_for(i, 0, A)
_for(j, 0, B)
_for(k, 0, C)
{
if(i && j) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k] + a[i] * b[j]);
if(k && j) dp[i][j][k] = max(dp[i][j][k], dp[i][j-1][k-1] + b[j] * c[k]);
if(i && k) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][k-1] + c[k] * a[i]);
}
printf("%lld\n", dp[A][B][C]);
return 0;
}
B. Identify the Operations(思维)
一直检查要删掉数据两边的元素就好
我的做法貌似比官方做法优秀,官方的题解想复杂了
我因为每次都memset超时了一次
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int a[N], b[N], s[N], v[N], n, k;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%d", &a[i]), s[a[i]] = i;
_for(i, 1, k) scanf("%d", &b[i]);
int ans = 1;
_for(i, 1, n) v[i] = 0;
_for(i, 1, k) v[s[b[i]]] = 1;
_for(i, 1, k)
{
int now = s[b[i]];
int t1 = 0, t2 = 0;
if(now - 1 >= 1 && !v[now - 1]) t1 = 1;
if(now + 1 <= n && !v[now + 1]) t2 = 1;
ans = 1ll * ans * (t1 + t2) % 998244353;
v[now] = 0;
}
printf("%d\n", ans);
}
return 0;
}
明天再把剩下一道题补掉
那是一道好题目
这周目标就是队训内容,队训优先吧
这周刷完kuangbin线段树题单。写得快的话就开启割点和桥
周二 4.6 (cf杂题)
今明两天大部分时间都要补作业,欠的作业有点多……
今天晚上就补了一道cf题。
这题还是很骚的
D. Rating Compression(逆向思维)
这题弄了好我好久
首先这题要反为来想
我一开始想的时候是区间从小到大
应该是区间从大到小考虑
区间大小为n时最小值为1即可
然后我们缩小区间为n - 1
此时必须一次是1一次是2
所以1必须是在头或者尾
如果1在头
那么a[1] = 1 min(a[2……n]) = 2
符合这两个条件,那么区间大小为n - 1就符合
那么我们继续考虑区间大小为n - 2
如果1在头
那么我们把第一个元素去掉,重复这个过程
同样2必须在首尾,如果2在尾,那么前面部分的最小值必须为3
满足这两个条件,n - 3长度的区间就ok
所以我们可以一直迭代下去
k = 1和k = n特判一下,中间就按照上面的算法
其中维护最小值我用的是multiset
当然也可以用线段树和ST表
官方题解给出了一种更简单的方法,维护每个数出现的次数就可以了
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 3e5 + 10;
int a[N], vis[N], n;
multiset<int> s;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(vis, 0, sizeof(vis));
s.clear();
scanf("%d", &n);
int flag = 1;
_for(i, 1, n)
{
scanf("%d", &a[i]);
if(vis[a[i]]) flag = 0;
vis[a[i]] = 1;
s.insert(a[i]);
}
int t = *s.begin() == 1 ? 1 : 0;
putchar(flag ? '1' : '0');
int l = 1, r = n, ans = n; //ans不初始化值的时候要小心,考虑有没有循环里面没有赋值的情况,比如极端情况
_for(i, 1, n - 2)
{
if(a[l] == i || a[r] == i)
{
auto it = s.find(i); s.erase(it);
if(*s.begin() == i + 1)
{
if(a[l] == i) l++;
else r--;
ans = n - i;
}
else break;
}
else break;
}
_for(i, 2, ans - 1) putchar('0');
_for(i, ans, n - 1) putchar('1');
if(n > 1) putchar(t ? '1' : '0'); //坑,注意n > 1.即n = 1时只有首没有尾
puts("");
}
return 0;
}
周三 4.7 (kuangbin线段树)
队里的任务是这个题单。最近一段时间的目标是刷完题单
HDU 1166 (单点修改 区间查询模板)
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e4 + 10;
int t[N << 2], n;
void up(int k)
{
t[k] = t[l(k)] + t[r(k)];
}
void build(int k, int l, int r)
{
if(l == r)
{
scanf("%d", &t[k]);
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void add(int k, int l, int r, int x, int p)
{
if(l == r)
{
t[k] += p;
return;
}
int m = (l + r) >> 1;
if(x <= m) add(l(k), l, m, x, p);
else add(r(k), m + 1, r, x, p);
up(k);
}
int sum(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
int res = 0, m = (l + r) >> 1;
if(L <= m) res += sum(l(k), l, m, L, R);
if(R > m) res += sum(r(k), m + 1, r, L, R);
return res;
}
int main()
{
int T; scanf("%d", &T);
_for(kase, 1, T)
{
printf("Case %d:\n", kase);
scanf("%d", &n);
build(1, 1, n);
char op[10];
while(scanf("%s", op) && op[0] != 'E')
{
int i, j; scanf("%d%d", &i, &j);
if(op[0] == 'A') add(1, 1, n, i, j);
else if(op[0] == 'S') add(1, 1, n, i, -j);
else printf("%d\n", sum(1, 1, n, i, j));
}
}
return 0;
}
HDU 1754(单点修改 维护区间最大值)
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int t[N << 2], a[N], n, m;
void up(int k)
{
t[k] = max(t[l(k)], t[r(k)]);
}
void build(int k, int l, int r)
{
if(l == r)
{
t[k] = a[l];
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void add(int k, int l, int r, int x, int p)
{
if(l == r)
{
t[k] += p;
return;
}
int m = (l + r) >> 1;
if(x <= m) add(l(k), l, m, x, p);
else add(r(k), m + 1, r, x, p);
up(k);
}
int query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
int res = 0, m = (l + r) >> 1;
if(L <= m) res = max(res, query(l(k), l, m, L, R));
if(R > m) res = max(res, query(r(k), m + 1, r, L, R));
return res;
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
_for(i, 1, n) scanf("%d", &a[i]);
build(1, 1, n);
while(m--)
{
char op[10]; int x, y;
scanf("%s%d%d", op, &x, &y);
if(op[0] == 'Q') printf("%d\n", query(1, 1, n, x, y));
else
{
add(1, 1, n, x, -a[x] + y);
a[x] = y;
}
}
}
return 0;
}
POJ 3468(区间修改 区间查询)
#include<cstdio>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll t[N << 2], f[N << 2];
int n, m;
void lazy(int k, int l, int r, ll p)
{
t[k] += (r - l + 1) * p;
f[k] += p;
}
void up(int k)
{
t[k] = t[l(k)] + t[r(k)];
}
void down(int k, int l, int r)
{
if(f[k])
{
int m = (l + r) >> 1;
lazy(l(k), l, m, f[k]);
lazy(r(k), m + 1, r, f[k]);
f[k] = 0;
}
}
void build(int k, int l, int r)
{
if(l == r)
{
scanf("%lld", &t[k]);
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void add(int k, int l, int r, int L, int R, ll p)
{
if(L <= l && r <= R)
{
lazy(k, l, r, p);
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) add(l(k), l, m, L, R, p);
if(R > m) add(r(k), m + 1, r, L, R, p);
up(k);
}
ll query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
down(k, l, r);
ll res = 0;
int m = (l + r) >> 1;
if(L <= m) res += query(l(k), l, m, L, R);
if(R > m) res += query(r(k), m + 1, r, L, R);
return res;
}
int main()
{
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--)
{
char op[10]; int x, y; ll z;
scanf("%s%d%d", op, &x, &y);
if(op[0] == 'Q') printf("%lld\n", query(1, 1, n, x, y));
else
{
scanf("%lld", &z);
add(1, 1, n, x, y, z);
}
}
return 0;
}
周四 4.8 (kuangbin线段树)
按照自己的节奏来,每道题充分思考,独立思考,追求高效率
POJ 2528(动态开点)
之前做过这道题,是用离散化做的
这种空间很大的题目有两种解决方法,一种是离散化,一种是动态开点
这次我用动态开点做,注意几个点
(1)动态开点的数组大小到底要开多少其实时挺迷的
q个询问,每个询问是一个区间,一个区间最多递归log区间长度 次
因此应该开qlogN
但是实际情况还要再开大一些
如果你直接开qlogN是有可能会RE的
可以再乘个2
这道题给的是64MB,我算了一下大概int能开到1e7
干脆能开多大开多大吧……
这道题我把qlogN乘了个2,AC了
(2)写的时候要引用,lazy和区间修改时会产生新节点
#include<cstdio>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2.4e5;
const int M = 1e4 + 10;
int t[N << 1], f[N << 1], n, cnt, color;
int ls[N << 1], rs[N << 1], ans[M];
void build(int& k) //建立新点。因为有多组数据,所以要初始化.注意这里要引用
{
k = ++cnt;
ls[k] = rs[k] = f[k] = t[k] = 0;
}
void up(int k) //0表示未初始化 -1表示有不同颜色
{
t[k] = t[ls[k]] == t[rs[k]] ? t[ls[k]] : -1;
}
void lazy(int& k, int p)
{
if(!k) build(k);
t[k] = f[k] = p;
}
void down(int k, int l, int r)
{
if(f[k])
{
int m = (l + r) >> 1;
lazy(ls[k], f[k]);
lazy(rs[k], f[k]);
f[k] = 0;
}
}
void change(int& k, int l, int r, int L, int R, int p)
{
if(!k) build(k);
if(L <= l && r <= R)
{
lazy(k, p);
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) change(ls[k], l, m, L, R, p);
if(R > m) change(rs[k], m + 1, r, L, R, p);
up(k);
}
void query(int k, int l, int r)
{
if(t[k] != -1)
{
ans[t[k]] = 1; //用ans数组防止一种颜色算多次
return;
}
down(k, l, r);
int m = (l + r) >> 1;
query(ls[k], l, m);
query(rs[k], m + 1, r);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
cnt = color = 0;
memset(ans, 0, sizeof(ans));
int root = 0; //弄一个root,因为要引用
scanf("%d", &n);
_for(i, 1, n)
{
int l, r;
scanf("%d%d", &l, &r);
change(root, 1, 1e7, l, r, ++color); //最大区间右端点1e7
}
query(root, 1, 1e7);
int sum = 0;
_for(i, 1, 1e4)
sum += ans[i];
printf("%d\n", sum);
}
return 0;
}
hdu 1698(区间修改 区间查询)
#include<cstdio>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll t[N << 2], f[N << 2];
int n, m;
void lazy(int k, int l, int r, ll p)
{
t[k] = (r - l + 1) * p;
f[k] = p;
}
void up(int k)
{
t[k] = t[l(k)] + t[r(k)];
}
void down(int k, int l, int r)
{
if(f[k])
{
int m = (l + r) >> 1;
lazy(l(k), l, m, f[k]);
lazy(r(k), m + 1, r, f[k]);
f[k] = 0;
}
}
void build(int k, int l, int r)
{
f[k] = 0;
if(l == r)
{
t[k] = 1;
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void change(int k, int l, int r, int L, int R, ll p)
{
if(L <= l && r <= R)
{
lazy(k, l, r, p);
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) change(l(k), l, m, L, R, p);
if(R > m) change(r(k), m + 1, r, L, R, p);
up(k);
}
ll query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
down(k, l, r);
ll res = 0;
int m = (l + r) >> 1;
if(L <= m) res += query(l(k), l, m, L, R);
if(R > m) res += query(r(k), m + 1, r, L, R);
return res;
}
int main()
{
int T; scanf("%d", &T);
_for(kase, 1, T)
{
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--)
{
int x, y; ll z;
scanf("%d%d%lld", &x, &y, &z);
change(1, 1, n, x, y, z);
}
printf("Case %d: The total value of the hook is %lld.\n", kase, t[1]);
}
return 0;
}
ZOJ 1610(区间染色问题)
这道题有几个坑
一个是下标要+1从1开始,从0开始左右节点都是0
其次是3和4中间是有颜色的,所以我就把下标乘以2加1,使得3和4变成7和9,多了个8
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1.6e4 + 10;
int t[N << 2], f[N << 2], ans[N + 10], n;
int last_r, last_color;
void up(int k)
{
t[k] = t[l(k)] == t[r(k)] ? t[l(k)] : -1;
}
void lazy(int k, int p)
{
t[k] = f[k] = p;
}
void down(int k, int l, int r)
{
if(f[k])
{
int m = (l + r) >> 1;
lazy(l(k), f[k]);
lazy(r(k), f[k]);
f[k] = 0;
}
}
void change(int k, int l, int r, int L, int R, int p)
{
if(L <= l && r <= R)
{
lazy(k, p);
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) change(l(k), l, m, L, R, p);
if(R > m) change(r(k), m + 1, r, L, R, p);
up(k);
}
void query(int k, int l, int r)
{
if(t[k] != -1)
{
if(!(t[k] == last_color && l == last_r + 1)) ans[t[k]]++;
last_r = r; last_color = t[k];
return;
}
down(k, l, r);
int m = (l + r) >> 1;
query(l(k), l, m);
query(r(k), m + 1, r);
}
int main()
{
while(~scanf("%d", &n))
{
memset(ans, 0, sizeof(ans));
memset(f, 0, sizeof(f));
memset(t, 0, sizeof(t));
last_r = last_color = 0;
_for(i, 1, n)
{
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
l++; r++; c++;
l = 2 * l + 1; r = 2 * r + 1;
change(1, 1, N, l, r, c);
}
query(1, 1, N);
_for(i, 1, N)
if(ans[i])
printf("%d %d\n", i - 1, ans[i]);
puts("");
}
return 0;
}
POJ 3264(维护区间最大值最小值)
刷水题好惭愧啊………………
#include<cstdio>
#include<algorithm>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e4 + 10;
int t1[N << 2], t2[N << 2], n, q;
void up1(int k)
{
t1[k] = max(t1[l(k)], t1[r(k)]);
}
void up2(int k)
{
t2[k] = min(t2[l(k)], t2[r(k)]);
}
void build(int k, int l, int r)
{
if(l == r)
{
scanf("%d", &t1[k]);
t2[k] = t1[k];
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up1(k); up2(k);
}
int qmax(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t1[k];
int res = 0, m = (l + r) >> 1;
if(L <= m) res = max(res, qmax(l(k), l, m, L, R));
if(R > m) res = max(res, qmax(r(k), m + 1, r, L, R));
return res;
}
int qmin(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t2[k];
int res = 1e9, m = (l + r) >> 1;
if(L <= m) res = min(res, qmin(l(k), l, m, L, R));
if(R > m) res = min(res, qmin(r(k), m + 1, r, L, R));
return res;
}
int main()
{
scanf("%d%d", &n, &q);
build(1, 1, n);
while(q--)
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", qmax(1, 1, n, l, r) - qmin(1, 1, n, l, r));
}
return 0;
}
hdu 3974(dfs序 + 区间染色 + 下标处理)
用dfs序转化一下区间
注意转化之后,任何操作都要用L[x],也就是其dfs序,我在这错了好多次了
然后是下标问题
题目给的颜色从0开始,而我默认没有颜色是0,冲突了,所以就把颜色的+1,输出-1
正好和没有颜色输出-1对应
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e4 + 10;
int t[N << 2], f[N << 2], in[N], L[N], R[N], cnt, n, m;
vector<int> g[N];
void up(int k) //0表示未赋值 -1表示儿子任务不一样
{
t[k] = t[l(k)] == t[r(k)] ? t[l(k)] : -1;
}
void lazy(int k, int p)
{
t[k] = f[k] = p;
}
void down(int k, int l, int r)
{
if(f[k])
{
int m = (l + r) >> 1;
lazy(l(k), f[k]);
lazy(r(k), f[k]);
f[k] = 0;
}
}
void change(int k, int l, int r, int L, int R, int p)
{
if(L <= l && r <= R)
{
lazy(k, p);
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) change(l(k), l, m, L, R, p);
if(R > m) change(r(k), m + 1, r, L, R, p);
up(k);
}
int query(int k, int l, int r, int p)
{
if(t[k] != -1) return t[k];
down(k, l, r);
int m = (l + r) >> 1;
if(p <= m) return query(l(k), l, m, p);
else return query(r(k), m + 1, r, p);
}
void dfs(int u)
{
L[u] = ++cnt;
for(int v: g[u])
dfs(v);
R[u] = cnt;
}
int main()
{
int T; scanf("%d", &T);
_for(kase, 1, T)
{
memset(in, 0, sizeof(in));
memset(f, 0, sizeof(f));
memset(t, 0, sizeof(t));
cnt = 0;
scanf("%d", &n);
_for(i, 1, n) g[i].clear();
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[v].push_back(u);
in[u]++;
}
_for(i, 1, n)
if(!in[i])
{
dfs(i);
break;
}
scanf("%d", &m);
printf("Case #%d:\n", kase);
while(m--)
{
char op[10]; int x, y;
scanf("%s%d", op, &x);
if(op[0] == 'C') printf("%d\n", query(1, 1, n, L[x]) - 1);
else
{
scanf("%d", &y); y++;
change(1, 1, n, L[x], R[x], y);
}
}
}
return 0;
}
接下来写
以后写kuangbin时可以看大概题意,太模板的题目直接跳过就好
以后队训的任务中太模板,自己确定已经掌握的题目就别浪费时间做了
没事,慢慢适应队训节奏
接下来目标
kuangbin 9 12 13
扫描线14 15 16
还有多的时间可以刷洛谷线段树题单
下周末自己拉一场比赛
周六 4.9(kuangbin线段树)
昨天有训练,只是做题没想出来,所以没写博客
hdu 1540(线段树维护最长联通区间 + 栈)
这是一道好题。多做好题,别做水题
我独立想没想出来,但是碰到了正解的边了
考虑一个联通区间,发现它一定是一个区间的右联通 + 一个区间的左联通构成
所以我们就维护一个区间的左右最大联通长度即可
没落在联通区间,就继续往下递归就行了
这道题还要维护最后删除的数,用一个栈维护就好
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e4 + 10;
int lmax[N << 2], rmax[N << 2], n, m;
int st[N], top;
void up(int k, int l, int r)
{
int m = (l + r) >> 1;
int llen = m - l + 1, rlen = r - m;
if(lmax[l(k)] == llen) lmax[k] = llen + lmax[r(k)];
else lmax[k] = lmax[l(k)];
if(rmax[r(k)] == rlen) rmax[k] = rlen + rmax[l(k)];
else rmax[k] = rmax[r(k)];
}
void build(int k, int l, int r)
{
if(l == r)
{
lmax[k] = rmax[k] = 1;
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k, l, r);
}
void change(int k, int l, int r, int x, int p)
{
if(l == r)
{
lmax[k] = rmax[k] = p;
return;
}
int m = (l + r) >> 1;
if(x <= m) change(l(k), l, m, x, p);
else change(r(k), m + 1, r, x, p);
up(k, l, r);
}
int query(int k, int l, int r, int x)
{
if(l == r) return lmax[k];
int m = (l + r) >> 1;
if(x <= m)
{
int pos = m - rmax[l(k)] + 1;
if(x >= pos) return rmax[l(k)] + lmax[r(k)];
else return query(l(k), l, m, x);
}
else
{
int pos = m + 1 + lmax[r(k)] - 1;
if(x <= pos) return rmax[l(k)] + lmax[r(k)];
else return query(r(k), m + 1, r, x);
}
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
top = 0;
build(1, 1, n);
while(m--)
{
char op[5]; int x;
scanf("%s", op);
if(op[0] == 'D')
{
scanf("%d", &x);
change(1, 1, n, x, 0);
st[++top] = x;
}
else if(op[0] == 'R') change(1, 1, n, st[top--], 1);
else
{
scanf("%d", &x);
printf("%d\n", query(1, 1, n, x));
}
}
}
return 0;
}
hdu 4614(线段树 + 二分答案)
其实这题细节挺多的,但是我静下心来写,一点一点调试,结果一次AC了,爽
用线段树维护区间有多少个放了花的花瓶
这道题难点在于需要求一个区间内第一个放花的花瓶和最后一个放花的花瓶
这个二分答案就好,枚举最后一个放花的花瓶的位置
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e4 + 10;
int t[N << 2], f[N << 2], n, m;
void lazy(int k, int l, int r, int p)
{
t[k] = p * (r - l + 1);
f[k] = p;
}
void up(int k)
{
t[k] = t[l(k)] + t[r(k)];
}
void down(int k, int l, int r)
{
if(f[k] != -1)
{
int m = (l + r) >> 1;
lazy(l(k), l, m, f[k]);
lazy(r(k), m + 1, r, f[k]);
f[k] = -1;
}
}
void build(int k, int l, int r)
{
f[k] = -1;
if(l == r)
{
t[k] = 0;
return;
}
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void change(int k, int l, int r, int L, int R, int p)
{
if(L <= l && r <= R)
{
lazy(k, l, r, p);
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) change(l(k), l, m, L, R, p);
if(R > m) change(r(k), m + 1, r, L, R, p);
up(k);
}
int query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
down(k, l, r);
int m = (l + r) >> 1, res = 0;
if(L <= m) res += query(l(k), l, m, L, R);
if(R > m) res += query(r(k), m + 1, r, L, R);
return res;
}
int cal(int x, int y) //从x开始需要放y朵花
{
int l = x - 1, r = n + 1;
while(l + 1 < r)
{
int m = (l + r) >> 1;
if((m - x + 1) - query(1, 1, n, x, m) >= y) r = m;
else l = m;
}
return r;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--)
{
int k, x, y;
scanf("%d%d%d", &k, &x, &y);
if(k == 2)
{
x++; y++;
printf("%d\n", query(1, 1, n, x, y));
change(1, 1, n, x, y, 0);
}
else
{
x++;
int t = (n - x + 1) - query(1, 1, n, x, n); //空瓶数量
if(!t) puts("Can not put any one.");
else
{
int ans1 = cal(x, 1), ans2 = cal(x, min(y, t));
printf("%d %d\n", ans1 - 1, ans2 - 1);
change(1, 1, n, ans1, ans2, 1);
}
}
}
puts("");
}
return 0;
}
下午做了约会安排,码量巨大,不做了……
然后学了扫描线,还没A掉模板题。明天早上做几道扫描线的题目,线段树就告一段落了
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e2 + 10;
int cnt[N << 3], n, m; //维护区间的次数
double sum[N << 3]; //乘以8 因为一个矩形两个点
vector<double> v;
struct node
{
double x1, x2, y, c;
bool operator < (const node& rhs) const
{
return y < rhs.y;
}
};
vector<node> line;
void up(int k)
{
cnt[k] = cnt[l(k)] == cnt[r(k)] ? cnt[l(k)] : -1;
sum[k] = sum[l(k)] + sum[r(k)];
}
void down(int k, int l, int r)
{
if(cnt[k] != -1)
{
cnt[l(k)] = cnt[r(k)] = cnt[k];
if(!cnt[k]) sum[l(k)] = sum[r(k)] = 0;
else
{
int m = (l + r) >> 1;
sum[l(k)] = v[m + 1] - v[l];
sum[r(k)] = v[r + 1] - v[m];
}
}
}
void build(int k, int l, int r)
{
sum[k] = cnt[k] = 0;
if(l == r) return;
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
}
void modify(int k, int l, int r, int L, int R, int p)
{
if(L <= l && r <= R)
{
cnt[k] += p;
sum[k] = !cnt[k] ? 0 : v[r + 1] - v[l];
return;
}
down(k, l, r);
int m = (l + r) >> 1;
if(L <= m) modify(l(k), l, m, L, R, p);
if(R > m) modify(r(k), m + 1, r, L, R, p);
up(k);
}
int main()
{
int kase = 0;
while(scanf("%d", &m) && m) //m为矩形个数
{
printf("Test case #%d\n", ++kase);
v.clear(); line.clear(); v.push_back(-1e9); //使得下标从1开始。使得第i个区间对应v[i]和v[i+1]
_for(i, 1, m)
{
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
v.push_back(x1); v.push_back(x2);
line.push_back({x1, x2, y2, 1});
line.push_back({x1, x2, y1, -1});
}
sort(v.begin(), v.begin()); v.erase(unique(v.begin(), v.end()), v.end()); //以后写离散化用vector写很方便
sort(line.begin(), line.end());
n = v.size() - 2; //n为区间个数.第i个区间代表v[i]到v[i + 1]
build(1, 1, n); //初始化
double ans = 0;
REP(i, 0, line.size() - 1)
{
int l = lower_bound(v.begin(), v.end(), line[i].x1) - v.begin(); //坐标离散化
int r = lower_bound(v.begin(), v.end(), line[i].x2) - v.begin();
modify(1, 1, n, l, r - 1, line[i].c);
ans += sum[1] * (line[i + 1].y - line[i].y); //y相同不怕
}
printf("Total explored area: %.2f\n\n", ans);
}
return 0;
}
周日 4.11(扫描线)
hdu 1542(扫描线求矩形面积)
还是挺秀的。其实一开始觉得挺难,后面理解了原理觉得也不就这样嘛
就是把矩形的边延长,切割成很多小矩形
然后弄一条扫描线,每次维护哪些矩形是要算面积的
可以发现这和区间覆盖的次数有关,所以就可以用线段树来维护这个区间覆盖次数
为了让线段树有下标,所以我们需要离散化,下标从1开始
注意这时线段树维护的方式和以前的不太一样
因为区间+1 和区间 -1 是成对出现的
所以我们可以维护区间的覆盖次数,直接在那个区间修改次数cnt,不需要和传统线段树一样把cnt上传和下传
但是sum的计算需要上传.
如果cnt > 0 sum = 区间长度
否则sum = 左右节点的和
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e2 + 10;
int cnt[N << 3], n, m; //维护区间的次数
double sum[N << 3]; //乘以8 因为一个矩形两个点
vector<double> v;
struct node
{
double x1, x2, y, c;
bool operator < (const node& rhs) const
{
return y < rhs.y;
}
};
vector<node> line;
void up(int k, int l, int r)
{
if(cnt[k]) sum[k] = v[r + 1] - v[l];
else if(l == r) sum[k] = 0; //注意这里叶子节点没有左右儿子
else sum[k] = sum[l(k)] + sum[r(k)];
}
void build(int k, int l, int r) //扫描线的up和down和传统线段树不一样。不需要lazytag
{
sum[k] = cnt[k] = 0;
if(l == r) return;
int m = (l + r) >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
}
void modify(int k, int l, int r, int L, int R, int p)
{
if(L <= l && r <= R)
{
cnt[k] += p;
up(k, l, r); //更新sum
return;
}
int m = (l + r) >> 1;
if(L <= m) modify(l(k), l, m, L, R, p);
if(R > m) modify(r(k), m + 1, r, L, R, p);
up(k, l, r); //更新sum
}
int main()
{
int kase = 0;
while(scanf("%d", &m) && m) //m为矩形个数
{
printf("Test case #%d\n", ++kase);
v.clear(); line.clear(); v.push_back(-1e9); //使得下标从1开始。使得第i个区间对应v[i]和v[i+1]
_for(i, 1, m)
{
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
v.push_back(x1); v.push_back(x2);
line.push_back({x1, x2, y1, 1});
line.push_back({x1, x2, y2, -1});
}
sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end()); //以后写离散化用vector写很方便
sort(line.begin(), line.end());
n = v.size() - 2; //n为区间个数.第i个区间代表v[i]到v[i + 1]
build(1, 1, n); //初始化
double ans = 0;
REP(i, 0, line.size() - 1)
{
int l = lower_bound(v.begin(), v.end(), line[i].x1) - v.begin(); //坐标离散化
int r = lower_bound(v.begin(), v.end(), line[i].x2) - v.begin();
modify(1, 1, n, l, r - 1, line[i].c);
ans += sum[1] * (line[i + 1].y - line[i].y); //y相同不怕
}
printf("Total explored area: %.2f\n\n", ans);
}
return 0;
}
hdu 1255(扫描线求重叠矩形面积)
我还是不习惯扫描线这种传递标记的方式,所以这道题没想出来
扫描线标记传递方式比较奇怪
cnt既不上传也不下传 sum只上传不下传
由于区间修改是对称的,所以这么搞是对的
对于这道题,多维护一个sum2
表示被覆盖2次以上的区间长度
cnt >= 2就等于区间长度
cnt = 1就为左右儿子sum的和,左右儿子被覆盖,自己被覆盖,就覆盖了两次
cnt = 0就为左右儿子sum2的和
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e3 + 10;
int cnt[N << 3], n, m;
double sum[N << 3], sum2[N << 3];
vector<double> v;
struct node
{
double x1, x2, y, c;
bool operator < (const node& rhs) const
{
return y < rhs.y;
}
};
vector<node> line;
void up(int k, int l, int r)
{
if(l == r) //特判叶子。这样思路清晰一些,宁愿写多几行
{
sum[k] = cnt[k] ? v[r + 1] - v[l] : 0;
sum2[k] = cnt[k] > 1 ? v[r + 1] - v[l] : 0;
return;
}
if(cnt[k]) sum[k] = v[r + 1] - v[l];
else sum[k] = sum[l(k)] + sum[r(k)];
if(cnt[k] > 1) sum2[k] = v[r + 1] - v[l];
else if(cnt[k] == 1) sum2[k] = sum[l(k)] + sum[r(k)];
else sum2[k] = sum2[l(k)] + sum2[r(k)];
}
void build(int k, int l, int r)
{
sum[k] = sum2[k] = cnt[k] = 0;
if(l == r) return;
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
}
void modify(int k, int l, int r, int L, int R, int p)
{
if(L <= l && r <= R)
{
cnt[k] += p;
up(k, l, r);
return;
}
int m = l + r >> 1;
if(L <= m) modify(l(k), l, m, L, R, p);
if(R > m) modify(r(k), m + 1, r, L, R, p);
up(k, l, r);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
v.clear(); v.push_back(-1e9);
line.clear();
scanf("%d", &m);
_for(i, 1, m)
{
double x1, x2, y1, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
v.push_back(x1); v.push_back(x2);
line.push_back({x1, x2, y1, 1});
line.push_back({x1, x2, y2, -1});
}
sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());
sort(line.begin(), line.end());
n = v.size() - 2;
build(1, 1, n);
double ans = 0;
REP(i, 0, line.size() - 1)
{
int l = lower_bound(v.begin(), v.end(), line[i].x1) - v.begin();
int r = lower_bound(v.begin(), v.end(), line[i].x2) - v.begin();
modify(1, 1, n, l, r - 1, line[i].c);
ans += sum2[1] * (line[i + 1].y - line[i].y);
}
printf("%.2f\n", ans);
}
return 0;
}
P3388 【模板】割点(割顶)
开启割点和桥
先敲模板
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e4 + 10;
int dfn[N], low[N], ans[N], cnt, n, m, root;
vector<int> g[N];
void dfs(int u)
{
dfn[u] = low[u] = ++cnt;
int son = 0;
for(auto v: g[u])
{
if(!dfn[v])
{
dfs(v);
low[u] = min(low[u], low[v]);
if(u == root && ++son > 1) ans[u] = 1;
if(u != root && low[v] >= dfn[u]) ans[u] = 1;
}
else low[u] = min(low[u], dfn[v]);
}
}
int main()
{
scanf("%d%d", &n, &m);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
_for(i, 1, n)
if(!dfn[i])
dfs(root = i);
int sum = 0;
_for(i, 1, n)
sum += ans[i];
printf("%d\n", sum);
_for(i, 1, n)
if(ans[i])
printf("%d ", i);
return 0;
}
P1656 炸铁路(求割边模板)
在割点的基础上修改一些
割点跟新low的时候,由于无向图有返回到父亲的边,所以low的值其实是至少会更新到其父亲的,其实和low的定义不符合,但是这不影响求割点
但是这影响求割边,求割边的时候这条边不能用来更新,所以要判一下
但是还有重边的问题,如果有重边,就一定不是割边
所以我们可以在后来再次遇到返回父亲的边时,这时就可以更新,使得low更新为父亲的值,这个时候一定不是割边
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 200;
int dfn[N], low[N], cnt, n, m;
vector<pair<int, int> > ans;
vector<int> g[N];
void dfs(int u, int fa)
{
dfn[u] = low[u] = ++cnt;
bool vis = false; //处理重边,如果有重边,就一定不是割边
for(auto v: g[u])
{
if(!dfn[v])
{
dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) ans.push_back(make_pair(min(u, v), max(u, v))); //判断割边
}
else
{
if(v == fa && !vis) vis = true; //只有第一次遇到父亲才不更新
else low[u] = min(low[u], dfn[v]); //后来遇到父亲就会更新,使得一定不为割边
}
}
}
int main()
{
scanf("%d%d", &n, &m);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
_for(i, 1, n)
if(!dfn[i])
dfs(i, i);
sort(ans.begin(), ans.end());
for(auto x: ans) printf("%d %d\n", x.first, x.second);
return 0;
}
求双联通分量
点双联通分量,可以理解为不存在割点
我们在求割点的时候可以顺便求出点双联通分量
我们dfs的过程中一直把点加入栈
如果找到一个割点u,那就弹出栈的元素,一直弹到u(有点类似求强联通分量)
那么这些点就构成一个点双联通分量
因为这个点构成的子图是没有割点的,首先u的父亲不属于这个分量,所以u不是割点
然后u之前在栈中元素也不是割点。如果是割点那就已经弹出栈了。
边双联通分量
把所有割边删掉,剩下构成的就是边双联通分量的
这里面一定没有割边,因为割边删完了, 太暴力了
然后dfs或者并查集就可以求出各个边双联通分量
一个有桥的联通图,最少加多少条边使得其成为边双联通分量?
首先一个边双联通分量内连边并没什么卵用,所以我们可以缩点
缩点后连边,此时一定是一颗树
因为是联通的,而且不存在环。如果有环就为一个边双联通分量了,而我们已经缩点了
缩点后就转化为,一颗树,最少添加多少条边使其成为边双联通分量,也就是没有割边
我们考虑叶子的个数
当为一条链,2个叶子,只需要一条边
3个叶子,两条边,这时边界情况
当为n个叶子的时候
可以发现连一条边可以删掉一个叶子或者两个叶子
我画了几条边,发现删掉一个叶子的情况时少数,很多时候是删掉2个叶子
所以我们就连边,每次删掉两个叶子
所以就可以一直连边,直到达到边界条件
所以答案是叶子的个数 (cnt + 1) / 2
「一本通 3.6 例 1」分离的路径(边双联通分量缩点 + 思维)
这道题的思路就是上面讲的思路
注意缩点之后,新图的每一条边都是原来的割边
看到一本通上的写法和我有一些不同
明天再学习学习
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e3 + 10;
const int M = 1e4 + 10;
vector<pair<int, int> > g[N];
int dfn[N], low[N], del[M], cnt, n, m;
int node[N], in[N], deg[N], num;
void dfs(int u, int fa)
{
dfn[u] = low[u] = ++cnt;
bool vis = false;
for(auto x: g[u])
{
int v = x.first, id = x.second;
if(!dfn[v])
{
dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) del[id] = 1;
}
else
{
if(v == fa && !vis) vis = true;
else low[u] = min(low[u], dfn[v]);
}
}
}
void mark(int u)
{
node[u] = num;
for(auto x: g[u])
{
int v = x.first, id = x.second;
if(node[v] || del[id]) continue; //访问过的点不用再访问了。Tarjan访问过的点还要再访问
mark(v);
}
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, m)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(make_pair(v, i));
g[v].push_back(make_pair(u, i));
}
dfs(1, 0);
_for(i, 1, n)
if(!node[i])
{
num++;
mark(i);
}
_for(u, 1, n)
for(auto x: g[u])
{
int v = x.first, id = x.second;
if(del[id] && node[u] > node[v]) //缩点后,连接的边就是割边。同时无向边只算一次
deg[node[u]]++, deg[node[v]]++;
}
int sum = 0;
_for(i, 1, num)
sum += deg[i] == 1;
printf("%d\n", (sum + 1) / 2);
return 0;
}