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;
}
posted @ 2024-02-27 15:58  JosephusWang  阅读(109)  评论(0)    收藏  举报