The 4th Universal Cup. Stage 11: Grand Prix of Southeastern Europe

Preface

最近期末要补实验,同时一直在忙着写论文,导致很长一段时间没训练了

(实则不然,其实是沉迷云顶,每天下棋下的不知天地为何物了)

还有两周就是 ECF 了,最近应该会保持一周三训稍微找回点状态


A. Sum Game

一个显著的观察就是能选的数一定在 \(1\sim m-1\) 中,并且每个数至多选一次

同时不难发现 \((1,m-1),(2,m-2),\ldots\) 不能同时出现,因此真正能选的数本质只有 \(\lceil\frac{m-1}{2}\rceil\)

要让这些数的任意线性组合都凑不出 \(m\) 的倍数,显然可以选 \(1,2,4,\ldots\) 来构造最优解

因此 Bob 获胜的充要条件为 \(2^{n-1}\le \lceil\frac{m-1}{2}\rceil\)

#include<cstdio>
#include<iostream>
using namespace std;
long long t,n,m;
int main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld%lld",&n,&m);
        if (m==1) { puts("Ana"); continue; }
        bool ok=0; long long tmp=1; --n;
        while (n>0&&!ok)
        {
            tmp*=2LL;
            if (tmp>m/2LL) ok=1;
            --n;
        }
        puts(ok?"Ana":"Bob");
    }
    return 0;
}

B. AND Reconstruction

首先答案一眼具有可二分性,同时我们一眼可以按位考虑,把问题转化为只有 \(0/1\) 比特的情形

考虑如何检验 \(k\) 是否合法,先根据原序列构造出此时对应的 \(\{x_i\}\),不难发现 \(\{x_i\}\) 中的 \(1\) 能确定一段长为 \(k+2\) 的原序列的值

(除了这个 \(1\) 本身能确定原序列 \(k\) 个位置是 \(1\) 以外,同时还可以确定原序列左右两侧的数的值)

因此问题转化为一个检验若干个环上的区间能否 cover 环上的每个位置,直接差分处理一下即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,b,A[N],a[N];
inline bool check(CI k)
{
    if (k==1) return 1;
    static int d[N];
    for (RI i=0;i<=n;++i) d[i]=0;
    int cnt=0,flag=0;
    for (RI i=0;i<k;++i) cnt+=a[i];
    if (cnt==k) flag=1;
    for (RI i=1;i<n;++i)
    {
        cnt-=a[i-1];
        cnt+=a[(i+k-1)%n];
        if (cnt==k)
        {
            if (i+k<n) ++d[i-1],--d[i+k+1];
            else ++d[i-1],++d[0],--d[(i+k)%n+1];
        }
    }
    for (RI i=1;i<n;++i) d[i]+=d[i-1];
    if (flag)
    {
        ++d[n-1];
        for (RI i=0;i<=k;++i) ++d[i];
    }
    for (RI i=0;i<n;++i)
    if (d[i]==0) return 0;
    return 1;
}
int main()
{
    scanf("%d%d",&n,&b); int k=n;
    for (RI i=0;i<n;++i) scanf("%d",&A[i]);
    for (RI p=0;p<b;++p)
    {
        for (RI i=0;i<n;++i) a[i]=((A[i]>>p)&1);
        int l=1,r=k;
        while (l<=r)
        {
            int mid=l+r>>1;
            if (check(mid)) k=mid,l=mid+1; else r=mid-1;
        }
    }
    return printf("%d\n",k),0;
}

C. XOR-Excluding Sets

首先简单分析会发现这个题实际要求的是,对于每个数求和:有多少个不包含它的集合,满足该集合中所有数异或和为 \(0\)

考虑列举出所有异或和为 \(0\) 的集合,设其数量为 \(k\),我们可以通过观察发现以下结论,对于某个数:

  • 若其不出现在任意一个异或和为 \(0\) 的集合中,则它对答案的贡献为 \(k\)
  • 否则它一定恰好出现在 \(\frac{k}{2}\) 个异或和为 \(0\) 的集合中,因此它对答案的贡献为 \(\frac{k}{2}\)

为了统计答案,我们需要用线性基维护异或和为 \(0\) 的集合的数目,同时还要维护不出现在任意一个异或和为 \(0\) 的集合中的数的数目

满足后者条件的数的数量是 \(O(\log a_i)\) 级别的,因此可以暴力维护这么多个线性基,总复杂度 \(O(n\log^2 a_i)\)

