AtCoder Beginner Contest 203(Sponsored by Panasonic)
导读:
简单的题目,只说明题意,或者直接说明结论
下面的难题,会做详细的解释和证明
A Chinchirorin
题意为:若有两个执行相同,输出剩下的那一个,否则,输出0
int a[N];
void work()
{
for (int i = 0; i < 3; i ++ )
cin >> a[i];
for (int i = 0; i < 3; i ++ )
for (int j = i + 1; j < 3; j ++ )
if (a[i] == a[j])
{
cout << a[3 - i - j] << endl;
return;
}
cout << 0 << endl;
}
B AtCoder Condominium
将每层楼的门牌号都加起来
int n, k;
void work()
{
cin >> n >> k;
int sum = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= k; j ++ )
{
sum += i * 100 + j;
}
cout << sum << endl;
}
C Friends and Travel costs
理解:体力有多少,就对应着能走多元呗。体力没了,寸步难行
LL n, k;
struct Node{
LL a, b;
bool operator< (const Node &W) const {
if (a == W.a) return b > W.b;
return a < W.a;
}
}a[N];
void work()
{
cin >> n >> k;
for (int i = 0; i < n; i ++ )
scanf("%lld%lld", &a[i].a, &a[i].b);
sort(a, a + n);
LL len = 0;
for (int i = 0; i < n; i ++ )
{
if (k >= a[i].a) k = k + a[i].b;
}
cout << k << endl;
}
D Pond
第(K * K / 2 + 1)大的数最小化,可以抽象为最大值最小化问题,用二分来做。
我们二分一个数为x,如果大于x就令值为1,如果值小于或等于x就令值为0.然后求一个前缀和,暴力枚举每个kk矩形,O(1)查询是否存在小于(K*K/2+1)的情况,如果存在,r=mid,否则,l=mid
证明:如果出现小于情况,说明至少存在一个数使得当前满足当前kk的方格内出现了可以比x小或等于x的数,试的它是当前KK方格的第(KK/2+1)大的格子。而其他格子,最起码都要大于等于(K * K / 1+ 1)说明,他们的第(K * K /2)大的数字一定要大于当前的x,而当前的x是合法的,当然也可以更小。我们可以接着这样二分下去,最后可以确定一个恰好的x,使得它至少是一个KK区域的第(KK/2+1)大数,然后,其他的格子的第(K*K/2+1)大数都要大于x。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits.h>
#include <sstream>
#include <cctype>
#include <numeric>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <set>
//#pragma GCC optimize(2)
//#pragma GCC optimize(3, "Ofast", "inlin")
using namespace std;
#define ios ios::sync_with_stdio(false) , cin.tie(0)
#define x first
#define y second
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 810, INF = 0x3f3f3f3f, mod = 1e9 + 7, base = 131;
const double eps = 1e-6, PI = acos(-1);
int n, k;
int g[N][N];
int s[N][N];
int trick;
bool check(int mid)
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (g[i][j] > mid) s[i][j] = 1;
else s[i][j] = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
for (int i = 1; i <= n - k + 1; i ++ )
for (int j = 1; j <= n - k + 1; j ++ )
{
int l = i + k - 1, r = j + k - 1;
int sum = s[l][r] - s[i - 1][r] - s[l][j - 1] + s[i - 1][j - 1];
if (sum < trick) return true;
}
return false;
}
void work()
{
cin >> n >> k;
trick = k * k / 2 + 1;
int r = 0, l = 1e9;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
{
scanf("%d", &g[i][j]);
l = min(l, g[i][j]);
r = max(r, g[i][j]);
}
while (l < r)
{
int mid = l + r >> 1;
bool f = check(mid);
// printf("mid = %d, f = %d\n", mid, f);
if (f) r = mid;
else l = mid + 1;
}
cout << r << "\n";
}
int main()
{
//ios;
int T = 1;
// cin >> T;
while (T -- )
{
work();
}
return 0;
}
E White Pawn
题意为:初始时有一个白棋子在(0,n)的位置,在棋盘上散布着m个黑棋子。
下面是移动规则:
规则1. 若当前白棋子在位置(i,j), 若在位置(i + 1, j - 1)有黑子,则白子可以到达(i + 1, j - 1)这个位置,如果位置(i + 1, j + 1)有黑子,则白字可到达(i+ 1, j + 1)
规则2.如果位置(i + 1, j)没有黑子,则白字可以到达(i+ 1, j)
做法:我们可以看到n是1e9大的数字,而m是要给2e5的数字,所以,中间肯定有很多行是空白的。那么,根据规则2,中间的空白可以直接跳过。
设集合S是我们每一行可达位置。
若第i行是空行,则,Si-1 = Si
那么我们使用一个S,每次转移的时候,若是空格可以直接到,我们就不管了,我们就看那一列可以新添加,那一列不可达必须要去掉,用set来存储,可以自动帮我们去重,最后的答案就是set的size
若第i行不是空行,根据规则1,我们可以得到若是(i, j)这个位置有黑子,那么如果(i-1,j-1)或者(i-1,j+1)是可达的,但是(i-1,j-1)是不可达的,则Si-1可以达到Si的(i,j)位置。(错位的我们默认可以能是新添加的)放到a数组中
若位置(i,j)是黑棋子,而且(i-1,j-1)和(i-1,j+1)都是空的,但是(i-1,j)是空格,那么一定不能到达位置(i,j),我们用b来存储。
每次,将a插入S,每次将b从S中去掉。
下面是代码:
#include <bits/stdc++.h>
using namespace std;
int main(void){
int n,m; cin >> n >> m;
vector<pair<int, int> > v;
for (int i = 0; i < m; i ++ )
{
int x, y; scanf("%d%d", &x, &y);
v.push_back({x, y});
}
sort(v.begin(), v.end());
int l = 0, r = 0;
set<int> s;
vector<int> a, b;
s.insert(n);
while (l < m)
{
a.clear();
b.clear();
for(r = l; r < m && v[r].first == v[l].first; r ++ );
for (int i = l; i < r; i ++ )
{
int y = v[i].second;
if ((s.find(y - 1) != s.end() || s.find(y + 1) != s.end()))
a.push_back(y);
if ((s.find(y - 1) == s.end() && s.find(y + 1) == s.end()) && s.find(y) != s.end())
b.push_back(y);
}
for (int it : a) s.insert(it);
for (int it : b) s.erase(it);
l = r;
}
cout << s.size() << "\n";
}
F Weed
题意:设第一个人是女生,第二个人是男生。
题目中,女生,男生都可以做相应的操作
女生的操作:女生可以最多操作k次,每次可以除去任何一棵草。
男生的操作:设当前没被铲除的草的最高高度为H,男生可以铲除当前所有高度大于H/2的所有草。
题目要求:让我们求在男生操作次数最少的情况下,女生操作次数也要最小,将所有杂草都给铲除掉。
做法:DP求解
第一步无脑排序,因为草的排布对答案无影响,升序拍个序。
然后思考
dp[i][j]
的意义,从第一颗草开始铲除一直铲除到了当前的第i棵草,男生操作了j次,女生最少操作的次数为dp[i][j]
那么对于每次的dp[][j]
有两种做法,第一就是女生操作,那就是dp[i][j] = dp[i - 1][j] + 1
,第二就是男生操作,结果为
dp[i][j] = dp[i - 1][j -1]
我们去一个min。
再考虑边界问题,对于i=0的时候,dp[0][j]
都是0
对于i>=1的时候,dp[i][0] = i
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 200010
#define rep(i, n) for(int i = 0; i < n; ++ i)
int n, k;
int a[N];
int dp[N][31];
int b[N];
/*
题意:女的先选择至多k个草给消灭掉,然后剩下的交给男的来操作,男的每次
可以消灭比当前最高的草的二分之一还高的草都给消灭掉。
要求我们求出能将所有草给消灭掉的情况下,男孩操作最少,在男孩操作数相同
的情况下,要让女孩操作数达到最少。
dp[i][j]的含义是,从第0跟草消灭到了第i根草的时候,男孩操作了j次,女孩最少要操作的次数(dp[i][j]的值)
*/
int main(void)
{
cin >> n >> k;
rep(i, n) cin >> a[i];
sort(a, a + n);
int cur = 0;
//b数组表示,以当前位置i的草作为最高的草,可以消灭一个区间的草,这个区间的左区间为b[i],右区间为i
rep(i, n){
while (a[cur] <= (a[i] / 2)) cur ++;
b[i] = cur;
}
// rep(i, n) printf("%3d ", a[i]); puts("");
// rep(i, n) printf("%3d ", b[i]); puts("");
rep(i, n){
dp[i + 1][0] = dp[i][0] + 1;//如果只让女孩来消灭,那么优即可就要消灭几次
rep(j, 30){
//要么是男孩不操作,所以第二维为j+1,然后女孩操作,在消灭i的基础上+1消灭掉第i+1的草
//要么是女孩不操作,男孩在从b[i]的位置(可以消灭掉第i草的最早的位置)操作一次,j+1由j转移而来
dp[i + 1][j + 1] = min(dp[i][j + 1] + 1, dp[b[i]][j]);
}
}
rep(j, 31){
if (dp[n][j] <= k) {
cout << j << " " << dp[n][j] << endl;
return 0;
}
}
return 0;
}