AtCoder Beginner Contest 383 Solution

A - Humidifier 1 (abc383 A)

题目大意

有一个加湿器,一开始里面没有水。给定N个时间点Ti,在每个时间点加Vi的水,且每个单位时间会流失单位1的水,问最后一次加水之后加湿器里剩多少水。

解题思路

输入是按时间顺序的,那么记录一个上次加水之后的剩余水量,模拟即可。

代码

#define rep(i,n) for(int i=0;i<(int)(n);i++)
int t[105], p[105];
void solve()
{
    int n;
    cin >> n;
    int las = 0;
    rep(i, n) {
        cin >> t[i] >> p[i];
        if (i) las -= t[i] - t[i - 1];
        las = max(0, las);//有可能相隔时间太长导致变成负数
        las += p[i];
    }
    cout << las;
}

B - Humidifier 2 (abc383 B)

题目大意

给定一个地图,#是墙,.是地板。要求在地板上放两台加湿器,加湿器的范围是曼哈顿距离下的D以内,求最多有多少地板可以受加湿器影响。

解题思路

纯暴力,地图只有100*100,枚举两台加湿器的坐标然后再暴力统计多少地板受影响了。

代码

#define rep(i,n) for(int i=0;i<(int)(n);i++)
string s[105];
void solve()
{
    int n, m, d;
    cin >> n >> m >> d;
    rep(i, n)
        cin >> s[i];
    int ans = 0;
    rep(i1, n) rep(j1, m) rep(i2, n) rep(j2, m) {
        if (s[i1][j1] == '#' || s[i2][j2] == '#') continue;
        if (i1 == i2 && j1 == j2) continue;
        int cnt = 0;
        rep(i, n) rep(j, m) {
            if (s[i][j] == '#') continue;
            if (abs(i - i1) + abs(j - j1) <= d || abs(i - i2) + abs(j - j2) <= d)
                cnt++;
        }
        ans = max(ans, cnt);
    }
    cout << ans;
}

C - Humidifier 3 (abc383 C)

题目大意

与上题类似,本题地图中H表示加湿器的位置,且影响的范围变成从加湿器出发向四联通方向走D步能够到达的地板,问有多少地板受加湿器影响了。

解题思路

经典的多起点BFS,一开始把加湿器的距离都设成0压进队列里,然后正常bfs就好。

代码

#define rep(i,n) for(int i=0;i<(int)(n);i++)
const int maxn = 1005;
const int dx[] = { 1,0,-1,0 }, dy[] = { 0,1,0,-1 };
int dis[maxn][maxn];
string s[maxn];
void solve()
{
    int n, m, d;
    cin >> n >> m >> d;
    rep(i, n) cin >> s[i];
    queue<pii> q;
    rep(i, n) rep(j, m) {
        dis[i][j] = 1e9 + 7;
        if (s[i][j] == 'H') {
            q.push({ i,j });
            dis[i][j] = 0;
        }
    }
    while (!q.empty()) {
        auto t = q.front();
        q.pop();
        rep(i, 4) {
            int x = t.fi + dx[i], y = t.se + dy[i];
            if (x < 0 || x >= n || y < 0 || y >= m || dis[x][y] <= dis[t.fi][t.se] + 1 || s[x][y] == '#') continue;
            dis[x][y] = dis[t.fi][t.se] + 1;
            q.push({ x,y });
        }
    }
    int ans = 0;
    rep(i, n) rep(j, m) {
        if (dis[i][j] <= d)
            ans++;
    }
    cout << ans;
}

D - 9 Divisors (abc383 D)

题目大意

给定N,问N以内有多少数字有刚好9个因数。

解题思路

其实看样例200以内的数就能总结出结论。

9个因数要么是两个质数平方的乘积,要么是一个质数的八次方。

质数的八次方不用多解释,0次方到8次方都是因数,一共9个。

两个质数平方的乘积,设质数为a和b,那么因数有\(1,a,b,a*b,a^2,b^2,a^2*b,a*b^2,a^2*b^2\)刚好9个。

接下来有两种做法:

1、看样例给出了最大值下的解,只有4e6个,那么显然枚举是可以的,直接枚举两个质数,然后乘起来超过N了直接break就好。

代码一

#include<bits/stdc++.h>

using namespace std;

long long n;
int p[2001000], c;
bool fl[2001000];
const int N=2e6;
int main(){
	cin>>n;
	for(int i=2;i<=N;i++)if(!fl[i]){
		p[++c]=i;
		for(int j=i*2;j<=N;j+=i)fl[j]=1;
	}
	int ans=0;
	for(int i=1;i<=c;i++)for(int j=i+1;j<=c;j++){
		long long e=1ll*p[i]*p[j];
		if(e>N)break;
		if(e*e<=n)ans++;
	}
	for(int i=1;i<=c;i++){
		long long z=1ll*p[i]*p[i]*p[i]*p[i]*p[i]*p[i]*p[i]*p[i];
		if(z>n)break;
		ans++;
	}
	printf("%lld", ans);
	return 0;
}