#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int mod=1e9+7,inv_2=(mod+1)/2;
struct LB
{
    int base[65],free;
    inline LB(void)
    {
        free=0; for (RI i=0;i<60;++i) base[i]=0;
    }
    inline void insert(int x)
    {
        for (RI i=59;i>=0;--i)
        {
            if (((x>>i)&1)==0) continue;
            if (!base[i])
            {
                base[i]=x; return;
            }
            x^=base[i];
        }
        ++free;
    }
    inline bool query(int x)
    {
        for (RI i=59;i>=0;--i)
        {
            if (((x>>i)&1)==0) continue;
            x^=base[i];
        }
        return x==0;
    }
}all; int n; vector <pair <int,LB>> vec;
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
signed main()
{
    scanf("%lld",&n);
    for (RI i=1;i<=n;++i)
    {
        int x; scanf("%lld",&x);
        for (auto &[_,lb]:vec) lb.insert(x);
        if (!all.query(x)) vec.push_back({x,all});
        all.insert(x);
        vector <pair <int,LB>> n_vec;
        for (auto [x,lb]:vec)
        if (!lb.query(x))  n_vec.push_back({x,lb});
        vec=n_vec;
        int un_pst=(int)vec.size(),fr=all.free;
        printf("%lld\n",(quick_pow(2,i)-1LL*un_pst*quick_pow(2,fr)%mod-1LL*(i-un_pst)*quick_pow(2,fr)%mod*inv_2%mod+2LL*mod)%mod);
    }
    return 0;
}

D. Two Options

队友写的,我题都没看

#include <bits/stdc++.h>

constexpr int $n = 1'000'006;
using llsi = long long signed int;
constexpr llsi mod = 1'000'000'007;

int n, m;

struct minivec {
    int x[2];
    int length;
    minivec(): x{0, 0}, length(0) {}
    minivec(int a, int b): x{a, b}, length(2) {
        if(x[0] > x[1]) std::swap(x[0], x[1]);
    };
    minivec join(const minivec &b) const {
        minivec res;
        int p = 0, q = 0;
        while(p < length && q < b.length) {
            if(x[p] == b.x[q]) res.x[res.length++] = x[p], p += 1, q += 1;
            else if(x[p] < b.x[q]) p += 1;
            else                   q += 1;
        }
        return res;
    }
};

std::optional<minivec> rst[$n];

std::vector<int> out[$n];
bool vis[$n];

std::tuple<int, int, int> dfs(int cur) {
    vis[cur] = 1;
    int res1 = 1, res2 = out[cur].size(), res3 = 2;
    for(auto out: out[cur]) {
        if(!vis[out]) {
            auto [r1, r2, r3] = dfs(out);
            res1 += r1, res2 += r2;
            res3 = std::min(res3, r3);
        } else if(out == cur) {
            res3 = 1;
        }
    }
    return {res1, res2, res3};
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin >> n >> m;
    while(m--) {
        int i, j, x;
        std::cin >> i >> j >> x;
        if(rst[x]) rst[x] = rst[x]->join(minivec(i, j));
        else       rst[x] = minivec(i, j);
    }
    // std::cerr << rst[1]->length << char(10);
    for(int i = 1; i <= n; ++i) if(auto r = rst[i]) {
        if(r->length == 0) {
            std::cout << " 0\n";
            return 0;
        }
        if(r->length == 1) {
            int x = r->x[0];
            out[x].emplace_back(x);
            out[x].emplace_back(x);
            // std::cerr << "[Self loop] " << x << char(10);
            continue;
        }
        auto [x, y] = r->x;
        out[x].emplace_back(y);
        out[y].emplace_back(x);
        // std::cerr << "[Edge] " << x << "-" << y << char(10);
    }
    llsi ans = 1, hkr = 0;
    for(int i = 1; i <= n; ++i) if(!vis[i]) {
        auto [vs, es, ars] = dfs(i);
        // std::cerr << std::format("dfs({}) = [vs = {}, es = {}]\n", i, vs, es);
        es /= 2;
        if(es > vs) {
            std::cout << "0\n";
            return 0;
        }
        if(es == vs) {
            ans = ans * ars % mod;
            continue;
        }
        if(es == vs - 1) {
            ans = ans * vs % mod;
            hkr += 1;
            continue;
        }
        // std::cerr << "FUCK\n";
        return 1;
    }
    for(int i = 1; i <= hkr; ++i) ans = ans * i % mod;
    std::cout << ans << char(10);
    return 0;
}

