您的位置:首页 > 其它

APIO2015 UOJ #110 - #112 题解

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

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

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

但我们注意到最后一个子任务中A等于1,即没有下限,那么我们可以用dp[i]表示前i个满足条件最少要分多少块,最后判断dp
是否 <= 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
)
++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
[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
)
++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
> 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: