北航2018级算法期末第二次上机考试部分题解
C题 三角形
题目描述
给定 n
根木棍的长度,然后有 q
个询问,每一次询问给定 l
, r
表示询问从第 l
根木棍到第 r
根木棍中是否存在可以组成三角形的木棍。如果存在输出 Yes
,否则输出 No
。
数据大小
多组输入
每组数据第一行两个正整数 N 和 q (N, q ≤ 1e5)
。
第二行包含 N
个正整数 a1~aN
, ai(1 ≤ ai ≤ 1e10)
表示第 i
个木棍的长度。
接下来 q
行。每行包含两个正整数 li
和 ri
(1 ≤ li ≤ ri ≤ N)
表示查询 [li, ri]
区间内是否存在能够组成三角形的三条木棍。
输入
5 5
13 20 3 7 8
1 3
1 4
2 4
2 5
3 5
输出
No
No
No
Yes
Yes
解题思路
我们首先思考什么情况是可以构成三角形的。任取三根长度已知的木棍,长度分别为:l1
, l2
, l3
,那么如果可以构成三角形,这三个长度应该满足:
l1 + l2 > l3 且 l1 + l3 > l2 且 l2 + l3 > l1
即应该满足任意两边之和大于第三边
,反之,就不存在三角形。
通过上面的分析,我们可以很自然地想到通过暴力枚举的方式来对一个区间里的每种木棍组成方式进行测验。但是 N
的取值很大,如果使用三层循环的暴力枚举就会 TLE。这样我们接着想到去降低暴力枚举的区间长度,来降低运行时间。
为了减小搜索区间,我们再次回到三角形组成的条件:任意两边之和大于第三边
。
从而我们可以得到三根木棍不可以组成三角形的条件:存在两边之和小于等于第三边
那么我们思考,如果在长度为 n
的查询序列中没有可以生成三角形的木棍组合,那么将这 n
个数从小到大排序后也依旧无法组成三角形。那么此时我们可以得到:a[i - 1] + a[i] ≤ a[i + 1]
。
此我们可以联想到斐波那契数列,如果考虑最小增长情况 a[i -1] + a[i] = a[i + 1]
,那么 a[i]
就会持续增长,当 i
等于 50 的时候,a[i]
就会超过 1e10
, 就不符合题目的输入要求了。
通过上面的分析我们可以发现:当检查区间的长度大于等于 50 时,就可以直接认定存在了可以生成三角形的木棍。而剩下的情况我们就可以用暴力枚举的方式来进行检查。
代码如下
题解代码
#include <cstdio>
#include <cstring>
#define MAX 100005
using namespace std;
int N, q;
int l, r;//查询区间的范围变量
long long input[MAX];//木棍长度
int main(void)
{
while(scanf("%d%d", &N, &q) != EOF)//多组输入
{
for(int i = 1; i <= N; i++)
scanf("%lld", &input[i]);
while(q--)//循环查询
{
scanf("%d%d", &l, &r);
if(r - l >= 50)//查询区间大于50, 一定存在
{
printf("Yes\n");
continue;
}
int flag = 0;
for(int i = l; i <= r && !flag; i++)//暴力枚举
{
for(int j = i + 1; j <= r && !flag; j++)
{
for(int k = j + 1; k <= r && !flag; k++)
{
bool temp1 = input[i] + input[j] > input[k];//满足三角形组成条件
bool temp2 = input[i] + input[k] > input[j];
bool temp3 = input[j] + input[k] > input[i];
if(temp1 && temp2 && temp3)
flag = 1;
}
}
}
if(flag)
printf("Yes\n");
else
printf("No\n");
}
}
return 0;
}
D 魔术
题目描述
魔术师的桌子上有 n
个杯子排成一行,编号为1 ~ n
,其中某些杯子底下藏有一个小球,如果你准确地猜出是哪些杯子,你就可以获得奖品。花费Cij
元,魔术师就会告诉你杯子i ~ j
底下藏有球的总数的奇偶性。
采取最优的询问策略,你至少需要花费多少元,才能保证猜出哪些杯子底下藏着球?
数据描述
第一行一个整数n (1 <= n <= 2000)
。
第i + 1
行(1 <= i <= n)
有n + 1 - i
个整数,表示每一种询问所需的花费。其中Cij
(对区间[i,j]进行询问的费用,1 <= i <= j <= n, 1 <= Cij <= 1e9
)为第i + 1
行第j + 1 - i
个数。
输入
5
1 2 3 4 5
4 3 2 1
3 4 5
2 1
5
输出
7
解题思路
知道了第 x~y
个杯子的奇偶性,就相当于知道了 x
和 x-1
之间的缝到 y
和
y + 1
之间的缝的奇偶性,知道了缝 a
到缝 b
的奇偶性和缝 b
到缝 c
的奇偶性,我们就知道了缝 a
到缝 c
的奇偶性。
所以如果我们要知道所有杯子底下有没有球,我们就要知道每个杯子左右两端的缝之间的奇偶性,也就相当于要知道任意两个缝之间的奇偶性。
所以我们利用 n + 1
个空隙来代表是否知道了一个区间,对于每一个Cij
, 我们在 i - 1
和 j
之间建立一条权值为 Cij
的无向边,然后利用Prim算法来跑一遍最小生成树,最后输出最小生成树的权值就是最后的答案。
题解代码
#include <cstdio>
#include <cstring>
#define MAX 2005
#define INF 0x3f3f3f3f
using namespace std;
int n;
bool vis[MAX];//判断节点是否已经访问过了
int dis[MAX];//记录现在到某一节点的最小距离
int G[MAX][MAX];
int C;//查询费用
long long ans;//最后结果
bool judge()
{
for(int i = 0; i <= n; i++)
if(!vis[i]) return 1;
return 0;
}
void Prim()
{
memset(dis, INF, sizeof(dis));
dis[0] = 0;
while(judge())
{
int u = -1, min = INF;
for(int j = 0; j <= n; j++)
{
if(!vis[j] && dis[j] < min)
{
u = j;
min = dis[j];
}
}
vis[u] = true;
ans += dis[u];
for(int v = 0; v <= n; v++)
{
if(!vis[v] && G[u][v] < dis[v])
dis[v] = G[u][v];
}
}
return;
}
int main(void)
{
scanf("%d", &n);
for(int i = 0; i <= n; i++)
for(int j = 0; j <= n; j++)
G[i][j] = INF;//初始化图
ans = 0;//初始化 ans 为 0
for(int i = 1; i <= n; i++)
{
for(int j = i; j <= n; j++)
{
scanf("%d", &C);
G[i - 1][j] = C;//建边
G[j][i - 1] = C;//注意是无向图,所以要建立双向边
}
}
Prim();//因为是稠密图,所以使用Prim算法跑最小生成树
printf("%lld\n", ans);
return 0;
}
E 乘法
题目描述
第一行一个正整数 t
,代表数据组数
每组数据包含:
给定进制 k(2≤k≤10
)和两个k进制数 a,b
,求 a
和 b
在 k
进制下乘积的值,要求输出一个 k
进制的数。
输入
2
2
10
1
10
3
4
输出
10
12
解题思路
这道题,显然不能手写进制转换的函数,先将 k
进制转化成 10
进制,再利用 FFT 计算出 10
进制结果,再转化回 k
进制(因为输入的字符串长度非常大,所以一定会超时)。所以我们直接修改 FFT 的操作流程即可,下面给出结果代码
题解代码
#include <cstdio>
#include <math.h>
#include <algorithm>
#include <cstring>
using namespace std;
const double PI = acos(-1.0);
struct Complex
{
double x,y;
Complex(double _x = 0.0, double _y = 0.0){
x = _x;
y = _y;
}
Complex operator -(const Complex &b)const{
return Complex(x - b.x, y - b.y);
}
Complex operator +(const Complex &b)const{
return Complex(x + b.x, y + b.y);
}
Complex operator *(const Complex &b)const{
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
};
void change(Complex y[], int len){
int i, j, k;
for(i = 1, j = len / 2; i < len - 1; i++){
if(i < j) swap(y[i], y[j]);
k = len / 2;
while(j >= k){
j -= k;
k /= 2;
}
if(j < k)
j += k;
}
}
void fft(Complex y[], int len, int on){
change(y, len);
for(int h = 2; h <= len; h <<= 1){
Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
for(int j = 0; j < len; j += h){
Complex w(1, 0);
for(int k = j; k < j + h / 2; k++){
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w * wn;
}
}
}
if(on == -1)
for(int i = 0; i < len; i++)
y[i].x /= len;
}
const int MAXN = 4000010;
Complex x1[MAXN], x2[MAXN];
char str1[MAXN / 2], str2[MAXN / 2];
int sum[MAXN];
int t, k;
int main(void)
{
scanf("%d", &t);
while(t--)
{
scanf("%d", &k);
scanf("%s%s", str1, str2);
int len1 = strlen(str1);
int len2 = strlen(str2);
if(str1[0] == '0' || str2[0] == '0')
{
printf("0\n");
continue;
}
int len = 1;
while(len < len1 * 2 || len < len2 * 2) len <<= 1;
for(int i = 0; i < len1; i++)
x1[i] = Complex(str1[len1 - 1 - i] - '0', 0);
for(int i = len1; i < len; i++)
x1[i] = Complex(0, 0);
for(int i = 0; i < len2; i++)
x2[i] = Complex(str2[len2 - 1 - i] - '0', 0);
for(int i = len2; i < len; i++)
x2[i] = Complex(0, 0);
fft(x1, len, 1);
fft(x2, len, 1);
for(int i = 0; i < len; i++)
x1[i] = x1[i] * x2[i];
fft(x1, len, -1);
for(int i = 0; i < len; i++)
sum[i] = (int)(x1[i].x + 0.5);
for(int i = 0; i < len; i++){
sum[i + 1] += sum[i] / k;
sum[i] %= k;
}
len = len1 + len2 - 1;
while(sum[len] <= 0 && len > 0) len--;
for(int i = len; i >= 0; i--)
printf("%c", sum[i] + '0');
printf("\n");
}
return 0;
}
F 图
题目描述
宝石排列在一个n*m的网格中,每个网格中有一块价值为v(i, j)
的宝石,阿尔托利亚·潘德拉贡可以选择自己的起点。
开始时刻为 0
秒。以下操作,每秒按顺序执行
在第i秒开始的时候,阿尔托利亚·潘德拉贡在方格(x,y)
上,她可以拿走(x,y)
中的宝石。
在偶数秒,阿尔托利亚·潘德拉贡周围四格的宝石会消失
若阿尔托利亚·潘德拉贡第i秒开始时在方格(x,y)
上,则在第 i + 1
秒可以立即移动到(x + 1,y),(x,y + 1),(x - 1,y)
或(x,y - 1)
上,也可以停留在(x, y)
上。
求阿尔托利亚·潘德拉贡最多可以获得多少价值的宝石?
数据描述
输入格式 第一行给出数字 N, M
代表行列数。
N, M
均小于等于 100
,宝石的价值不会超过 10000
。
下面 N
行 M
列用于描述数字矩阵
输出格式
输出最多可以拿到多少价值宝石
输入
2 2
1 2
2 1
输出
4
解题思路
这道题其实思路十分简单,看出是二分图的问题还算比较简单的。那么接下来的问题就是如何建图(虽然看出二分图就应该知道怎么建图了)。
那么我们来思考一下,偶数秒可以获得当前格的礼物,但是周围四个格的礼物会消失,而下一秒也就是奇数秒我们只能任选周围的四个格子中的一个来进行位置的转移。而且初始时刻是 0
秒。那么也就是说,我们只能在偶数秒获得礼物,并且每一次获得的礼物都是不相邻的(所以说时刻根本没用)。
说了这么多,其实就是说选取格子中的礼物,但是每次选取的礼物不可以相邻。那么这就是二分图最大点权独立集。
我们只需要利用每个格子横坐标和纵坐标之和的奇偶性来区分黑白格子,再创建超级源点和超级汇点,跑出最小割(也就是最大流),再用礼物总和减去最小割就是最终的答案。这里使用的最大流最小割算法是 Dinic 算法
题解代码
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstring>
#define MAX 100100
#define INF 0x3f3f3f3f
using namespace std;
int n, m;
int input;
int s, t;
int d[MAX], cur[MAX];
bool vis[MAX];
struct Edge{//边的数据结构
int from, to, cap, flow;
Edge(int u, int v, int c, int f):from(u), to(v), cap(c), flow(f){}
};
vector<int> G[MAX];
vector<Edge> edges;
bool BFS()//BFS分层
{
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(0);
d[0] = 0;
vis[0] = 1;
while(!Q.empty())
{
int x = Q.front();
Q.pop();
for(int i = 0; i < G[x].size(); i++)
{
Edge &e = edges[G[x][i]];
if(!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
Q.push(e.to);
d[e.to] = d[x] + 1;
}
}
}
return vis[t];
}
int DFS(int x, int a)//DFS计算流
{
if(x == t || !a)
return a;
int flow = 0, f;
for(int& i = cur[x]; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if(d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if(!a)
break;
}
}
return flow;
}
int Dinic()//Dinic跑出最大流最小割
{
int flow = 0;
while(BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(0, INF);
}
return flow;
}
void AddEdge(int u, int v, int c)//建边的函数
{
edges.push_back(Edge(u, v, c, 0));
edges.push_back(Edge(v, u, 0, 0));
int si = edges.size();
G[u].push_back(si - 2);
G[v].push_back(si - 1);
return;
}
int main(void)
{
scanf("%d%d", &n, &m);
s = 0;//超级源点
t = n * m + 1;//超级汇点
long long ans = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
scanf("%d", &input);
ans += input;//输入的同时计算好礼物总和
if((i + j) % 2)//奇顶点
{
AddEdge(s, (i - 1) * m + j, input);
}
else//偶顶点
{
AddEdge((i - 1) * m + j, t, input);
}
}
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
int temp = i + j;
int loc = (i - 1) * m + j;
if(temp % 2)//奇顶点
{
if(j < n)
AddEdge(loc, loc + 1, INF);
if(j > 1)
AddEdge(loc, loc - 1, INF);
if(i < m)
AddEdge(loc, loc + n, INF);
if(i > 1)
AddEdge(loc, loc - n, INF);
}
}
}
ans -= Dinic();//礼物总和减去最小割
printf("%lld\n", ans);
return 0;
}