E. Phone Company

签到,显然 \(n=2k+1\),要求方案的话用循环构造,每个点向其后面的 \(k\) 个点连边即可

#include<cstdio>
#include<iostream>
using namespace std;
int k;
int main()
{
    scanf("%d",&k); int n=2*k+1;
    printf("%d\n",n);
    for (int x=0;x<n;++x)
    for (int d=1;d<=k;++d)
    printf("%d%c",(x+d)%n+1," \n"[d==k]);
    return 0;
}

F. Language Barrier

赛时最后 5min 调完结果 TLE on 49 了,赛后发现是 multiset 常数太大了,换成可删除的 priority_queue 就过了,只能说沟槽的卡常

首先如果 \(1\)\(n\) 对应的区间有交,那么直接走过去翻译就是最优的;否则我们只考虑 \(R[1]<L[n]\) 的情形,对于令一种情况取反即可变为上述情形

不难发现此时纸条上的数一定是递增变化最优,同时走到任何一个点 \(x\) 时,能翻译的话直接把纸条上的数变为 \(R[x]\) 一定不劣

因此考虑 DP,令 \(f_x\) 表示走到 \(x\) 所需的最小代价,并钦定在点 \(x\) 处进行了一次翻译,转移就是枚举上一个走到的点 \(y\)

\[f_x=\min_{L[y]\le L[x]\le R[y]} \{f_y+\operatorname{dist}(x,y)+1\} \]

为了处理下标,我们可以使用扫描线的思路,把每个 \(L[i]\) 看作激活点 \(i\)\(R[i]\) 看作失活点 \(i\)

将所有 \(L[i],R[i]\) 升序排序后,在加入 \(L[x]\) 的时候,所有激活的点就是可供转移的点 \(y\)

后面的式子是一个关于距离的最小值,树上距离很容易想到用点分树处理

我们每次激活/失活某个点时,将其到根的路径上的所有点插入其权值;查询的时候同理,查询一遍其到根的路径上所有点权值的最小值即可

