Educational Codeforces Round 92 (Rated for Div. 2)
F. Bicolored Segments
题意:n条线段,每条线段起点为
l
i
l_{i}
li,终点为
r
i
r_{i}
ri,颜色为
t
i
t_{i}
ti,
t
i
t_{i}
ti为0或1。颜色不同的线段不能相交。求最多可以选择多少线段。
思路:线段树优化DP
枚举末尾位置i,颜色为opt,假设上一个与i的颜色不同的位置为j。设f[opt][i]为末尾节点为i,颜色为opt时可以选择的最多线段数。f[opt][i] =
m
a
x
0
<
j
<
i
max_{0<j<i}
max0<j<if[opt^1][j]+j到i之间颜色为opt的线段数。可以用线段树维护这一操作。
- 将线段端点离散处理
- 将所有线段按照右端点(从小到大)为第一关键字,左端点(从大到小)为第二关键字排序。
- 枚举右端点,在线段树上更新相应线段。如果当前颜色存在线段[ l i l_{i} li, r i r_{}i ri],那么在线段树[1, l i − 1 l_{i}-1 li−1]区间上+1。统计答案,并用f[opt][i]更新线段树上i位置的值。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2e5 + 10;
struct LINE{
int l, r, t;
LINE(){}
LINE(int x, int y, int z){l = x, r = y, t = z;}
bool operator < (const LINE & other)
const{return r < other.r || (r == other.r && t < other.t) || (r == other.r && t == other.t && l > other.l);}
}line[N];
int tr[2][N * 8], tag[2][N * 8], h[N * 2], f[2][N * 2];
void pushdown(int opt, int x)
{
if(tag[opt][x])
{
tr[opt][x * 2] += tag[opt][x];
tr[opt][x * 2 + 1] += tag[opt][x];
tag[opt][x * 2] += tag[opt][x];
tag[opt][x * 2 + 1] += tag[opt][x];
tag[opt][x] = 0;
}
}
void update(int opt, int x, int l, int r, int ll, int rr, int v)
{
if(r < ll || l > rr) return;
if(ll <= l && r <= rr)
{
tr[opt][x] += v;
tag[opt][x] +=v;
return;
}
pushdown(opt, x);
int mid = (l + r) >> 1;
update(opt, x * 2, l, mid, ll, rr, v);
update(opt, x * 2 + 1, mid + 1, r, ll, rr, v);
tr[opt][x] = max(tr[opt][x * 2], tr[opt][x * 2 + 1]);
}
int main()
{
int n;
scanf("%d", &n);
int cnt = 0;
for(int i = 1; i <= n; i++)
{
int l, r, t;
scanf("%d%d%d", &l, &r, &t);
h[++cnt] = l;
h[++cnt] = r;
line[i] = LINE(l, r, t - 1);
}
h[++cnt] = 0;
sort(line + 1, line + n + 1);
sort(h + 1, h + cnt + 1);
int m = unique(h + 1, h + cnt+ 1) - h - 1;
for(int i = 1; i <= n; i++)
{
line[i].l = lower_bound(h + 1, h + m + 1, line[i].l) - h;
line[i].r = lower_bound(h + 1, h + m + 1, line[i].r) - h;
}
int ans = 0;
int j = 1;
for(int i = 1; i <= m; i++)
{
for(int opt = 0; opt <= 1; opt++)
{
while(j <= n && line[j].r == i && line[j].t == opt)
{
update(opt, 1, 1, m, 1, line[j].l - 1, 1);
j++;
}
f[opt][i] = tr[opt][1];
ans = max(ans, f[opt][i]);
}
update(1, 1, 1, m, i, i, f[0][i]);
update(0, 1, 1, m, i, i, f[1][i]);
}
cout<<ans<<endl;
return 0;
}
G Directing Edges
题意:给一张无向图,现在可以给一些边定向,如果一条边没有被定向,那么需要支付
c
i
c_{i}
ci的代价。图中有k个关键点,对于一个点x,如果所有关键点都可以到达x,那么我们称x为可达点,可以获得
w
x
w_{x}
wx的利润。n组询问,第i次询问在保证节点i是可达点的情况下,整张图的最大利润。
思路:
- tarjan缩点,双连通分量里的点一定可以互达,这样整张图变成一棵树
- 如果一个节点的所有子节点中没有关键点,那么只要把这棵子树所有边定成向下的就好。因此,这棵子树也可以被“缩”起来。
- 此时,树上所有叶子节点都是关键点。选一个关键点作为根,然后进行树上DP。f[x]表示x为可达点时的最大利润,f[x] += max(f[y] - w[x][y], 0)(y是其子节点)。如果y也是可达点,那么edge[x][y]必然是双向边,贡献的利润为f[y]-w[x][y];否则贡献的利润为0。最后再进行换根就可以了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 3e5 + 10;
const int M = 3e5 + 10;
struct EDGE{
int to, nxt;
LL w;
EDGE(int a = 0, int b = 0, int c = 0)
{nxt = a; to = b; w = c;}
}edge[2][M * 4];
int t[2][N], vist[N], dfn[N], low[N], key[N], col[N], in[N], st[N];
LL v[N], f[N], w[M];
int tot[2] = {1, 1}, cnt = 0, stop = 0;
void addedge(int opt, int x, int y, int w)
{
edge[opt][++tot[opt]] = EDGE(t[opt][x], y, w);
t[opt][x] = tot[opt];
}
void tarjan(int x, int from)
{
dfn[x] = ++cnt;
low[x] = cnt;
in[x] = 1;
st[++stop] = x;
for(int p = t[0][x]; p; p = edge[0][p].nxt)
{
int y = edge[0][p].to;
if(!dfn[y])
{
tarjan(y, p);
low[x] = min(low[x], low[y]);
}
else
if(p != (from ^ 1))
low[x] = min(low[x], dfn[y]);
}
if(dfn[x] == low[x])
{
int y;
do{
y = st[stop--];
col[y] = x;
in[y] = 0;
}while(y != x);
}
}
void build(int n)
{
for(int i = 1; i <= n; i++)
{
key[col[i]] |= key[i];
if(col[i] != i)
v[col[i]] += v[i];
for(int p = t[0][i]; p; p = edge[0][p].nxt)
{
int y = edge[0][p].to;
if(col[y] != col[i])
{
addedge(1, col[i], col[y], edge[0][p].w);
addedge(1, col[y], col[i], edge[0][p].w);
}
}
}
}
void dfs2(int x, int fa)
{
vist[x] = 1;
for(int p = t[1][x]; p; p = edge[1][p].nxt)
{
int y = edge[1][p].to;
if(!vist[y])
{
dfs2(y, x);
key[x] |= key[y];
}
}
}
void dfs3(int x, int from)
{
vist[x] = 1;
if(x != from)
v[from] += v[x];
col[x] = from;
for(int p = t[1][x]; p; p = edge[1][p].nxt)
{
int y = edge[1][p].to;
if(!vist[y])
{
if(!key[y]) dfs3(y, from);
else dfs3(y, y);
}
}
}
void dfs4(int x, int fa)
{
vist[x] = 1;
f[x] = v[x];
for(int p = t[1][x]; p; p = edge[1][p].nxt)
{
int y = edge[1][p].to;
if(key[y] && !vist[y])
{
dfs4(y, x);
f[x] += max((LL)0, f[y] - edge[1][p].w);
}
}
}
void dfs5(int x, int fa)
{
vist[x] = 1;
for(int p = t[1][x]; p; p = edge[1][p].nxt)
{
int y = edge[1][p].to;
if(key[y] && !vist[y])
{
f[y] += max((LL)0, f[x] - max((LL)0, f[y] - edge[1][p].w) - edge[1][p].w);
dfs5(y, x);
}
}
}
int findf(int x)
{
if(x == col[x]) return col[x];
col[x] = findf(col[x]);
return col[x];
}
int main()
{
int n, m, k, x, y;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= k; i++)
{
scanf("%d", &x);
key[x] = 1;
}
for(int i = 1;i <= n; i++) scanf("%lld", &v[i]);
for(int i = 1; i <= m; i++) scanf("%lld", &w[i]);
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
addedge(0, x, y, w[i]);
addedge(0, y, x, w[i]);
}
int rt = 0;
for(int i = 1; i <= n; i++)
if(key[i])
{
rt = i;
break;
}
tarjan(rt, 0);
build(n);
memset(vist, 0, sizeof(vist));
dfs2(rt, rt);
memset(vist, 0, sizeof(vist));
dfs3(rt, rt);
memset(vist, 0, sizeof(vist));
dfs4(rt, rt);
memset(vist, 0, sizeof(vist));
dfs5(rt, rt);
for(int i = 1; i <= n; i++)
{
x = findf(i);
printf("%lld ",f[x]);
}
return 0;
}