A + B Problem 题解

(重发下这篇原发于2013-06-08的网易博客)

 

 

【算法一】

对于30%的数据 n <= 20。

于是可以直接O(2^n)枚举答案,然后O(n^2)求出答案进行更新。

期望得分30分

 

【算法二】

对于30%的数据 maxA <= 10。

于是可以用状压DP来解。记f[i][status]表示考虑前i个格子,每个数的状态为status的情况下的好看度最大是多少。status的第j位为1表示之前出现了白色的这个数,否则表示没出现。dp方程很简单就不写了。不过好像内存有点压力,所以用滚动数组搞一下就行了。O(n * 2^maxA)

期望得分30分。

 

【算法三】

对于60%的数据 n <= 500。

对于每个格子有黑白两种状态,可以联想到最小割。一个格子有在S割和在T割两种状态。

 

我们来回忆一下最小割。如果v向u连一条容量为w的有向边,表示v如果在S割,那么u不在S割会产生w的代价。

一个等价的表述是,u如果在T割,那么v不在T割会产生w的代价。

注意v如果在T割,那么u在S割是不会产生代价的。

特别的,如果v向u连一条容量为正无穷大的有向边,表示v如果在S割,那么u一定也要在S割。

一个等价的表述是,u如果在T割,那么v一定也要在T割。

 

考虑要最大化的式子。发现又有加法又有减法,而我们是最小割不是最大割,这样就很坑。

于是可以这样想:对于每个格子,先拿到b[i]和w[i]的好看度。

然后如果这个格子是黑色,那么付出w[i]的代价,如果这个格子是白色,付出b[i]的代价。

这样就全统一成了代价,最小化这个代价即可。

 

建立结点1, 2, ..., n。结点i在S割表示格子i染黑,在T割表示格子i染白。

根据前面算代价的方式,那么结点i在S割需要付出w[i]的代价,在T割需要付出b[i]的代价。

所以连边:(用(起始点,终点,容量)表示一条有向边)

(S, i, b[i])

(i, T, w[i])

 

接下来就是奇怪的格子的问题,如果格子i是奇怪的格子,就要付出p[i]的代价。

于是我们希望能有一个结点i',使得如果存在白色的格子j满足 1 <= j < i, l[i] <= a[j] <= r[i],那么i'就在T割,否则在S割。

这样如果构造出来了这样的i',我们希望当且仅当i在S割,i'在T割时付出p[i]的代价。发现这个很好搞。

连边:

