Codeforces Round #703 (Div. 2)
A(水题)
题目链接
⭐
题目:
给出\(n\)堆砖块,允许将第\(i\)堆砖块向\(i+1\)堆移动任意块,则询问所给砖块序列可否构成一个单调递增的序列
解析:
所给操作允许将前\(i\)个砖块以任意形式摆放,所以只需要当前砖块总数满足递增序列的最小要求即可
#include<bits/stdc++.h>
int main() {
int T;
int n;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
long long now = 0, cnt = 0, re = 0;
int t;
bool ok = true;
while (n--) {
scanf("%d", &t);
now += t;
if (!ok) continue;
if (re > now)
ok = false;
re += ++cnt;
}
printf("%s\n", ok ? "YES" : "NO");
}
}
B(水题)
题目链接
⭐
题目:
给出一组点的坐标,求出满足曼哈顿距离最小的点的个数
解析:
尽管是二维坐标,其实可以与一维坐标问题的求解是一样得,先将数组排序,考虑到某点到任意两点的距离为\(d=|x_1-x|+|x_2-x|\),在两点之间取得最小值,所以可以每次向两端取点,这样最后最小值就落在了中位数
如果是奇数则只有1种取法,偶数则为中位数的距离,乘法原理处理二维问题即可
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
long long x[maxn], y[maxn];
int main() {
int T;
int n;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 0; i < n; ++i)
scanf("%lld%lld", &x[i], &y[i]);
if (n & 1) {
printf("1\n");
continue;
}
sort(x, x + n); sort(y, y + n);
printf("%lld\n", (x[n / 2] - x[n / 2 - 1] + 1) * (y[n / 2] - y[n / 2 - 1] + 1));
}
}
C(二分)
题目链接
⭐⭐
题目:
给出数组长度\(n\), 最多进行不超过20次询问,每次询问\(l,r\),给出\([l,r]\)第二大值的坐标,求出最大值的位置
解析:
- 进行一次\([1,n]\)的询问,可以求得整个数组第二大值的坐标\(p\)
- 考虑到比第二大值只有与最大值在同一区间内时才会返回\(p\),所以进行第二次询问确定\(MAX\in[1,p-1]\)或者\([p+1,n]\)
- 不断二分另一点的位置,询问\([ans,p]\)或者\([p,ans]\),求得答案
注意:
如果\(p\)出现在1的位置,则不需要进行第二次询问,\(MAX\in[2,n]\)是肯定的,否则会出现[1,1]的询问错误
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
long long x[maxn], y[maxn];
bool ok;
int main() {
int n;
scanf("%d", &n);
int sec;
printf("? 1 %d\n\n", n);
fflush(stdout);
scanf("%d", &sec);
int mid;
int t;
int l, r;
if (sec != 1) {
printf("? 1 %d\n\n", sec);
fflush(stdout);
scanf("%d", &t);
}
if (sec == 1 || sec != t)
{
l = sec + 1, r = n;
while (r != l) {
mid = l + (r - l) / 2;
printf("? %d %d\n\n", sec, mid);
fflush(stdout);
scanf("%d", &t);
if (t == sec)
r = mid;
else
l = mid + 1;
}
}
else {
l = 1, r = sec - 1;
while (r != l) {
mid = l + (r - l + 1) / 2;
printf("? %d %d\n\n", mid, sec);
fflush(stdout);
scanf("%d", &t);
if (t == sec)
l = mid;
else
r = mid - 1;
}
}
printf("! %d", l);
}
D(中位数求解+二分)
题目链接
⭐⭐⭐
题目:
给出\(n,k\),以及一个长度为\(n\)的数组,求出一个长度至少为\(k\)的连续子序列值在排序后的中位数最大值
解析:
- 可以二分答案,假定结果一定大于等于\(x\)
- 对\(x\)的判定可以将数组所有的数看作两类,小于\(x\)的取-1,大于等于\(x\)的取1,求取整个数组前缀和,可以肯定前缀和是连续变化的,因此可以动态维护差值为k的最大值与最小值,判断最大值与最小值的差是否大于0即可
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int dat[maxn];
int sum[maxn];
int n, k;
bool check(int x) {
int mn = 0x3f3f3f3f, mx = -mn;
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + (dat[i] >= x ? 1 : -1);
if (i >= k) mn = min(sum[i - k], mn), mx = max(sum[i] - mn, mx);
}
return mx > 0;
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", &dat[i]);
int l = 1, r = n, mid;
while (l < r) {
mid = l + (r - l + 1) / 2;
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d", l);
}
E(思维+dijkstra)
题目链接
⭐⭐⭐⭐
题目:
给出一幅图,要求一次必须走两端路,且费用为两端路之和的平方,问从\(1\)点出发到各点的最小费用
解析:
由于必须走两端路,因此可以将路分为两段,其中前半段可以从任意点出发获得,后半段必须从中间点出发获得
- 方法一(400ns)
维护两个距离数组\(dis[N][51]\)与\(dis2[N]\),\(dis[i][j]\)代表若以\(i\)点为中间点,前半段权值为\(j\),对应的最小费用(最小二字可去除,主要是为了转移使用),\(dis2[i]\)代表以\(i\)为终点的最小费用
跑\(dijkstra\),假设给出\(node(state,u,pw,cost),e(u,v,w)\),state=0代表刚经过前半段,1代表刚经过后半段
\[前半段:dis[v][w]=min(dis[v][w],dis2[u]+w^2)\\
后半段:dis2[v]=min(dis2[v],dis[u][pw]+2\times pw\times w+w^2)\\
值得注意的是,前半段的权值可以转移到后半段,不一定要分开写
\]
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
typedef pair<int, int> P;
vector<P> e[maxn];
int dis[maxn][51], dis2[maxn];
int n, m;
void addedge(int u, int v, int w) {
e[u].push_back(P(v, w));
}
struct T {
int st, pre, pw, c;
bool operator<(const T& a)const {
return c > a.c;
}
};
void dij() {
priority_queue<T> q;
memset(dis, 0x3f3f3f3f, sizeof(dis));
memset(dis2, 0x3f3f3f3f, sizeof(dis2));
dis2[1] = 0;
q.push(T{ 0,1,0,0 });
while (!q.empty()) {
auto t = q.top();
q.pop();
if (t.st) {
if (dis[t.pre][t.pw] < t.c) continue;
for (auto& i : e[t.pre]) {
if (dis2[i.first] > t.c + 2 * t.pw * i.second + i.second * i.second) {
dis2[i.first] = t.c + 2 * t.pw * i.second + i.second * i.second;
q.push(T{ 0,i.first,0,dis2[i.first] });
}
}
}
else {
if (dis2[t.pre] < t.c) continue;
for (auto& i : e[t.pre]) {
if (dis[i.first][i.second] > t.c + i.second * i.second) {
dis[i.first][i.second] = t.c + i.second * i.second;
q.push(T{ 1,i.first,i.second,dis[i.first][i.second] });
}
}
}
}
for (int i = 1; i <= n; ++i)
if (dis2[i] == 0x3f3f3f3f) printf("-1 ");
else printf("%d ", dis2[i]);
}
int main() {
int a, b, c;
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c), addedge(b, a, c);
}
dij();
}
- 方法二(2000ns)(哈希)
构建新点新边, 每个点具有两个属性\((v,w)\) \(v\)代表原本对应点,\(w\)代表上一条边的权重,为0代表终点,非0代表中间点,跑\(dijkstra\)
注意:
开大数组范围
#include<bits/stdc++.h>
using namespace std;
const int maxn = 6e6 + 5;
typedef pair<int, int> P;
vector<P> e[maxn];
int dis[maxn];
int n, m;
void addedge(int u, int v, int w) {
e[u * 51].push_back(P(v * 51 + w, 0));
for (int i = 1; i <= 50; ++i)
e[u * 51 + i].push_back(P(v * 51, (w + i) * (w + i)));
}
void dij() {
priority_queue<P, vector<P>, greater<P>> q;
memset(dis, 0x3f3f3f3f, sizeof(dis));
dis[0] = 0;
q.push(P(0, 0));
while (!q.empty()) {
auto t = q.top();
q.pop();
if (dis[t.second] < t.first) continue;
for (auto& i : e[t.second]) {
if (dis[i.first] > dis[t.second] + i.second) {
dis[i.first] = dis[t.second] + i.second;
q.push(P(dis[i.first], i.first));
}
}
}
for (int i = 0; i < n; ++i)
if (dis[i * 51] == 0x3f3f3f3f) printf("-1 ");
else printf("%d ", dis[i * 51]);
}
int main() {
int a, b, c;
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &a, &b, &c);
--a, --b;
addedge(a, b, c), addedge(b, a, c);
}
dij();
}
努力变成更好的自己吧!