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. 进行一次\([1,n]\)的询问,可以求得整个数组第二大值的坐标\(p\)
  2. 考虑到比第二大值只有与最大值在同一区间内时才会返回\(p\),所以进行第二次询问确定\(MAX\in[1,p-1]\)或者\([p+1,n]\)
  3. 不断二分另一点的位置,询问\([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\)的连续子序列值在排序后的中位数最大值

解析:

  1. 可以二分答案,假定结果一定大于等于\(x\)
  2. \(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();
}
posted @ 2021-02-21 17:06  DreamW1ngs  阅读(117)  评论(0编辑  收藏  举报