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;
}