CF Edu 92

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的线段数。可以用线段树维护这一操作。

  1. 将线段端点离散处理
  2. 将所有线段按照右端点(从小到大)为第一关键字,左端点(从大到小)为第二关键字排序。
  3. 枚举右端点,在线段树上更新相应线段。如果当前颜色存在线段[ l i l_{i} li, r i r_{}i ri],那么在线段树[1, l i − 1 l_{i}-1 li1]区间上+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是可达点的情况下,整张图的最大利润。
思路

  1. tarjan缩点,双连通分量里的点一定可以互达,这样整张图变成一棵树
  2. 如果一个节点的所有子节点中没有关键点,那么只要把这棵子树所有边定成向下的就好。因此,这棵子树也可以被“缩”起来。
  3. 此时,树上所有叶子节点都是关键点。选一个关键点作为根,然后进行树上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;
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值