AtCoder Beginner Contest 339

前面两道阅读理解直接跳过。

C - Perfect Bus

大意

给定以下不等式组,求min(x+A_1+A_2+...+A_n)

x+A_1 \ge 0

x+A_1+A_2 \ge 0

\cdots

x+A_1+A_2+\cdots+A_n \ge 0

思路

难点在于计算x

先不管条件,设x=0,再计算这些不等式左边的结果,取最小值。

如果为负数,则x为最小值的相反数。

算出x后,计算最终结果。

代码

#include<iostream>
using namespace std;
typedef long long LL;
int main() {
	LL n, x;
	cin >> n;
	LL cur = 0, minn = 0;
	while (n--) {
		cin >> x;
		cur += x;
		minn = min(minn, cur);
	}
	cout << max(0ll, -minn) + cur << endl;
	return 0;
}

D - Synchronized Players

大意

一个n\times n的迷宫,有一些障碍物,有两人。

问最小的操作数,使得两人位于同一个格子。

操作为:选定上下左右一个方向,使两人均往同方向移动一格。如果目的地是障碍物或出界,则不动。

思路

数据范围不大,暴力bfs就行。

设数组vis,vis_{a,b,c,d}表示状态(a,b,c,d)是否访问过。

从两个玩家起始的位置开始搜索,首先把起始状态丢入队列。

对于每个状态,尝试让两个玩家移动到上下左右四个位置,如果有一个玩家移动后出界或者是移动到障碍物上,那就不动它。

在看一个新的状态时,如果没有访问过,那就扔进队列。

当两个玩家位置重合,就输出步数。

如果队列为空时仍未找到答案,输出-1。

代码

#include<iostream>
#include<queue>
using namespace std;
const int N = 70;
int n;
char map[N][N];
bool vis[N][N][N][N];
const int dir[4][2] = { -1,0,0,1,1,0,0,-1 };

// 表示状态的结构体
struct node {
	int x1, y1, x2, y2, step;
};
queue<node> q;

// 把一个状态扔进队列并标记
void que_add(int x1, int y1, int x2, int y2, int s) {
	node p = { x1,y1,x2,y2,s };
	vis[x1][y1][x2][y2] = true;
	q.push(p);
}

// 检查点(x,y)是否合法
bool check(int x, int y) {
	if (x<1 || x>n || y<1 || y>n) return false;
	if (map[x][y] == '#') return false;
	return true;
}

// 广搜函数
int bfs(int x1, int y1, int x2, int y2) {
	que_add(x1, y1, x2, y2, 0);   // 初始状态入队
	while (q.size()) {
		node t = q.front();
		q.pop();

		// 扩展节点
		for (int i = 0; i < 4; i++) {
			int nx1 = t.x1 + dir[i][0], ny1 = t.y1 + dir[i][1];
			int nx2 = t.x2 + dir[i][0], ny2 = t.y2 + dir[i][1];

			if (!check(nx1, ny1)) nx1 = t.x1, ny1 = t.y1;
			if (!check(nx2, ny2)) nx2 = t.x2, ny2 = t.y2;

			// 位置重合
			if (nx1 == nx2 && ny1 == ny2) return t.step + 1;

			// 如果没有访问过就扔进队列继续搜索
			if (!vis[nx1][ny1][nx2][ny2]) que_add(nx1, ny1, nx2, ny2, t.step + 1);
		}
	}
	return -1;
}

int main() {
	int a = 0, b, c, d;
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> map[i][j];
			if (map[i][j] == 'P') {
				if (a) c = i, d = j;
				else a = i, b = j;
			}
		}
	}
	cout << bfs(a, b, c, d) << endl;
	return 0;
}

E - Smooth Subsequence

大意

给定序列A 和阈值D

我们称一个序列S是「光滑序列」,当且仅当其相邻两项之差的绝对值都不超过 D

A的最长「光滑子序列」。

思路

和最长不下降子序列相似,但是朴素的O(n^2)算法过不了,需要线段树优化。

这里,我们将[-5\times 10^5,5\times 10^5]范围内的数字作为节点,记录每个节点的dp值。

