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;
}

浙公网安备 33010602011771号