APIO2015 UOJ #110 - #112 题解

5 篇文章 0 订阅
3 篇文章 0 订阅

T1:给你一个序列A,问将A划分成K块(A <= K <= B),将每块中的元素求和,再按位或能得到的最小值是多少(话说为毛是最小,这背后怕是有奇怪的交易)

有个显然的想法是按二进制位依次考虑。那么假设当前在考虑第pos位,之前位的ans已经确定,我们可以用dp[[i][j]表示前i个元素分成j块能否满足条件,那么枚举断点,再判断一下就行。最后看这一位能否为0,就是找dp[N][i] (A <= i <= B)中有没有为true的项。

上述做法复杂度为O(N ^ 3 * log(Ans)),无法通过最后一个子任务。

但我们注意到最后一个子任务中A等于1,即没有下限,那么我们可以用dp[i]表示前i个满足条件最少要分多少块,最后判断dp[N]是否 <= B即可,复杂度O(N ^ 2 * log(Ans)).

代码如下:


/*
* @Author: 逸闲
* @Date:   2016-04-25 11:33:23
* @Last Modified by:   逸闲
* @Last Modified time: 2016-04-25 12:09:05
*/

#include "cstdio"
#include "cstdlib"
#include "iostream"
#include "algorithm"
#include "cstring"
#include "queue"

using namespace std;

#define INF 0x3F3F3F3F
#define Eps
#define Mod
#define Get(x, a) (x ? x -> a : 0)

inline int Get_Int()
{
	int Num = 0, Flag = 1;
	char ch;
	do
	{
		ch = getchar();
		if(ch == '-')
			Flag = -Flag;
	}
	while(ch < '0' || ch > '9');
	do
	{
		Num = Num * 10 + ch - '0';
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9');
	return Num * Flag;
}

int N, A, B;

namespace Task_1_4
{
	const int MAX_SIZE = 105;

	long long Ans;
	long long Sum[MAX_SIZE];

	bool DP[MAX_SIZE][MAX_SIZE];

	inline void Solve()
	{
		for(int i = 1; i <= N; ++i)
			Sum[i] = Sum[i - 1] + Get_Int();
		long long Now = 0;
		while(1LL << Now < Sum[N])
			++Now;
		for(; Now >= 0; --Now)
		{
			memset(DP, false, sizeof(DP));
			DP[0][0] = true;
			for(int i = 1; i <= N; ++i)
				for(int j = 1; j <= B; ++j)
					for(int k = 0; k < i; ++k)
						if(DP[k][j - 1])
						{
							long long temp = Sum[i] - Sum[k];
							if((((temp >> Now << Now) | Ans) == Ans) && ((temp & (1LL << Now)) == 0))
								DP[i][j] = true;
						}
			bool Flag = false;
			for(int i = A; i <= B; ++i)
				if(DP[N][i])
				{
					Flag = true;
					break;
				}
			if(!Flag)
				Ans += 1LL << Now;
		}
		cout << Ans << endl;
	}
}

namespace Task_5
{
	const int MAX_SIZE = 2005;

	long long Ans;
	long long Sum[MAX_SIZE];

	int DP[MAX_SIZE];

	inline void Solve()
	{
		for(int i = 1; i <= N; ++i)
			Sum[i] = Sum[i - 1] + Get_Int();
		long long Now = 0;
		while(1LL << Now < Sum[N])
			++Now;
		for(; Now >= 0; --Now)
		{
			memset(DP, 0x3F, sizeof(DP));
			DP[0] = 0;
			for(int i = 1; i <= N; ++i)
				for(int j = 0; j < i; ++j)
				{
					long long temp = Sum[i] - Sum[j];
					if((((temp >> Now << Now) | Ans) == Ans) && ((temp & (1LL << Now)) == 0))
						DP[i] = min(DP[i], DP[j] + 1);
				}
			if(DP[N] > B)
				Ans += 1LL << Now;
		}
		cout << Ans << endl;
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	cin >> N >> A >> B;
	if(N <= 100)
		Task_1_4::Solve();
	else
		Task_5::Solve();
	fclose(stdin);
	fclose(stdout);
	return 0;
}


T2:给你N个点M个边集,每个边集是从一个点开始,每隔相同的格子就连边。求两点间最短路。

一眼看上去就是要分块咯。若边集小于sqrt(N)则直接连边,否则的话:对于每个点为起点,只有sqrt(N)种连边方式,那我们新建N * sqrt(N)个点,每层之间互相连边。对于每个边集就直接往对应的起点的对应辅助点连边即可。

这样点数和边数均为O(N * sqrt(N)),然后跑最短路即可。

HINT:我写了个spfa只拿了97分,估计是被卡了,不过感觉dijkstra好不到哪里去。注意到边权都为0或1,那么可以直接用bfs代替最短路,如果通过边权为0的边更新了距离,那么将点加入队首,否则加入队尾。(不过懒得写了,嘴巴选手的感觉真爽)

97分代码如下:

*
* @Author: duyixian
* @Date:   2016-04-25 20:00:18
* @Last Modified by:   duyixian
* @Last Modified time: 2016-04-25 20:27:05
*/

#include "cstdio"
#include "cstdlib"
#include "iostream"
#include "algorithm"
#include "cstring"
#include "queue"
#include "cmath"

using namespace std;

#define INF 0x3F3F3F3F
#define MAX_SIZE 30005
#define Eps
#define Mod
#define Get(x, a) (x ? x -> a : 0)
#define Travel(x) for(typeof(x.begin()) it = x.begin(); it != x.end(); ++it)

inline int Get_Int()
{
	int Num = 0, Flag = 1;
	char ch;
	do
	{
		ch = getchar();
		if(ch == '-')
			Flag = -Flag;
	}
	while(ch < '0' || ch > '9');
	do
	{
		Num = Num * 10 + ch - '0';
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9');
	return Num * Flag;
}

class Edge
{
public:
	int To, Next, C;
}Edges[MAX_SIZE * 102 * 8];

int N, M, Total, S, T, Size = 100;
int Front[MAX_SIZE * 102], Distance[MAX_SIZE * 102], B[MAX_SIZE], P[MAX_SIZE];

bool InQueue[MAX_SIZE * 102];

inline void Add_Edge(int From, int To, int C)
{
	//printf("%d %d %d\n", From, To, C);
	Edges[++Total].To = To;
	Edges[Total].Next = Front[From];
	Edges[Total].C = C;
	Front[From] = Total;
}

inline void Add_Edges(int From, int To, int C)
{
	Add_Edge(From, To, C);
	Add_Edge(To, From, C);
}

inline void SPFA()
{
	memset(Distance, 0x3F, sizeof(Distance));
	Distance[S] = 0;
	queue<int> Queue;
	Queue.push(S);
	InQueue[S] = true;
	while(!Queue.empty())
	{
		int Now = Queue.front();
		Queue.pop();
		InQueue[Now] = false;
		for(int i = Front[Now]; i; i = Edges[i].Next)
			if(Distance[Edges[i].To] > Distance[Now] + Edges[i].C)
			{
				Distance[Edges[i].To] = Distance[Now] + Edges[i].C;
				if(!InQueue[Edges[i].To])
				{
					Queue.push(Edges[i].To);
					InQueue[Edges[i].To] = true;
				}
			}
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	cin >> N >> M;
	Size = min(Size, (int)sqrt(N));
	for(int i = 1; i <= M; ++i)
	{
		B[i] = Get_Int() + 1;
		P[i] = Get_Int();
	}
	S = B[1];
	T = B[2];
	for(int i = 1; i <= N; ++i)
		for(int j = 1; j <= Size; ++j)
		{
			Add_Edge(i + j * N, i, 0);
			if(i + j <= N)
				Add_Edges(i + j * N, i + j + j * N, 1);
		}
	for(int i = 1; i <= M; ++i)
	{
		int Now = B[i];
		if(P[i] > Size)
		{
			for(int j = 1; Now + j * P[i] <= N; ++j)
				Add_Edge(Now, Now + j * P[i], j);
			for(int j = 1; Now - j * P[i] >= 1; ++j)
				Add_Edge(Now, Now - j * P[i], j);
		}
		else
			Add_Edge(Now, Now + P[i] * N, 0);
	}
	SPFA();
	if(Distance[T] != INF)
		cout << Distance[T] << endl;
	else
		cout << -1 << endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3:有一条河,可以建造K座桥(K <= 2),桥必须垂直于河,河宽度为1,有N个人,给出他们家和办公地点的位置,问最短总距离。

对于K=1的情况:桥显然要建在所有坐标的中位数(不用过河的人先去掉);

对于K=2的情况:一个人肯定要走离他两个坐标平均值距离最近的那座桥,那么把所有人按两个坐标平均值排序,对于任意建造方案,定有一个分割点,使得分割点左边的点和右边的点走不同的桥,那么枚举分割点,转化成K=1的情况即可。

代码如下:

/*
* @Author: 逸闲
* @Date:   2016-04-26 10:18:50
* @Last Modified by:   逸闲
* @Last Modified time: 2016-04-26 13:50:14
*/

#include "cstdio"
#include "cstdlib"
#include "iostream"
#include "algorithm"
#include "cstring"
#include "queue"

using namespace std;

#define INF 0x3F3F3F3F
#define MAX_SIZE 200005
#define Eps
#define Mod
#define Get(x, a) (x ? x -> a : 0)
#define L(i) (i ? Mid + 1 : Left)
#define R(i) (i ? Right : Mid)

inline int Get_Int()
{
	int Num = 0, Flag = 1;
	char ch;
	do
	{
		ch = getchar();
		if(ch == '-')
			Flag = -Flag;
	}
	while(ch < '0' || ch > '9');
	do
	{
		Num = Num * 10 + ch - '0';
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9');
	return Num * Flag;
}

namespace Segment_Tree
{
	class Node
	{
	public:
		Node *Child[2];
		long long Sum, Size;
	}Nodes[MAX_SIZE * 35];

	Node *Total = Nodes;

	Node* Mofidy(Node *x, int Position, int Value, int Left, int Right)
	{
		if(!x)
			x = Total++;
		x -> Size += Value;
		x -> Sum += (long long)Value * Position;
		if(Left != Right)
		{
			int Mid = Left + Right >> 1, i = Position > Mid;
			x -> Child[i] = Mofidy(x -> Child[i], Position, Value, L(i), R(i));
		}
		return x;
	}

	long long Query(Node *x, int K, long long &temp, int Left, int Right)
	{
		if(!x)
			return 0;
		long long Ans = 0;
		if(Left == Right)
		{
			temp = Left;
			return 0;
		}
		int LeftSize = Get(x -> Child[0], Size);
		int i = 0, Mid = Left + Right >> 1;
		if(LeftSize < K)
			K -= LeftSize, i = 1;
		Ans += Query(x -> Child[i], K, temp, L(i), R(i));
		if(i)
			Ans += temp * Get(x -> Child[0], Size) - Get(x -> Child[0], Sum);
		else
			Ans += Get(x -> Child[1], Sum) - temp * Get(x -> Child[1], Size);
		return Ans;
	}
}

class Point
{
public:
	int Left, Right;

	inline bool operator < (Point const &a) const
	{
		return Left + Right < a.Left + a.Right;
	}
}Points[MAX_SIZE];

int K, N, Total;

long long Ans, Min;

Segment_Tree::Node *Root1, *Root2;

int main()
{
#ifndef ONLINE_JUDGE
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	cin >> K >> N;
	for(int i = 1; i <= N; ++i)
	{
		char temp1[3], temp2[3];
		int Left, Right;
		scanf("%s%d%s%d", temp1, &Left, temp2, &Right);
		if(Left > Right)
			swap(Left, Right);
		if(temp1[0] == temp2[0])
			Ans += (long long)(Right - Left);
		else
			Points[++Total] = (Point){Left, Right};
	}
	Ans += (long long)Total;
	sort(Points + 1, Points + Total + 1);
	long long temp;
	for(int i = 1; i <= Total; ++i) 	
	{
		Root1 = Segment_Tree::Mofidy(Root1, Points[i].Left, 1, 0, INF);
		Root1 = Segment_Tree::Mofidy(Root1, Points[i].Right, 1, 0, INF);
	}
	if(K == 1)
		cout << Ans + Segment_Tree::Query(Root1, Get(Root1, Size) >> 1LL, temp, 0, INF) << endl;
	else
	{
		Min = Segment_Tree::Query(Root1, Get(Root1, Size) >> 1, temp, 0, INF);
		for(int i = Total; i; --i)
		{
			Root1 = Segment_Tree::Mofidy(Root1, Points[i].Left, -1, 0, INF);
			Root1 = Segment_Tree::Mofidy(Root1, Points[i].Right, -1, 0, INF);
			Root2 = Segment_Tree::Mofidy(Root2, Points[i].Left, 1, 0, INF);
			Root2 = Segment_Tree::Mofidy(Root2, Points[i].Right, 1, 0, INF);
			Min = min(Min, Segment_Tree::Query(Root2, Get(Root2, Size) >> 1LL, temp, 0, INF) + Segment_Tree::Query(Root1, Get(Root1, Size) >> 1LL, temp, 0, INF));
		}
		cout << Ans + Min << endl;
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值