计算dp_i时查询[A_i-D,A_i+D]范围内的最大值,取最大值+1作为dp_i(注意dp_i最小是1)。

再将线段树内数字A_i对应点改为dp_i

最后求线段树内所有点的最大值。

另外,输入的每个A_i都要加上5\times 10^5,保证A_i为正整数,方便线段树处理。

代码

#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 20, INF = 0x3f3f3f3f;

struct segment{
    struct node{
        int l = 0, r = 0, mx = 0;
    };
    vector<node> tr;

    segment(int n){
        tr.resize(n << 2);
        build(1, 1, n);
    }

    void pushup(int u){
        tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
    }

    void build(int u, int l, int r){
        if (l == r)
        {
            tr[u] = { l,l,-INF };
            return;
        }
        tr[u] = { l,r,-INF };
        int mid = l + r >> 1;
        build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }

    void modify(int u, int x, int val){
        if (tr[u].l == tr[u].r && tr[u].l == x){
            tr[u].mx = val;
            return;
        }
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, val);
        else modify(u << 1 | 1, x, val);
        pushup(u);
    }

    int query(int u, int x, int y){
        if (x <= tr[u].l && y >= tr[u].r) return tr[u].mx;
        int mid = tr[u].l + tr[u].r >> 1;
        int res = -INF;
        if (x <= mid) res = query(u << 1, x, y);
        if (y > mid)  res = max(res, query(u << 1 | 1, x, y));
        return res;
    }
};

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, d;
    cin >> n >> d;
    segment seg(N);
    vector<int> a(n + 5, 0), dp(n + 5, 0);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] += 500000;
    }
    for (int i = 1; i <= n; i++)
    {
        dp[i] = max(1, seg.query(1, a[i] - d, a[i] + d) + 1);
        seg.modify(1, a[i], dp[i]);
    }
    cout << seg.query(1, 1, N) << endl;
    return 0;
}

F - Product Equality

大意

给定n个整数A_1,A_2,\cdots,A_n

求满足以下条件的整数(i,j,k) 的三元组的个数:

  • 1 \le i,j,k \le n
  • A_i\times A_j=A_k

注意\color{red} {1 \le A_i \le 10^{1000}}

思路

如果不看那行红字显眼包,可以先用桶排建立权值数组cnt,再花O(n^2)枚举i,j,计算A_i\times A_j,对应的k即有cnt_{A_i\times A_j}个。

但由于A_i很大,最多有1001位,A_i\times A_j的计算变得棘手。

为了避免大数的计算,我们可以进行取模(利用同余定理)。

我们将模数设为998244353之类的质数,那么取模后的结果就完全可以进行相乘,然后按上面的方法做。

但是由于相乘的结果个数最大为10^6,可能导致哈希冲突,因此需要取多个模数。

代码

下面代码使用998244353,954169327,906097321进行取模。

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
typedef long long LL;
const LL mod1 = 998244353, mod2 = 954169327, mod3 = 906097321;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<string> a(n);
    for(auto &x: a) cin >> x;
    
    auto getmod = [&](string s, LL mod) -> LL{
        LL res = 0;
        for(auto& i: s){
            res = res * 10 + (i - '0');
            res %= mod;
        }
        return res;
    };

    vector<LL> b1(n), b2(n), b3(n);
    unordered_map<LL, LL> cnt1, cnt2, cnt3;
    for(int i = 0; i < n; i++){
        b1[i] = getmod(a[i], mod1);
        b2[i] = getmod(a[i], mod2);
        b3[i] = getmod(a[i], mod3);
        cnt1[b1[i]]++, cnt2[b2[i]]++, cnt3[b3[i]]++;
    }
    LL ans = 0;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++){
            LL k1 = (b1[i] * b1[j]) % mod1, k2 = (b2[i] * b2[j]) % mod2, k3 = (b3[i] * b3[j]) % mod3;
            LL t1 = 0, t2 = 0, t3 = 0;
            if(cnt1.count(k1)) t1 = cnt1[k1];
            if(cnt2.count(k2)) t2 = cnt2[k2];
            if(cnt3.count(k3)) t3 = cnt3[k3];
            ans += min(t1, min(t2, t3));
        }
    cout << ans << endl;
    return 0;
}

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值