2020-2021 ICPC, NERC, Southern and Volga Russian Regional Contest AGHIJM 题解
A. LaIS#
设 为到第 i 位的最长的 almost increasing 长度。可以发现,这个 的转移只有从 的地方转移过去,或者是找到一个最靠后的 k 使得 ,再从 找到一个 j 满足 。
那么,递推式为
那么可以用一个单调递减的队列来维护并找出 k。用主席树来维护 数组,下标 存值 。时间复杂度
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef long double ld;
#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl
template<typename Tp> IL void read(Tp &x) {
x=0; int f=1; char ch=getchar();
while(!isdigit(ch)) {if(ch == '-') f=-1; ch=getchar();}
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();}
x *= f;
}
int buf[42];
template<typename Tp> IL void write(Tp x) {
int p = 0;
if(x < 0) { putchar('-'); x=-x;}
if(x == 0) { putchar('0'); return;}
while(x) {
buf[++p] = x % 10;
x /= 10;
}
for(int i=p;i;i--) putchar('0' + buf[i]);
}
const int N = 500000 + 5;
struct PRETree {
int n, tot;
int rt[N];
int maxv[N << 5], ls[N << 5], rs[N << 5];
void init(int n) {
this -> n = n;
fill(rt, rt + n + 1, 0);
fill(maxv, maxv + (n << 5) + 1, 0);
fill(ls, ls + (n << 5) + 1, 0);
fill(rs, rs + (n << 5) + 1, 0);
}
int newnode() { return ++tot;}
void copynode(int u, int v) {
maxv[u] = maxv[v];
ls[u] = ls[v];
rs[u] = rs[v];
}
int upd(int pre, int L, int R, int pos, int v) {
int o = newnode();
copynode(o, pre);
if(L == R) {
maxv[o] = v;
return o;
}
int M = L+R >> 1;
if(pos <= M) ls[o] = upd(ls[pre], L, M, pos, v);
else rs[o] = upd(rs[pre], M+1, R, pos, v);
maxv[o] = max(maxv[ls[o]], maxv[rs[o]]);
return o;
}
int query(int o, int L, int R, int qL, int qR) {
if(qL <= L && R <= qR) {
return maxv[o];
}
int M = L+R >> 1;
int ans = 0;
if(qL <= M) ans = max(ans, query(ls[o], L, M, qL, qR));
if(M < qR) ans = max(ans, query(rs[o], M+1, R, qL, qR));
return ans;
}
int query_max(int posR, int aR) {
if(posR == 0) return 0;
return query(rt[posR], 1, n, 1, aR);
}
}lpr;
int n;
int a[N], dp[N];
int m;
int q[N];
int main() {
#ifdef LOCAL
freopen("A.in", "r", stdin);
#endif
int T; read(T);
while(T--) {
read(n);
lpr.init(n);
m = 0;
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++) {
dp[i] = lpr.query_max(i-1, a[i]) + 1;
if(m >= 1) {
int pos = lower_bound(q+1, q+m+1, i, [&](int x, int y) { return a[x] > a[y];}) - q - 1;
if(pos > 0) {
dp[i] = max(dp[i], lpr.query_max(q[pos]-1, a[i]) + 2);
q[m = pos + 1] = i;
}
else q[m = 1] = i;
}
else {
q[++m] = i;
}
lpr.rt[i] = lpr.upd(lpr.rt[i-1], 1, n, a[i], dp[i]);
}
int ans = 0;
// puts("dp[i] = ");
// for(int i=1;i<=n;i++) {
// dbg1(i); dbg2(dp[i]);
// }
for(int i=1;i<=n;i++) ans = max(ans, dp[i]);
printf("%d\n", ans);
}
return 0;
}
G. Hobbits#
非常简单的计算几何,模拟的时候为啥没读到?
维护一个最高的点, 然后判断线段是不是在眼睛到最高点这个向量左侧即可。记得判一下平行。
eps 开太小了导致本机能过的数据交上去 WA 了。
#include <bits/stdc++.h>
using namespace std;
typedef double db;
#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl
const db eps = 1e-8;
int dcmp(db x) {
if(fabs(x) < eps) return 0; else return x < 0 ? -1 : 1;
}
struct P {
db x, y;
P(db x=0.0, db y=0.0):x(x), y(y) {}
};
typedef P V;
IL V operator + (const V& a, const V& b) { return V(a.x + b.x, a.y + b.y);}
IL V operator - (const V& a, const V& b) { return V(a.x - b.x, a.y - b.y);}
IL V operator * (const V& a, db p) { return V(a.x * p, a.y * p);}
IL V operator / (const V& a, db p) { return V(a.x / p, a.y / p);}
IL bool operator < (const P& a, const P& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
}
IL bool operator == (const P& a, const P& b) {
return dcmp(a.x-b.x) == 0 && dcmp(a.y-b.y) == 0;
}
IL db Dot(const V& a, const V& b) { return a.x * b.x + a.y * b.y;}
IL db Length(const V& a) { return sqrt(Dot(a, a));}
IL db Angle(const V& a, const V& b) { return acos(Dot(a, b) / Length(a) / Length(b));}
IL db Cross(const V& a, const V& b) { return a.x*b.y - a.y*b.x;}
IL P GetLineIntersection(P p, V v, P q, V w) {
V u = p-q;
db t = Cross(w, u) / Cross(v, w);
return p + v * t;
}
IL bool OnLeft(P a, P b, P p) {
return dcmp(Cross(b - a, p - a)) > 0;
}
IL bool OnSegment(P p, P a1, P a2) {
return dcmp(Cross(a1-p, a2-p)) == 0 && dcmp(Dot(a1-p, a2-p)) < 0;
}
const int N = 200000 + 5;
int n, H;
P p[N];
int main() {
#ifdef LOCAL
freopen("G.in", "r", stdin);
#endif
scanf("%d%d", &n, &H);
for(int i=1;i<=n;i++) {
int xx, yy; scanf("%d%d", &xx ,&yy);
p[i] = P(xx, yy);
}
p[0] = P(p[n].x, p[n].y + H);
P high = p[n];
db ans = 0.0;
for(int i=n;i>=2;i--) {
if(!OnLeft(p[0], high, p[i])) high = p[i];
V v = p[i-1] - p[i];
if(dcmp(Cross(v, high - p[0])) == 0) { // parallel
if(OnLeft(p[0], high, p[i])) continue;
else ans += Length(v);
continue;
}
// not parallel
int b = 2;
P a = GetLineIntersection(p[0], high - p[0], p[i], p[i-1] - p[i]);
if(OnSegment(a, p[i-1], p[i])) {
ans += Length(a - p[i-1]);
}
else {
if(dcmp(Cross(high - p[0], p[i] - p[0])) >= 0 && dcmp(Cross(high - p[0], p[i-1] - p[0])) >= 0) continue;
else ans += Length(p[i] - p[i-1]);
}
}
printf("%.10lf\n", ans);
return 0;
}
H. K and Medians#
结论是满足以下两个条件就是 yes。
- 删后的序列中满足一个条件,存在某一个数在删后的序列中且它前面删了 个数,后面删了 个数。
以上。
#include <bits/stdc++.h>
using namespace std;
const int N = 200000 + 5;
int n, k, m;
int a[N];
bool check() {
for(int i=1;i<=m;i++) {
int delL = a[i] - i;
int delR = (n - a[i]) - (m - i);
if(delL >= (k-1) / 2 && delR >= (k-1) / 2) return true;
}
return false;
}
int main() {
int T; scanf("%d", &T);
while(T--) {
scanf("%d%d%d", &n, &k, &m);
for(int i=1;i<=m;i++) scanf("%d", a+i);
if((n-m) % (k-1) != 0) { puts("NO"); continue;}
puts(check() ? "YES" : "NO");
}
return 0;
}
I. Plane Tiling#
题意:给你两个向量 请恰好选 个不同点 ,使得平面上的所有点都能由下式表示出来。
做法:先把从 出发的向量 构成的平行四边形画出来。接着扩展这个平行四边形,铺满整个平面(可以自行脑补长边贴着长边,短边贴着短边)。然后可以发现整个平面很多点在平行四边形的顶点上。答案其实就等于单个平行四边形内不在平行四边形顶点的点的个数。尝试计算它,可以发现
不在平行四边形顶点上的点的个数 = 平行四边形内部的点的个数 + (平行四边形边上的点的个数 - 4) / 2(注:这里 /2 是因为计算答案的时候只能计算左边和上边。右边和下边会和平面上别的平行四边形算重。减去的是顶点)
引用 pick 定理,可以发现 答案 = 平行四边形面积 - 1 + 1 = 平行四边形面积。(注:这里 +1 是考虑 )
所以第一步先判断 是否等于 。如果不等于则无解,反之有解。
法一:接下来可以把平行四边形里面的所有点都输出出来,即平行四边形内部的点和位于上边、左边的点并去掉顶点。记得输出 。
法二:再试试分割 。设 ,那么最后的答案 。
先取 中的所有点,很显然它们每一个都无法相互表示出来。接下来考虑如何用 去扩展这些点。这一步可以把选出来的点矩阵沿着 轴正向每次平移 格共平移 次。这个即为答案。
证明:由于 无法构造出一个 使得 ,那么我们考虑在 的时候,是否会使得 重选或者漏选。由上式可得 。带入 相关式子得
而 ,确实是平移距离。
综上所述,证毕。
最终答案即为
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define IL inline
long long n;
long long dx1, dx2, dy1, dy2;
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b);}
IL ll absl(ll x) { return x < 0 ? -x : x;}
int main() {
#ifdef LOCAL
freopen("I.in", "r", stdin);
#endif
scanf("%lld", &n);
scanf("%lld%lld", &dx1, &dy1);
scanf("%lld%lld", &dx2, &dy2);
long long d = absl(dx1 * dy2 - dx2 * dy1);
if(d != n) return puts("NO"), 0;
ll dx = absl(gcd(dx1, dx2)), dy = absl(gcd(dy1, dy2));
ll m = n / dx / dy;
puts("YES");
for(int rx=0;rx<dx;rx++) {
for(int ry=0;ry<dy;ry++) {
for(int i=0;i<m;i++) {
printf("%lld %lld\n", i*dx+rx, 1ll * ry);
}
}
}
return 0;
}
J. Road Reform#
先做一次最小生成树,得到最小生成树 T。设做完后最大边权为 。
- 若 ,那么由于最小生成树又是最小瓶颈树,那么
- 若 ,那么从非最小生成树边与最小生成树最大边权 中找到离 k 最近的边。
不知道模拟的时候自己在干什么,可能在梦游?
M. Similar Sets#
题意:求出二分图中是否存在四元环。若存在,输出四元环在左边集合中的两个点的下标。左边集合大小 ,右边集合大小 ,最坏情况下可能会有 条边。
做法:第一步离散化。再考虑分治。设左边集合中度数大于等于 的点为大点,其余点为小点。枚举每一个左边集合中的点 ,对大点做一种操作,对小点做另一种操作。
- 若该点 度数为 大于等于 ,我们看这个点与其他左边集合中其他的点的关系。再设一个 数组,若右边集合中下标为 的点与点 相连,这个 ,反之 。这样每次枚举到一个大点,把这个点对应的 数组弄出来,然后对左边集合中剩余其他点判断是否有两个 为 。如果题目想要数据一直不匹配到四元环的话,单次的时间复杂度为 。由于如果找到了四元环可以直接跳出,所以如果数据一直找不到四元环的话,左边集合中的每两个大点,最多只会连接重复的一个右边集合中的点。那么最多只会有 个大点。那么这一步总体的最坏时间复杂度应为
- 若该点 度数为 小于 ,是个小点,由于上一步看过了所有大点和其他所有点的关系,这里看这个点与其他小点的关系即可。接下来我们把点 所连接的所有右边集合中的点组成的点对 推入 这个 vector 中。然后枚举完所有小点得到整个 vector 后,再看哪一个 vector 中某个数出现了两次。由于 ,时间复杂度应为 ,根据导数等高中知识,让越多的 取到 这个时间复杂度越大,所以最坏时间复杂度为 。
最终时间复杂度为 ,在 时取到较优秀的时间复杂度为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef long double ld;
typedef pair<int, int> pii;
#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl
template<typename Tp> IL void read(Tp &x) {
x=0; int f=1; char ch=getchar();
while(!isdigit(ch)) {if(ch == '-') f=-1; ch=getchar();}
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();}
x *= f;
}
int buf[42];
template<typename Tp> IL void write(Tp x) {
int p = 0;
if(x < 0) { putchar('-'); x=-x;}
if(x == 0) { putchar('0'); return;}
while(x) {
buf[++p] = x % 10;
x /= 10;
}
for(int i=p;i;i--) putchar('0' + buf[i]);
}
const int N = 100000 + 5;
const int M = N << 1;
int n, m, mm;
int tmp[M];
int pre[M];
int w[M];
vector<int> a[N];
vector<pair<int, int> > mp[M];
void solve() {
mm = 0;
read(n);
for(int i=1;i<=n;i++) {
a[i].clear();
int ki; read(ki);
for(int j=1;j<=ki;j++) {
int x; read(x);
a[i].push_back(x);
tmp[++mm] = x;
}
sort(a[i].begin(), a[i].end());
}
sort(tmp+1, tmp+1+mm);
m = unique(tmp+1, tmp+1+mm) - tmp - 1;
for(int i=1;i<=n;i++) {
for(int j=0;j<a[i].size();j++) {
a[i][j] = lower_bound(tmp+1, tmp+1+m, a[i][j]) - tmp;
}
}
int sz = sqrt(mm + 0.5);
// big -> others
bool ok = false;
for(int i=1;i<=n;i++) {
if(a[i].size() < sz) continue;
for(int j=0;j<a[i].size();j++) w[a[i][j]] = 1;
for(int j=1;j<=n;j++) if(i != j) {
if(j < i && a[j].size() >= sz) continue;
int cnt = 0;
for(int k=0;k<a[j].size();k++) {
if(w[a[j][k]]) cnt++;
if(cnt >= 2) { printf("%d %d\n", i, j); ok = true; break;}
}
if(ok) break;
}
for(int j=0;j<a[i].size();j++) w[a[i][j]] = 0;
if(ok) return;
}
// small -> small
for(int i=1;i<=n;i++) {
if(a[i].size() >= sz) continue;
for(int j=0;j<a[i].size();j++) {
for(int k=j+1;k<a[i].size();k++) {
mp[a[i][j]].push_back(mk(a[i][k], i));
}
}
}
for(int x=1;x<=m;x++) {
for(auto pp : mp[x]) {
int y = pp.first, p = pp.second;
if(pre[y]) {
printf("%d %d\n", p, pre[y]);
ok = true;
break;
}
pre[y] = p;
}
for(auto pp : mp[x]) {
int y = pp.first, p = pp.second;
pre[y] = 0;
}
if(ok) break;
}
for(int i=1;i<=m;i++) mp[i].clear();
if(ok) return;
puts("-1");
return;
}
int main() {
#ifdef LOCAL
freopen("M.in", "r", stdin);
#endif
int T; read(T);
while(T--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端