总复杂度 \(O(n\log^2 n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<set>
#include<algorithm>
#include<queue>
#define int long long
#define RI int
#define CI const int&
#define fi first
#define se second
using namespace std;
const int N=200005,INF=1e18;
int n,x,y,L[N],R[N],f[N]; vector <int> v[N];
class Tree_Solver
{
	private:
		int anc[N][20],dep[N];
	public:
		inline void DFS(CI now=1,CI fa=0)
		{
			anc[now][0]=fa; dep[now]=dep[fa]+1;
			for (RI i=0;i<19;++i) if (anc[now][i]) anc[now][i+1]=anc[anc[now][i]][i]; else break;
			for (auto to:v[now]) if (to!=fa) DFS(to,now);
		}
		inline int getLCA(int x,int y)
		{
			if (dep[x]<dep[y]) swap(x,y);
			for (RI i=19;i>=0;--i) if (dep[anc[x][i]]>=dep[y]) x=anc[x][i];
			if (x==y) return x;
			for (RI i=19;i>=0;--i) if (anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];
			return anc[x][0];
		}
		inline int getdis(CI x,CI y)
		{
			return dep[x]+dep[y]-2*dep[getLCA(x,y)];
		}
}T;
namespace Point_Divide_Tree
{
	int rt,ots,sz[N],mx[N],fa[N]; bool vis[N];
	inline void getrt(CI now=1,CI fa=0)
	{
		sz[now]=1; mx[now]=0; for (auto to:v[now])
		if (to!=fa&&!vis[to]) getrt(to,now),sz[now]+=sz[to],mx[now]=max(mx[now],sz[to]);
		if (mx[now]=max(mx[now],ots-sz[now]),mx[now]<mx[rt]) rt=now;
	}
	inline void solve(int now)
	{
		vis[now]=1; for (auto to:v[now]) if (!vis[to])
		mx[rt=0]=INF,ots=sz[to],getrt(to,now),getrt(rt,0),fa[rt]=now,solve(rt);
	}
	inline void divide(CI n)
	{
		mx[rt=0]=INF; ots=n; getrt(); solve(rt);
	}
}
using namespace Point_Divide_Tree;
struct Removable_Heap
{
    priority_queue <int,vector <int>,greater <int>> hp,rmv;
    inline void insert(CI x)
    {
        hp.push(x);
    }
    inline void remove(CI x)
    {
        rmv.push(x);
    }
    inline int top(void)
    {
        while (!hp.empty()&&!rmv.empty()&&hp.top()==rmv.top())
        {
            hp.pop(); rmv.pop();
        }
        return hp.top();
    }
}s[N];
signed main()
{
    scanf("%lld",&n);
	for (RI i=1;i<n;++i)
	{
        scanf("%lld%lld",&x,&y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    T.DFS(); divide(n);
    for (RI i=1;i<=n;++i)
    scanf("%lld%lld",&L[i],&R[i]);
    if (max(L[1],L[n])<=min(R[1],R[n])) return printf("%lld\n",T.getdis(1,n)),0;
    if (L[1]>R[n])
    {
        for (RI i=1;i<=n;++i)
        swap(L[i],R[i]),L[i]=-L[i],R[i]=-R[i];
    }
    vector <tuple <int,int,int>> Event;
    for (RI i=1;i<=n;++i)
    {
        Event.push_back({L[i],0,i});
        Event.push_back({R[i],1,i});
    }
    sort(Event.begin(),Event.end());
    for (RI i=1;i<=n;++i) s[i].insert(INF);
    for (RI i=1;i<=n;++i)
    {
        f[i]=INF;
        if (max(L[1],L[i])<=min(R[1],R[i])) f[i]=T.getdis(1,i)+1;
    }
    f[1]=0;
    for (auto [_,tp,id]:Event)
    {
        if (tp==0)
        {
            int res=INF;
            for (int x=id;x;x=fa[x]) res=min(res,s[x].top()+T.getdis(id,x));
            f[id]=min(f[id],res+1);
            // printf("f[%d] = %d\n",id,f[id]);
            for (int x=id;x;x=fa[x]) s[x].insert(f[id]+T.getdis(id,x));
        } else
        {
            for (int x=id;x;x=fa[x]) s[x].remove(f[id]+T.getdis(id,x));
        }
    }
	return printf("%lld\n",f[n]-1),0;
}

G. Intervals from Triplets

队友写的,我题都没看

#include <bits/stdc++.h>

using llsi = long long signed int;

#define fi first
#define se second

constexpr int $n = 1'000'006;
constexpr llsi NNF = -1e16;

using ti3 = std::array<int, 3>;

int n;
ti3 hkr[$n];
std::pair<ti3, ti3> ars[$n];
llsi dp[$n][3];

int main() {
    std::ios::sync_with_stdio(false);
    std::cin >> n;
    for(int i = 1; i <= n; ++i) {
        auto &[a, b, c] = hkr[i];
        std::cin >> a >> b >> c;
    }
    std::sort(hkr + 1, hkr + n + 1, [&](auto &&lhs, auto &&rhs) {
        return lhs[1] < rhs[1];
    });
    int p = 1, l = 1, r = 1;
    ars[0] = {{-1, -1, -1}, {-1, -1, -1}};
    while(l <= n) {
        while(r <= n && hkr[r][1] == hkr[l][1]) r += 1;
        if(r - l > 2) {
            std::cout << "-1\n";
            return 0;
        } else
        if(r - l == 2) {
            ars[p++] = {hkr[l], hkr[l + 1]};
            l = r;
            continue;
        }
        int b = hkr[l][1];
        ars[p++] = {hkr[l], {b, b, b}};
        l = r;
    }
    n = p - 1;
    memset(dp, 0x80, sizeof dp);
    dp[0][0] = dp[0][1] = 0;
    // for(int i = 0; i <= n; ++i) std::cerr << std::format("ars[{}] = {} {} {} {} {} {}\n", i, ars[i].fi[0], ars[i].fi[1], ars[i].fi[2], ars[i].se[0], ars[i].se[1], ars[i].se[2]);
    for(int i = 1; i <= n; ++i) {
        if(ars[i].fi[0] >= ars[i - 1].se[2]) dp[i][0] = std::max(dp[i][0], dp[i - 1][0] + ars[i].fi[1] - ars[i].fi[0] + ars[i].se[2] - ars[i].se[1]);
        if(ars[i].fi[0] >= ars[i - 1].fi[2]) dp[i][0] = std::max(dp[i][0], dp[i - 1][1] + ars[i].fi[1] - ars[i].fi[0] + ars[i].se[2] - ars[i].se[1]);
        if(ars[i].fi[0] >= ars[i - 1].fi[2]) dp[i][0] = std::max(dp[i][0], dp[i - 1][2] + ars[i].fi[1] - ars[i].fi[0] + ars[i].se[2] - ars[i].se[1]);
        if(ars[i].se[0] >= ars[i - 1].se[2]) dp[i][1] = std::max(dp[i][1], dp[i - 1][0] + ars[i].se[1] - ars[i].se[0] + ars[i].fi[2] - ars[i].fi[1]);
        if(ars[i].se[0] >= ars[i - 1].fi[2]) dp[i][1] = std::max(dp[i][1], dp[i - 1][1] + ars[i].se[1] - ars[i].se[0] + ars[i].fi[2] - ars[i].fi[1]);
        if(ars[i].se[0] >= ars[i - 1].fi[2]) dp[i][1] = std::max(dp[i][1], dp[i - 1][2] + ars[i].se[1] - ars[i].se[0] + ars[i].fi[2] - ars[i].fi[1]);

        if(ars[i].se[0] != ars[i].se[1]) continue;
        if(ars[i].fi[0] >= ars[i - 1].se[2]) dp[i][2] = std::max(dp[i][2], dp[i - 1][0] + ars[i].fi[2] - ars[i].fi[0]);
        if(ars[i].fi[0] >= ars[i - 1].fi[2]) dp[i][2] = std::max(dp[i][2], dp[i - 1][1] + ars[i].fi[2] - ars[i].fi[0]);
        if(ars[i].fi[0] >= ars[i - 1].fi[2]) dp[i][2] = std::max(dp[i][2], dp[i - 1][2] + ars[i].fi[2] - ars[i].fi[0]);
        
        // std::cerr << "dp[" << i << "] = " << dp[i][0] << char(32) << dp[i][1] << char(10);
    }
    llsi ans = *std::max_element(dp[n], dp[n] + 3);
    if(ans < 0) std::cout << "-1\n";
    else std::cout << ans << char(10);
    return 0;
}

K. Connect the Points

首先如果一条线段的两个端点不都在边界上时,显然可以直接将其忽略

只考虑那些两个端点都在边界上的线段,检验是否合法是个经典结论(这个结论还是我上半年看某篇论文的时候看到的)

根据某个方向绕边界一圈,按顺序把遇到的线段端点对应的编号记录下来

一种连边方式合法,当且仅当记录下来的编号序列是类括号序列

即用栈维护该序列,遇到栈顶两个相同的元素就出栈,最后栈为空即合法

总复杂度 \(O(n)\),不知道为什么给一个 \(n\le 7\) 的数据范围

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<stack>
#define RI register int
#define CI const int&
using namespace std;
int n,a,b,c,d; vector <pair <int,int>> L[4];
int main()
{
    scanf("%d",&n);
    for (RI i=1;i<=n;++i)
    {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        if ((a!=0&&a!=n&&b!=0&&b!=n)||(c!=0&&c!=n&&d!=0&&d!=n)) continue;
        auto addpoint=[&](CI x,CI y,CI id)
        {
            if (y==0) L[0].push_back({x,id}); else
            if (x==n) L[1].push_back({y,id}); else
            if (y==n) L[2].push_back({x,id}); else
            if (x==0) L[3].push_back({y,id});
        };
        addpoint(a,b,i); addpoint(c,d,i);
    }
    sort(L[0].begin(),L[0].end());
    sort(L[1].begin(),L[1].end());
    sort(L[2].begin(),L[2].end(),greater <pair <int,int>>());
    sort(L[3].begin(),L[3].end(),greater <pair <int,int>>());
    stack <int> stk;
    for (RI i=0;i<4;++i)
    for (auto [_,x]:L[i])
    {
        // printf("x = %d\n",x);
        if (!stk.empty()&&stk.top()==x) stk.pop();
        else stk.push(x);
    }
    puts(stk.empty()?"YES":"NO");
    return 0;
}

L. Neo-Nim

好像是个繁琐的博弈+分讨,赛时后面这题我全部扔给队友了,不知道后面会不会补这个题


Postscript

感觉这场题都没啥代码难度,拿来复建一下还是挺好的

posted @ 2026-01-19 16:55  空気力学の詩  阅读(2)  评论(0)    收藏  举报