2、把素数的平方塞进set里,枚举一个素数,通过upperbound找出满足条件的最大的另一个素数,然后减一下就是当前素数合法的配对数量,加进ans里就好。

代码二

#define rep(i,n) for(int i=0;i<(int)(n);i++)
int p[2000005];
void solve()
{
    ll n;
    cin >> n;
    ll ans = 0;
    int cnt = 0;
    set<pair<ll, int> > s;//第二维记录一下是第几个素数
    rep(i, 1e6 + 5) {
        if (i >= 2 && !p[i]) {
            s.insert({ 1ll * i * i,++cnt });
            if (i <= 100 && 1ll * i * i * i * i * i * i * i * i <= n)
                ans++;
            for (ll j = i;j < 1e6 + 5;j += i) {
                p[j] = 1;
            }
        }
    }
    for (auto x : s) {
        //其实这里面可以加点break优化,但是已经是log级别了,无所谓
        ll y = n / x.fi;
        auto it = s.upper_bound({ y,1e9 + 7 });
        if (it == s.begin()) continue;
        it--;
        ans += max(0, it->se - x.se);
    }
    cout << ans;
}

E - Sum of Max Matching (abc383 E)

题目大意

给定一张简单有权无向图,一条路径的权重为这条路径上所有边权的最大值,定义函数f(x,y)表示xy两点之间所有可能路径的最小权重。

给定两个长度为K的数组A和B,AB之间数不同,求把A和B两个数组里的数两两配对,配对后求f函数和的最小值。

形式化的说:重新排列B,使\(∑^K_{i=1}f(A_i,B_i)\)最小,输出这个值。

解题思路

前置知识:最小生成树

这种路径的权重,明显是最小生成树。

任意两点的配对值肯定是最小生成树中这两点之间唯一路径的权重。

回想克鲁斯卡尔的过程,我们从小到大依次连边,每次将两个连通块连在一起。
由于是从小到大连边的,那么这次连边时,两个连通块里任意两点的配对值就是当前连的这条边的边权。(因为两个连通块里任意两点间的路径必定经过当前边,并且当前边的边权一定是路径中最大的)

那么问题就变成在克鲁斯卡尔的过程中,我需要知道两个连通块中有多少对点是在AB中的。

可以维护一个带权并查集,可以开两个数组记录当前连通块中有多少点在数组A中,有多少点在数组B中,比较直观。

一个写起来更简便的方法是,其实我们不关心有多少在数组A中,多少在数组B中,我们只关心有多少多出来的A数组点等待配对,或者是多少数组B中的点等待配对,那么我们可以把两个数组合并成一个数组,值为两数组之差,如果为正那么说明他需要B数组配对,如果为负那么说明需要A数组配对。

代码

const int maxn = 200005;
int n, m, k;
int fa[maxn], cnt[maxn];
pair<int, pair<int, int> > e[maxn];
ll ans = 0;
int A[maxn], B[maxn];
int findfa(int x) {
    if (fa[x] == x) return x;
    else return fa[x] = findfa(fa[x]);
}
void solve()
{
    cin >> n >> m >> k;
    rep(i, m) {
        cin >> e[i].se.fi >> e[i].se.se >> e[i].fi;
        e[i].se.fi--;
        e[i].se.se--;
    }
    for (int i = 0;i < n;i++) fa[i] = i;
    sort(e, e + m);
    //下面如果是使用两个数组A和B记录每个连通块中在A和B的数量,那么cnt其实等同于A-B
    rep(i, k) {
        int x;
        cin >> x;
        x--;
        cnt[x]++;
    }
    rep(i, k) {
        int x;
        cin >> x;
        x--;
        cnt[x]--;
    }
    rep(i, m) {
        int w = e[i].fi, x = e[i].se.fi, y = e[i].se.se;
        x = findfa(x);
        y = findfa(y);
        if (x == y) continue;

        if ((cnt[x] > 0 && cnt[y] < 0) || (cnt[x] < 0 && cnt[y]>0)) {
            int d = min(abs(cnt[x]), abs(cnt[y]));
            ans += 1ll * w * d;
        }
        cnt[x] += cnt[y];
        fa[y] = x;
    }
    cout << ans;
}
posted @ 2024-12-08 00:26  ~清渠~  阅读(33)  评论(0编辑  收藏  举报