Опять зима-зима-зима-зима-зима -Две ты|

JosephusWang

园龄:2年7个月粉丝:3关注:4

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

线段树可以有效减少思维含量。建议评分:蓝。

\[x=\max _{k=1}^i a_k \]

\[y=\max _{k=i+1}^{j-1} a_k \]

则 FJ 的限制 \((i, j)\) 可以表示为 \(x\ge y\) 并且 \(x<a_j\)

将所有限制按 \(i\) 从小到大排序后,对每个限制 \((i, j)\) 执行以下流程。

  1. \(x<y\)。如果 \(1\sim i\) 全部填完了,则无解。否则,为了字典序最小,找到最靠近 \(i\) 的。没填的下标 \(pre(i)\)\(a_{pre(i)}\leftarrow y\)\(x\leftarrow y\)
  2. \(a_j\) 已经填数但 \(a_j\le x\)。一定无解。
  3. \(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. 左上左下两个点;
  2. 右上;
  3. 右下。

将奶牛按照斜率的正负分开处理。斜率为正的奶牛向上打 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 中国大陆许可协议进行许可。

posted @   JosephusWang  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起