(i, i', p[i])

 

现在来考虑构造i'。

对于每个白色的格子j满足 1 <= j < i, l[i] <= a[j] <= r[i],我们希望j如果在T割,那么i'也一定要在T割。发现这个很好搞。

连边:

(i', j, 正无穷大)

 

这样是不是完了呢?其实这样构造出来的i',如果存在白色的格子j满足 1 <= j < i, l[i] <= a[j] <= r[i],那么i'一定在T割。如果不存在,那么i'可能在S割也可能在T割。

注意到此时i'在T割肯定比i'在S割的代价大,由于我们求的是最小割,所以这样构造i'是正确的。

 

如此以来网络流图的点数V = O(n), 边数E = O(n^2)。足以通过这60%的数据。

期望得分60分。

 

【算法四】

使用算法二和算法三,可以拿下前8个测试点。

期望得分80分。

 

【算法五】

为什么算法三不能通过最后两个点呢?原因是边数过多,MLE了。(而且会TLE)

那么有没有办法能减少边的数量呢?

答案是有的。

 

考虑一个这样的问题:现在给你一个染色方案,请在O(n log n)时间内求出这个方案的好看度。

离散化 + 线段树或树状数组的裸题。

 

既然如此,我们能不能将线段树嵌入到网络流的图里去呢?

首先简化问题,假设没有1 <= j < i这个限制,且所有a的值互不相等。

关键是看i'的构造。如前所述,我们只要构造出来 "如果存在满足条件的j,那么i'一定在T割。如果不存在,那么i'可能在S割也可能在T割" 就行了。

考虑一棵线段树,用线段树查询[l[i], r[i]]这个区间能得到一些结点,这些结点每个代表一个区间,这些不相交的区间的并集就是[l[i], r[i]]。

那么这些区间里面,只要有一个白色的结点,i'都必须在T割。

连边:

(i', 查询到的结点, 正无穷大)

 

接下来就是保证线段树结点一定满足这样的性质:它所代表的区间中有在T割的,那么它也一定在T割。

连边:

(线段树的非叶结点, 左儿子, 正无穷大)

(线段树的非叶结点, 右儿子, 正无穷大)

(a[i]对应的线段树叶结点, i, 正无穷大)

 

这样就成功嵌入进去了。

 

现在考虑加上1 <= j < i这个限制呢?所有a的值仍然互不相等。

只要用可持久化线段树就行了。每次在a[i]这个位置插入构造出来新的线段树。

 

如果a值有相等呢?

我们希望满足的是,"它所代表的区间中有在T割的,那么它也一定在T割。"

于是此时线段树叶结点就要向所有的k满足k <= i, a[k] = a[i]的连边了?

显然是不必要的。

只要多加一条这个就行了:

(当前新建的叶结点, 之前的叶结点, 正无穷大)

 

如此以来网络流图的点数V = O(n log n), 边数E = O(n log n)。

期望得分100分。

 

 

我觉得算法五好有趣的!

代码:

#include <iostream>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;

const int MaxN = 5000;
const int MaxLogN = 13;

const int MaxNSegNode = MaxN * (MaxLogN + 1);
const int MaxNSegRes = 2 * (MaxLogN + 1);

// getint
inline int getint()
{
    char c;
    while (c = getchar(), ('0' > c || c > '9') && c != '-');
    
    if (c != '-')
    {
        int res = c - '0';
        while (c = getchar(), '0' <= c && c <= '9')
            res = res * 10 + c - '0';
        return res;
    }
    else
    {
        int res = 0;
        while (c = getchar(), '0' <= c && c <= '9')
            res = res * 10 + c - '0';
        return -res;
    }
}

// adjust
template <class T>
inline bool relax(T &a, const T &b)
{
	if (b > a)
	{
		a = b;
		return true;
	}
	return false;
}
template <class T>
inline bool tension(T &a, const T &b)
{
	if (b < a)
	{
		a = b;
		return true;
	}
	return false;
}

// max flows
namespace NetworkFlows
{
	const int MaxNVer = 1 + MaxN + MaxN + MaxNSegNode + 1;
	const int MaxNE = MaxNSegNode * 2 + MaxN + MaxN + MaxN * (3 + MaxNSegRes);

	struct halfEdge
	{
		int u, w;
		halfEdge *next;
	};
	halfEdge adj_pool[MaxNE * 2], *adj_tail;

	int nVer;
	int source, terminal;
	halfEdge *adj[MaxNVer];

	inline void init()
	{
		fill(adj, adj + nVer, (halfEdge *)NULL);
		adj_tail = adj_pool;
	}
	inline void addEdge(const int &v, const int &u, const int &w)
	{
		adj_tail->u = u, adj_tail->w = w, adj_tail->next = adj[v], adj[v] = adj_tail++;
		adj_tail->u = v, adj_tail->w = 0, adj_tail->next = adj[u], adj[u] = adj_tail++;
	}

	inline halfEdge *opposite(halfEdge *e)
	{
		return adj_pool + ((e - adj_pool) ^ 1);
	}

	int outcome_flow;

	int dist[MaxNVer];
	int book[MaxNVer];

	int dfs(const int &v, const int &delta)
	{
		if (v == terminal)
			return delta;
		int flow = 0;
		for (halfEdge *i = adj[v]; i; i = i->next)
			if (i->w > 0 && dist[v] == dist[i->u] + 1)
			{
				int add = dfs(i->u, min(i->w, delta - flow));
				i->w -= add, opposite(i)->w += add;
				flow += add;

				if (flow == delta || dist[source] == INT_MAX)
					return flow;
			}
		if (flow == 0)
		{
			if (--book[dist[v]] == 0)
			{
				dist[source] = INT_MAX;
				return 0;
			}

			dist[v] = INT_MAX;
			for (halfEdge *i = adj[v]; i; i = i->next)
				if (i->w > 0 && dist[i->u] != INT_MAX)
					tension(dist[v], dist[i->u] + 1);
			if (dist[v] != INT_MAX)
				book[dist[v]]++;
		}
		return flow;
	}

	inline void calcMaxFlows()
	{
		fill(dist, dist + nVer, 0);
		fill(book, book + nVer, 0);
		book[0] = nVer;

		outcome_flow = 0;
		while (dist[source] != INT_MAX)
			outcome_flow += dfs(source, INT_MAX);
	}
}

struct seg_node
{
	seg_node *lc, *rc;
};
seg_node seg_pool[MaxNSegNode], *seg_tail = seg_pool;

seg_node *seg_add(seg_node *p, const int &pL, const int &pR, const int &val)
{
	seg_node *np = seg_tail++;
	if (p)
		*np = *p;
	else
		np->lc = np->rc = NULL;

	if (pL == pR)
		return np;

	int pMid = pL + pR >> 1;
	if (val <= pMid)
		np->lc = seg_add(np->lc, pL, pMid, val);
	else
		np->rc = seg_add(np->rc, pMid + 1, pR, val);
	return np;
}

int seg_res_n;
seg_node *seg_res[MaxNSegRes];

void seg_query(seg_node *p, const int &pL, const int &pR, const int &qL, const int &qR)
{
	if (!p)
		return;
	if (qL <= pL && pR <= qR)
	{
		seg_res[seg_res_n++] = p;
		return;
	}

	int pMid = pL + pR >> 1;
	if (qL <= pMid)
		seg_query(p->lc, pL, pMid, qL, qR);
	if (pMid < qR)
		seg_query(p->rc, pMid + 1, pR, qL, qR);
}

int main()
{
	int n;
	static int a[MaxN + 1];
	static int bVal[MaxN], wVal[MaxN + 1];
	static int qL[MaxN + 1], qR[MaxN + 1];
	static int penalty[MaxN + 1];

	cin >> n;
	for (int i = 1; i <= n; i++)
		a[i] = getint(), bVal[i] = getint(), wVal[i] = getint(), qL[i] = getint(), qR[i] = getint(), penalty[i] = getint();

	int seqA_n = 0;
	static int seqA[MaxN];
	for (int i = 1; i <= n; i++)
		seqA[seqA_n++] = a[i];
	sort(seqA, seqA + seqA_n);
	seqA_n = unique(seqA, seqA + seqA_n) - seqA;

	for (int i = 1; i <= n; i++)
	{
		a[i] = lower_bound(seqA, seqA + seqA_n, a[i]) - seqA;
		qL[i] = lower_bound(seqA, seqA + seqA_n, qL[i]) - seqA;
		qR[i] = upper_bound(seqA, seqA + seqA_n, qR[i]) - seqA - 1;
	}

	static seg_node *tr[MaxN + 1];
	tr[0] = NULL;
	for (int i = 1; i <= n; i++)
		tr[i] = seg_add(tr[i - 1], 0, n - 1, a[i]);

	int nSegNode = seg_tail - seg_pool;

	namespace NF = NetworkFlows;

	NF::nVer = 1 + n + n + nSegNode + 1;
	NF::source = 0, NF::terminal = NF::nVer - 1;
	NF::init();

	int sum = 0;

	int shSeg = 1 + n + n;

	for (seg_node *p = seg_pool; p != seg_tail; p++)
	{
		if (p->lc)
			NF::addEdge(shSeg + p - seg_pool, shSeg + p->lc - seg_pool, INT_MAX);
		if (p->rc)
			NF::addEdge(shSeg + p - seg_pool, shSeg + p->rc - seg_pool, INT_MAX);
	}
	for (int i = 1; i <= n; i++)
	{
		seg_res_n = 0;
		seg_query(tr[i], 0, n - 1, a[i], a[i]);
		seg_node *cur = seg_res[0];

		seg_res_n = 0;
		seg_query(tr[i - 1], 0, n - 1, a[i], a[i]);
		seg_node *last = seg_res[0];

		NF::addEdge(shSeg + cur - seg_pool, i, INT_MAX);
		if (last)
			NF::addEdge(shSeg + cur - seg_pool, shSeg + last - seg_pool, INT_MAX);
	}
	for (int i = 1; i <= n; i++)
	{
		sum += bVal[i];
		NF::addEdge(NF::source, i, bVal[i]);

		sum += wVal[i];
		NF::addEdge(i, NF::terminal, wVal[i]);

		NF::addEdge(i, n + i, penalty[i]);
		if (qL[i] <= qR[i])
		{
			seg_res_n = 0;
			seg_query(tr[i - 1], 0, n - 1, qL[i], qR[i]);

			for (int j = 0; j < seg_res_n; j++)
				NF::addEdge(n + i, shSeg + seg_res[j] - seg_pool, INT_MAX);
		}
	}

	NF::calcMaxFlows();
	cout << sum - NF::outcome_flow << endl;

	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值