USACO 2024 Season
2023DEC
Silver
Bovine Acrobatics
模拟的思路很简单:从最重的牛开始,依次放入奶牛塔中。
考虑优化,使用 set
快速维护有多少相同的堆。
// Title: Bovine Acrobatics // Source: USACO23DEC Silver // Author: Jerrywang #include <bits/stdc++.h> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cout<<#x<<":"<<x<<endl; const int N=200010; using namespace std; int n, m, k; struct node { int w, a; bool operator <(node b) const { return w>b.w; } } a[N]; int main() { #ifdef Jerrywang freopen("E:/OI/in.txt", "r", stdin); #endif scanf("%d%d%d", &n, &m, &k); rep(i, 1, n) scanf("%d%d", &a[i].w, &a[i].a); sort(a+1, a+n+1); set<node> S; ll res=0; S.insert({INT_MAX, m}); rep(i, 1, n) { int W=a[i].w, A=a[i].a, towers=0; // 修改了多少塔 while(S.size() && A) { if(S.begin()->w<W+k) break; int cur=S.begin()->a; // 当前塔重复数量 if(A>=cur) { towers+=cur; A-=cur; S.erase(S.begin()); } else { towers+=A; node t={S.begin()->w, cur-A}; A=0; S.erase(S.begin()); S.insert(t); } } if(towers) S.insert({W, towers}); res+=towers; } printf("%lld", res); return 0; }
Cycle Correspondence
题面有点抽象。首先,如果有的编号两头牛都没用,那么这头牛当然可以有两个相同的编号。
剩下的问题就是如何旋转或翻转这两个序列,使得有尽可能多的 \(a_i=b_i\)。这可以通过算贡献的思想解决。先不考虑翻转,看看对于相同的 \(x\) 在 \(a,b\) 中出现的位置,就知道要旋转多少“角度”才能使得两个 \(x\) 对在一起。通过累加,就能知道旋转哪个“角度”最优了。翻转的情况只需将数组翻转后再算一遍。
// Title: Cycle Correspondence // Source: USACO23DEC Silver // Author: Jerrywang #include <bits/stdc++.h> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cout<<#x<<":"<<x<<endl; const int N=500010; using namespace std; int n, k, a[N], b[N], vis[N], pos[N], cnt[N]; int main() { #ifdef Jerrywang freopen("E:/OI/in.txt", "r", stdin); #endif scanf("%d%d", &n, &k); rep(i, 1, k) scanf("%d", a+i), vis[a[i]]=1; rep(i, 1, k) scanf("%d", b+i), vis[b[i]]=1, pos[b[i]]=i; int res=0, mx=0; rep(i, 1, n) res+=(!vis[i]); rep(i, 1, k) { int j=pos[a[i]]; if(!j) continue; int d=(i>=j?i-j:i-j+k); cnt[d]++; } rep(i, 0, n) mx=max(mx, cnt[i]), cnt[i]=0; reverse(a+1, a+k+1); rep(i, 1, k) { int j=pos[a[i]]; if(!j) continue; int d=(i>=j?i-j:i-j+k); cnt[d]++; } rep(i, 0, n) mx=max(mx, cnt[i]), cnt[i]=0; printf("%d", res+mx); return 0; }
Gold
Flight Routes
看来出题人是 Taylor 粉。
题目中明确说了,单向直飞航班一定是 \((i,j)|i<j\)。那么可以观察到线索:\((i,i+1)\) 的奇偶性就是 \((i,i+1)\) 是否有单向直飞航班。
有了这一点就好做了。倒序枚举 \(i\),然后枚举 \(j\),计算 \((i,j)\) 是否有单向直飞航班。枚举转折点 \(k|i<k<j\),由于现在已经求得了 \((i,k)\) 是否有单向直飞航班,又已知 \((k,j)\) 的航线奇偶性,就可以知道所有中途需要换乘的 \((i,j)\) 航线奇偶性。如果刚才求得的奇偶性不同于输入的,则 \((i,j)\) 有单向直飞航班。
// Title: Flight Routes // Source: USACO23DEC Gold // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=755; using namespace std; int n, par[N][N], g[N][N]; int main() { #ifdef Jerrywang freopen("in.txt", "r", stdin); #endif scanf("%d", &n); rep(i, 1, n) rep(j, i+1, n) scanf("%1d", &par[i][j]); int res=0; for(int i=n-1; i; i--) { if(par[i][i+1]) g[i][i+1]=1, res++; rep(j, i+2, n) { int sum=0; rep(k, i+1, j-1) if(g[i][k]) sum^=par[k][j]; if(sum!=par[i][j]) g[i][j]=1, res++; } } printf("%d", res); return 0; }
Minimum Longest Trip
首先原图是一个 DAG,果断想到拓扑排序,先找到拓扑序。
然后倒序枚举拓扑序上的每个点 \(u\)。然后找到 \(u\) 的走得最长且标签最小的出边,在从这些出边走出的路径中选字典序最小的一条。果断想到倍增哈希。
// Title: Minimum Longest Trip // Source: USACO23DEC Gold // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll unsigned long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=200010, lg=18, inf=INT_MAX, B=13331; using namespace std; int n, m, q[N], hh=1, tt, deg[N]; vector<pii> g[N]; int d[N], to[N][lg+1]; ll pw[N], sum[N], h[N][lg+1]; int main() { #ifdef Jerrywang freopen("in.txt", "r", stdin); #endif scanf("%d%d", &n, &m); pw[0]=1; rep(i, 1, n) pw[i]=pw[i-1]*B; rep(i, 1, m) { int u, v, l; scanf("%d%d%d", &u, &v, &l); g[u].push_back({v, l}), deg[v]++; } rep(i, 1, n) if(!deg[i]) q[++tt]=i; while(hh<=tt) { int u=q[hh++]; for(auto [v,l]:g[u]) if(!--deg[v]) q[++tt]=v; } for(int i=n; i; i--) { int u=q[i], mn=inf, w=0; for(auto [v,l]:g[u]) d[u]=max(d[u], d[v]+1); for(auto [v,l]:g[u]) if(d[u]==d[v]+1) mn=min(mn, l); for(auto [v,l]:g[u]) if(d[u]==d[v]+1 && l==mn) { if(!w) {w=v; continue;} int vv=v, ww=w; for(int i=lg; i>=0; i--) { if(to[vv][i] && h[vv][i]==h[ww][i]) vv=to[vv][i], ww=to[ww][i]; } if(h[vv][0]<h[ww][0]) w=v; } if(g[u].size()) sum[u]=sum[w]+mn; to[u][0]=w, h[u][0]=mn; rep(i, 1, lg) to[u][i]=to[to[u][i-1]][i-1], h[u][i]=h[u][i-1]*pw[1<<i-1]+h[to[u][i-1]][i-1]; } rep(i, 1, n) printf("%d %lld\n", d[i], sum[i]); return 0; }
Haybale Distribution
考虑 \(y=0\) 和 \(y=10^6\) 的极端情况,这两种情况下都会有很大浪费。猜测这是个单峰函数,想到三分。
预处理前缀和,在函数求值时,只需要二分一下。
// Title: Haybale Distribution // Source: USACO23DEC Gold // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=200010; using namespace std; int n, x[N], a, b; ll sum[N]; ll f(ll X) { int i=lower_bound(x+1, x+n+1, X)-x-1; return a*(i*X-sum[i])+b*(sum[n]-sum[i]-(n-i)*X); } int main() { #ifdef Jerrywang freopen("in.txt", "r", stdin); #endif scanf("%d", &n); rep(i, 1, n) scanf("%d", x+i); sort(x+1, x+n+1); rep(i, 1, n) sum[i]=sum[i-1]+x[i]; int T; scanf("%d", &T); while(T--) { scanf("%d%d", &a, &b); int l=0, r=1e6; while(r-l>3) { int m1=l+(r-l)/3, m2=r-(r-l)/3; if(f(m1)>f(m2)) l=m1; else r=m2; } ll res=LLONG_MAX; rep(m, l, r) res=min(res, f(m)); printf("%lld\n", res); } return 0; }
2024JAN
Silver
Cowmpetency
线段树可以有效减少思维含量。建议评分:蓝。
设
则 FJ 的限制 \((i, j)\) 可以表示为 \(x\ge y\) 并且 \(x<a_j\)。
将所有限制按 \(i\) 从小到大排序后,对每个限制 \((i, j)\) 执行以下流程。
- \(x<y\)。如果 \(1\sim i\) 全部填完了,则无解。否则,为了字典序最小,找到最靠近 \(i\) 的。没填的下标 \(pre(i)\),\(a_{pre(i)}\leftarrow y\),\(x\leftarrow y\)。
- \(a_j\) 已经填数但 \(a_j\le x\)。一定无解。
- \(a_j\) 没有填数。如果 \(1\sim i\) 全都没有填数,\(a_j\leftarrow 2\),不然 \(a_j\leftarrow x+1\)。
然后扫描一遍整个 \(a\) 数组,如果 \(a_i>c\) 则无解,如果 \(a_i\) 没有填则 \(a_i\leftarrow 1\)。
最后复查一遍,输出答案。线段树需要支持单点修改、区间查询最大值。
// Title: Cowmpetency // Source: USACO24JAN Silver // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cout<<#x<<":"<<x<<endl; const int N=300010; using namespace std; int n, T, C, a[N], pre[N]; pii q[N]; struct node { int l, r, x; } t[N<<4]; #define lc p<<1 #define rc p<<1|1 void build(int p, int l, int r) { t[p]={l, r, a[l]}; if(l==r) return; int m=l+r>>1; build(lc, l, m), build(rc, m+1, r); t[p].x=max(t[lc].x, t[rc].x); } int query(int p, int l, int r) { if(l<=t[p].l && t[p].r<=r) return t[p].x; int m=t[p].l+t[p].r>>1, res=0; if(l<=m) res=max(res, query(lc, l, r)); if(r>m) res=max(res, query(rc, l, r)); return res; } void modify(int p, int i, int x) { if(t[p].l==t[p].r) {t[p].x=x; return;} int m=t[p].l+t[p].r>>1; if(i<=m) modify(lc, i, x); else modify(rc, i, x); t[p].x=max(t[lc].x, t[rc].x); } #define err {puts("-1"); return;} void solve() { scanf("%d%d%d", &n, &T, &C); rep(i, 1, n) { scanf("%d", a+i); if(a[i]) pre[i]=pre[i-1]; else pre[i]=i; } build(1, 1, n); rep(i, 1, T) scanf("%d%d", &q[i].F, &q[i].S); sort(q+1, q+T+1); rep(k, 1, T) { int i=q[k].F, j=q[k].S; int x=query(1, 1, i), y=query(1, i+1, j-1); if(x<y) { if(!pre[i]) err x=a[pre[i]]=y, modify(1, pre[i], y); } if(a[j] && a[j]<=x) err if(!a[j]) a[j]=x?x+1:2, modify(1, j, a[j]); } rep(i, 1, n) { if(a[i]>C) err if(!a[i]) a[i]=1, modify(1, i, a[i]); } rep(k, 1, T) { int i=q[k].F, j=q[k].S; int x=query(1, 1, i), y=query(1, i+1, j-1); if(!(x>=y && a[j]>x)) err } rep(i, 1, n) printf("%d%c", a[i], " \n"[i==n]); } int main() { #ifdef Jerrywang freopen("in.txt", "r", stdin); #endif int T; scanf("%d", &T); while(T--) solve(); return 0; }
Cowlendar
很有意思的数学题。
一个很显然的结论:将原序列去重不会影响答案。
本题的核心是枚举,考虑如何减少枚举量。因为 \(a_i \bmod L\) 最多有 \(3\) 个不同值,suo'yi如果 \(L\) 是合法的,由鸽巢原理得 \(a_1\sim a_4\) 中一定能找到 \(a_i\equiv a_j\pmod L\)。
移项得 \(a_i-a_j\equiv 0\pmod L\),因此 \(L|a_i-a_j\)。枚举 \(1\le i,j\le 4\),再枚举 \(a_i-a_j\) 的所有因数,就一定能枚举到所有可能的 \(L\)。然后验证即可。
如果去重后还剩 \(\le 3\) 个数,令 \(x=\min \lfloor a/4 \rfloor\),则 \(L\) 可以取 \(1\sim x\) 任意一数。
// Title: Cowlendar // Source: USACO24JAN Silver // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cout<<#x<<":"<<x<<endl; const int N=10010; using namespace std; int n; ll a[N]; vector<ll> d; void Divisor(ll x) { d.clear(); for(ll i=1; i*i<=x; i++) if(x%i==0) { d.push_back(i); if(i*i!=x) d.push_back(x/i); } } bool ok(ll x) { set<ll> S; rep(i, 1, n) { if(x>a[i]/4) return 0; S.insert(a[i]%x); if(S.size()>3) return 0; } return 1; } int main() { #ifdef Jerrywang freopen("in.txt", "r", stdin); #endif scanf("%d", &n); rep(i, 1, n) scanf("%lld", a+i); sort(a+1, a+n+1); n=unique(a+1, a+n+1)-a-1; if(n<=3) { ll x=a[1]/4; printf("%lld", x*(x+1)/2); return 0; } set<ll> res; rep(i, 1, 4) rep(j, i+1, 4) { ll delta=abs(a[i]-a[j]); Divisor(delta); for(ll x:d) if(ok(x)) res.insert(x); } ll s=0; for(ll x:res) s+=x; printf("%lld", s); return 0; }
2024FEB
Silver
Target Practice II
考虑拆分未知量(最远奶牛之间的最小距离),其实就是要求最上面和最下面两头奶牛的位置。
将矩形的四个顶点分类:
- 左上左下两个点;
- 右上;
- 右下。
将奶牛按照斜率的正负分开处理。斜率为正的奶牛向上打 1、3 类点,可以求得最下面的奶牛位置;斜率为负的奶牛向下打 1、2 类点,可以求得最上面的奶牛位置。
两种牛都使用了 1 类点。有一个贪心策略:靠上的 1 类点给斜率为正的奶牛向上打,靠下的 1 类点给斜率为负的奶牛向下打。这样就确定了两类奶牛打的点的点集。
下面只说最下面的奶牛位置如何求。另一个同理。
现在二分到最下面的奶牛纵坐标 \(y\)。然后处理出 \((0,y)\) 到达点集内点 \(i\) 的斜率 \(k_i\)。把所有点按照 \(k_i\) 从小到大排序。也把所有牛按照斜率从小到大排序。依次检查每头牛是否能匹配上对应的点。
注意要开 __int128
,避免浮点数除。
// Title: Target Practice II // Source: USACO24FEB Silver // Author: Jerrywang #include <bits/stdc++.h> #define x first #define y second #define pii pair<ll, ll> #define ll long long #define lll __int128 #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=100010; ll inf=1e18; using namespace std; int n, x1; vector<pii> a, b; vector<int> L, posi, nega; ll m; bool cmp1(int i, int j) { // return (a[i].y-m)/a[i].x<(a[j].y-m)/a[j].x; return (lll)(a[i].y-m)*a[j].x<(lll)(a[j].y-m)*a[i].x; } bool checkPositive() { vector<int> ord; rep(i, 0, (int)a.size()-1) ord.push_back(i); sort(posi.begin(), posi.end()); sort(ord.begin(), ord.end(), cmp1); rep(j, 0, (int)a.size()-1) { int i=ord[j]; // if(posi[j]>(a[i].y-m)/a[i].x) if(posi[j]*a[i].x>(a[i].y-m)) return 0; } return 1; } ll Positive() { ll l=-inf, r=inf, res; while(l<=r) { m=l+r>>1; if(checkPositive()) res=m, l=m+1; else r=m-1; } return res; } bool cmp2(int i, int j) { // return (b[i].y-m)/b[i].x<(b[j].y-m)/b[j].x; return (lll)(b[i].y-m)*b[j].x<(lll)(b[j].y-m)*b[i].x; } bool checkNegative() { vector<int> ord; rep(i, 0, (int)b.size()-1) ord.push_back(i); sort(nega.begin(), nega.end()); sort(ord.begin(), ord.end(), cmp2); rep(j, 0, (int)b.size()-1) { int i=ord[j]; // if(nega[j]<(b[i].y-m)/b[i].x) if(nega[j]*b[i].x<(b[i].y-m)) return 0; } return 1; } ll Negative() { ll l=-inf, r=inf, res; while(l<=r) { m=l+r>>1; if(checkNegative()) res=m, r=m-1; else l=m+1; } return res; } void solve() { a.clear(), b.clear(), L.clear(), posi.clear(), nega.clear(); scanf("%d%d", &n, &x1); rep(i, 1, n) { int y1, y2, x2; scanf("%d%d%d", &y1, &y2, &x2); a.push_back({x2, y1}); b.push_back({x2, y2}); L.push_back(y1), L.push_back(y2); } rep(i, 1, 4*n) { int k; scanf("%d", &k); if(k>0) posi.push_back(k); else nega.push_back(k); } if(posi.size()<a.size() || nega.size()<b.size()) { puts("-1"); return; } sort(L.begin(), L.end(), greater<int>()); int i; for(i=0; i<L.size() && posi.size()>a.size(); i++) a.push_back({x1, L[i]}); for(; i<L.size(); i++) b.push_back({x1, L[i]}); ll lo=Positive(), hi=Negative(); printf("%lld\n", hi-lo); } int main() { #ifdef Jerrywang freopen("in.txt", "r", stdin); #endif int T; scanf("%d", &T); while(T--) solve(); return 0; }
Test Tubes
这道题看上去毫无头绪,其实考察的是乱搞的勇气。
第一步想到去重。然后用三个栈维护三个试管 \(a,b,c\)。然后怎么搞呢?
先考虑一些平凡的情况。如果 \(a\) 的栈顶和 \(b\) 的栈顶相同,则根据两个栈的大小,选择消去 \(a\) 的栈顶或 \(b\) 的栈顶。如果 \(c\) 为空,则选择 \(a,b\) 中较大的一个栈,弹出栈顶,放入 \(c\) 试管中。如果 \(a,c\) 或 \(a,b\) 的栈顶相同,则可以消去 \(a\) 或 \(b\) 的栈顶。
不难发现,上述过程只有在 \(c\) 为空时才会加入,因此 \(c\) 的栈内元素个数不超过 \(1\)。
再考虑一些边界。如果 \(a,b\) 大小都为 \(1\) 且两个栈顶元素不同,则要把 \(c\) 清空,大功告成。如果 \(a,b\) 中有一个为空,假设 \(a\) 为空。如果 \(b\) 只有一个元素了,把 \(c\) 倒给 \(a\),大功告成。否则弹出 \(b\) 的栈顶给 \(a\)。\(b\) 为空的情况同理。
看上去上述策略都很符合直觉,但我感觉赛时很难写出代码。
// Title: Test Tubes // Source: USACO24FEB Silver // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=1000010; using namespace std; int n, p; pii res[N]; int cnt=0; void solve() { cnt=0; scanf("%d%d", &n, &p); stack<int> a, b, c; rep(i, 1, n) { int x; scanf("%1d", &x); if(a.empty() || x!=a.top()) a.push(x); } rep(i, 1, n) { int x; scanf("%1d", &x); if(b.empty() || x!=b.top()) b.push(x); } while(1) { if(a.size()==1 && b.size()==1 && a.top()!=b.top()) { if(c.empty()) break; if(a.top()==c.top()) res[++cnt]={3, 1}; if(b.top()==c.top()) res[++cnt]={3, 2}; break; } if(a.empty()) { if(b.size()==1) {res[++cnt]={3, 1}; break;} a.push(b.top()), b.pop(), res[++cnt]={2, 1}; } else if(b.empty()) { if(a.size()==1) {res[++cnt]={3, 2}; break;} b.push(a.top()), a.pop(), res[++cnt]={1, 2}; } else if(a.size() && b.size() && a.top()==b.top()) { if(a.size()>b.size()) a.pop(), res[++cnt]={1, 2}; else b.pop(), res[++cnt]={2, 1}; } else if(c.empty()) { if(a.size()>b.size()) c.push(a.top()), a.pop(), res[++cnt]={1, 3}; else c.push(b.top()), b.pop(), res[++cnt]={2, 3}; } else if(a.size() && a.top()==c.top()) a.pop(), res[++cnt]={1, 3}; else if(b.size() && b.top()==c.top()) b.pop(), res[++cnt]={2, 3}; } printf("%d\n", cnt); if(p>1) rep(i, 1, cnt) printf("%d %d\n", res[i].F, res[i].S); } int main() { #ifdef Jerrywang freopen("E:/OI/in.txt", "r", stdin); #endif int T; scanf("%d", &T); while(T--) solve(); return 0; }
2024OPEN
Silver
Bessie's Interview
赛时想到了并查集,正解是优先队列。
将前 \(k\) 头牛入队。每层循环内,找到等效的面试官编号,用 \(vec\) 临时记录;用 \(record\) 记录每一层的 \(vec\)。给接下来的 \(|vec|\) 头牛分配面试官。
第二问的本质是什么?假如面试官 \(i\) 可以面试 Bessie,某一层循环中 \(i,j\) 是等效的,可以相互替换,那么 \(j\) 就可以面试 Bessie。
考虑回溯求得答案。倒着循环层数 \(i\),有一位面试官 \(x\in record_i\) 又已知 \(x\) 可以面试 Bessie,则所有的 \(y\in record_i\) 都可以。
// Title: Bessie's Interview // Source: USACO24OPEN Silver // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<ll, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=300010; using namespace std; int n, k, res[N]; ll a[N]; priority_queue<pii, vector<pii>, greater<pii>> q; vector<vector<int>> record; int main() { #ifdef Jerrywang freopen("E:/OI/in.txt", "r", stdin); #endif scanf("%d%d", &n, &k); rep(i, 1, n) scanf("%lld", a+i); rep(i, 1, k) q.push({a[i], i}); int cur=k; while(1) { ll x=q.top().F; vector<int> vec; while(q.size() && q.top().F==x) vec.push_back(q.top().S), q.pop(); if(cur+vec.size()>n) { printf("%lld\n", x); for(int i:vec) res[i]=1; break; } record.push_back(vec); for(int i:vec) q.push({x+a[++cur], i}); } for(int i=record.size()-1; i>=0; i--) { bool ok=0; for(int j:record[i]) if(res[j]) ok=1; if(!ok) continue; for(int j:record[i]) res[j]=1; } rep(i, 1, k) printf("%d", res[i]); return 0; }
Painting Fence Posts
阅读题解前请确保完全理解题面。
首先有一个性质,想不到就没法做题了:假设有柱子 \((x,y_1),(x,y_2),(x,y_3),(x,y_4),\cdots\) 有着相同的横坐标,一定是形如 \((x,y_{2k-1}),(x,y_{2k})\) 的一对柱子中间连了栅栏,形如 \((x,y_{2k}),(x,y_{2k+1})\) 的一对柱子中间一定没连。对于有相同纵坐标的同理。
剩下的比较套路。连边后构成一个环图,dfs 找出环的顺序,破环成链,用差分维护区间修改即可。代码很难写。
在实现中,我将询问中的点与柱子一起处理。然后对于同一行(列)的点,找到相邻的两个柱子,并把其间的询问点一起连上。迭代器的处理也很繁琐。
如何处理重复的点也是个问题。各数组的含义详见代码注释。
// Title: Painting Fence Posts // Source: USACO24OPEN Silver // Author: Jerrywang #include <bits/stdc++.h> #define F first #define S second #define pii pair<int, int> #define ll long long #define rep(i, s, t) for(int i=s; i<=t; ++i) #define debug(x) cerr<<#x<<":"<<x<<endl; const int N=1000010; using namespace std; int n, nn, m; map<int, set<int>> row, col; // row,col: 相同行(列)上的点的列(行)坐标 map<pii, int> id; pii point[N]; // point[i]: 原始编号为i的点的坐标 // id[{x,y}]: 坐标为(x,y)的点的最小原始编号 vector<int> g[N]; int a[N], a_id[N], cnt[N]; ll d[N]; // a[i]: 环上编号为i的点的原始编号 // a_id[i]: 原始编号为i的点的环上编号 ll dis(pii a, pii b) { return abs(a.F-b.F)+abs(a.S-b.S); } void add(int u, int v) { g[u].push_back(v), g[v].push_back(u); } bool vis[N]; void dfs(int u) { a[++nn]=u; vis[u]=1; for(int v:g[u]) if(!vis[v]) dfs(v); } int main() { #ifdef Jerrywang freopen("E:/OI/in.txt", "r", stdin); #endif scanf("%d%d", &m, &n); rep(i, 1, n) { int x, y; scanf("%d%d", &x, &y); id[{x, y}]=i, point[i]={x, y}; row[x].insert(y), col[y].insert(x); } rep(i, 1, m) { int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2); point[n+i]={x1, y1}, point[n+m+i]={x2, y2}; if(!id[{x1, y1}]) id[{x1, y1}]=n+i, row[x1].insert(y1), col[y1].insert(x1); if(!id[{x2, y2}]) id[{x2, y2}]=n+m+i, row[x2].insert(y2), col[y2].insert(x2); } for(auto &[x,S]:row) { auto i=S.begin(); while(i!=S.end()) { for(; i!=S.end(); i++) if(id[{x, *i}]<=n) break; auto j=next(i), k=i; for(; j!=S.end(); j++) if(id[{x, *j}]<=n) break; if(j==S.end()) break; for(; k!=j; k++) add(id[{x, *k}], id[{x, *next(k)}]); i=next(j); } } for(auto &[y,S]:col) { auto i=S.begin(); while(i!=S.end()) { for(; i!=S.end(); i++) if(id[{*i, y}]<=n) break; auto j=next(i), k=i; for(; j!=S.end(); j++) if(id[{*j, y}]<=n) break; if(j==S.end()) break; for(; k!=j; k++) add(id[{*k, y}], id[{*next(k), y}]); i=next(j); } } dfs(1); rep(i, 1, nn) a[nn+i]=a[i], a_id[a[i]]=i; rep(i, 2, nn+nn) d[i]=dis(point[a[i]], point[a[i-1]]), d[i]+=d[i-1]; rep(i, 1, m) { int u=a_id[id[point[n+i]]], v=a_id[id[point[n+m+i]]]; ll d1, d2; if(d[u]<d[v]) { d1=d[v]-d[u], d2=d[nn+u]-d[v]; if(d1<d2) cnt[u]++, cnt[v+1]--; else cnt[v]++, cnt[nn+u+1]--; } else { d1=d[u]-d[v], d2=d[nn+v]-d[u]; if(d1<d2) cnt[v]++, cnt[u+1]--; else cnt[u]++, cnt[nn+v+1]--; } } rep(i, 1, nn+nn) cnt[i]+=cnt[i-1]; rep(i, 1, n) printf("%d\n", cnt[a_id[i]]+cnt[nn+a_id[i]]); return 0; }
本文作者:JosephusWang
本文链接:https://www.cnblogs.com/JosephusWang/p/18037038
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效