The 4th Universal Cup. Stage 10: Grand Prix of Wrocław

Preface

又是经典外国场,基本全是思维题,套路算法题感觉挺少的,和 ECF 的风格有点差异,不过用来找找状态也是不错的

最后 9 题收场,C 题最后半小时开的感觉还有些细节没想清楚,后续也懒得补了,只能说摆烂这一块


A. Automatized Mineral Classification

队友在我中间上厕所的时候秒的,我题目都没看

#include <bits/stdc++.h>
using namespace std;

const int N = 505;
int n;

int current = 0;
int push(int ball) {
    if (ball <= 0 && ball > n) {
        printf("ball = %d\n", ball);
        assert(ball > 0 && ball <=n);
    }
    std::cout << "+ " << ball << std::endl;
    int new_val; std::cin >> new_val;
    int res = new_val - current;
    current = new_val;
    return res;
}

int pop() {
    std::cout << "-" << std::endl;
    int new_val; std::cin >> new_val;
    int res = new_val - current;
    current = new_val;
    return res;
}

vector<int> ans[N];

void print_answer() {
    int k = 0;
    for (int i=1; i<=500; ++i) if (ans[i].size() > 0) ++k;
    std::cout << "! " << k << std::endl;
    for (int i=1; i<=500; ++i) if (ans[i].size() > 0) {
        std::cout << ans[i].size();
        for(const auto x: ans[i]) std::cout << ' ' << x;
        std::cout << std::endl;
    }
}


int main() {
    // std::ios::sync_with_stdio(false);
    vector<int> xx, yy;
    cin >> n;
    for (int i=1; i<=n; ++i) {
        int res = push(i);
        if (res==1) xx.push_back(i), ans[i].push_back(i);
        else yy.push_back(i);
    }
    for (int i=1; i<=n; ++i) pop();

    int cnt = ((n - xx.size()) + 9) / 10;
    for (int blk=0, y=0; blk<cnt; ++blk, y+=10) {
        // printf("blk=%d\n", blk);
        vector<int> vec;
        for (int i=0; i<xx.size(); ++i) push(xx[i]);
        for (int i=y; i<y+10 && i<yy.size(); ++i) {
            push(yy[i]);
        }
        for (int i=0; i<xx.size(); ++i) {
            int res = pop();
            if (0==res) vec.push_back(xx[i]);
        }
        for (int i=y; i<y+10 && i<yy.size(); ++i) pop();

        for (int j=y; j<y+10 && j<yy.size(); ++j) {
            for (int i=0; i<vec.size(); ++i) {
                push(vec[i]);
                int res = push(yy[j]);
                pop();
                pop();
                if (res==0) {
                    ans[vec[i]].push_back(yy[j]);
                    break;
                }
            }
        }
    }

    print_answer();
    return 0;
}

D. DNA

这题刚开始我和徐神想了 90min 给了三四种启发式做法,结果交上去没一个对的

后面祁神一眼找到关键就直接秒了,我只能说对电波很关键

注意最后答案一定是个 0/1 串,那么其中要么 0 的个数超过一半,要么 1 的个数超过一半

因此我们钦定答案全为 0 或 1,分别跑一遍让答案取最大值即可

#include<cstdio>
#include<iostream>
using namespace std;
const int N=3005;
int t,n,nxt[3][N][2]; char s[3][N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d",&n);
        for (int i=0;i<3;++i)
        {
            scanf("%s",s[i]+1);
            nxt[i][n+1][0]=nxt[i][n+1][1]=n+1;
            for (int j=n;j>=1;--j)
            {
                nxt[i][j][s[i][j]-'0']=j;
                nxt[i][j][(s[i][j]-'0')^1]=nxt[i][j+1][(s[i][j]-'0')^1];
            }
        }
        auto find=[&](int v)
        {
            int ans=0,pos[3]={1,1,1};
            while (pos[0]<=n&&pos[1]<=n&&pos[2]<=n)
            {
                int meets=0;
                for (int i=0;i<3;++i)
                meets+=(nxt[i][pos[i]][v]<=n);
                ans+=(meets==3);
                for (int i=0;i<3;++i)
                pos[i]=nxt[i][pos[i]][v]+1;
            }
            return ans;
        };
        printf("%d\n",max(find(0),find(1)));
    }
    return 0;
}

F. Foxes

对于在一端动态插入/删除元素的 LIS 问题,有个经典的单调栈套栈的做法用来维护不同长度的 LIS 末端元素最优值是什么

(其实本质就是把 DP+二分 求 LIS 的算法可持久化了,只能说这个还是很套路的)

因此我们在移动指针时对于左右两侧分别维护这个结构,所有的修改操作都很 trivial 了(只需要简单二分即可),然后考虑如何合并左右两侧的答案

考虑用权值线段树,对于左侧的最优取值点 \(x\),我们对 \([x+1,10^6]\) 的后缀作区间加;对于右侧的最优取值点 \(x\),我们对 \([1,x]\) 的前缀作区间加,全局的 LIS 最大值就是 \([1,10^6]\) 所有位置的最大值

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5,M=1e6+1;
int n,q,a[N];
class Segment_Tree
{
    private:
        int mx[N<<2],tag[N<<2];
        inline void pushup(CI now)
        {
            mx[now]=max(mx[now<<1],mx[now<<1|1]);
        }
        inline void apply(CI now,CI mv)
        {
            mx[now]+=mv; tag[now]+=mv;
        }
        inline void pushdown(CI now)
        {
            if (tag[now]) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=0;
        }
    public:
        #define TN CI now=1,CI l=1,CI r=M
        #define LS now<<1,l,mid
        #define RS now<<1|1,mid+1,r
        inline void modify(CI beg,CI end,CI mv,TN)
        {
            if (beg<=l&&r<=end) return apply(now,mv);
            int mid=l+r>>1; pushdown(now);
            if (beg<=mid) modify(beg,end,mv,LS);
            if (end>mid) modify(beg,end,mv,RS);
            pushup(now);
        }
        inline int query(void)
        {
            return mx[1];
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
inline void update(CI x,CI y)
{
    // printf("upt %d %d\n",x,y);
    if (x>0) SEG.modify(x+1,M,y); else SEG.modify(1,-x,y);
}
struct stack_of_stacks
{
    vector <int> stk[N]; int top;
    inline void insert(CI x)
    {
        if (top==0||stk[top].back()<x)
        {
            stk[++top].push_back(x);
            update(x,1);
            return;
        }
        int l=1,r=top,mid,res;
        while (l<=r)
        if (stk[mid=l+r>>1].back()>=x) res=mid,r=mid-1; else l=mid+1;
        update(stk[res].back(),-1);
        stk[res].push_back(x);
        update(x,1);
    }
    inline void erase(CI x)
    {
        update(x,-1);
        int l=1,r=top,mid,res;
        while (l<=r)
        if (stk[mid=l+r>>1].back()>=x) res=mid,r=mid-1; else l=mid+1;
        stk[res].pop_back();
        if (!stk[res].empty()) update(stk[res].back(),1);
        if (res==top&&stk[res].empty()) --top;
    }
}L,R;
int main()
{
    scanf("%d%d",&n,&q);
    for (RI i=1;i<=n;++i)
    scanf("%d",&a[i]);
    L.insert(a[1]);
    for (RI i=n;i>=2;--i)
    R.insert(-a[i]);
    int pos=1;
    while (q--)
    {
        char opt[5]; scanf("%s",opt);
        if (opt[0]=='<')
        {
            R.insert(-a[pos]);
            L.erase(a[pos--]);
        } else if (opt[0]=='>')
        {
            L.insert(a[++pos]);
            R.erase(-a[pos]);
        } else
        {
            int v; scanf("%d",&v);
            L.erase(a[pos]);
            L.insert(a[pos]=v);
            printf("%d\n",SEG.query());
        }
    }
    return 0;
}

G. Game of Darts

签到,暴搜即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstdlib>
using namespace std;
vector <int> score;
int P;
inline void DFS(int now,int sum,vector <int> ans=vector <int>())
{
    if (sum==P)
    {
        int last=ans.back();
        if (last%2==1) return;
        if (last==50||last/2>=1&&last/2<=20)
        {
            puts("YES");
            printf("%d\n",now);
            for (auto x:ans) printf("%d ",x);
            putchar('\n');
            exit(0);
        }
    }
    if (now>=3) return;
    for (auto x:score)
    {
        ans.push_back(x);
        DFS(now+1,sum+x,ans);
        ans.pop_back();
    }
}
int main()
{
    score.push_back(50);
    score.push_back(25);
    for (int a=1;a<=20;++a)
    {
        score.push_back(a);
        score.push_back(2*a);
        score.push_back(3*a);
    }
    scanf("%d",&P);
    if (P==0) return puts("NO"),0;
    DFS(0,0);
    return puts("NO"),0;
}

H. Hiking

\(mn=\min a_i,mx=\max a_i\),首先考虑 \(mx<2mn\) 的情形

有个很显然的构造法就是先把所有数分别取一个递增排列,然后再在剩下还有的数中每个取一个递增排列,直到将所有数用完

这样构造出的答案为所有数出现次数的最大值 \(cnt\),这显然是答案的下界

但是当 \(mx=2mn\) 时,这样做就会不可避免的出现把 \(mx\) 放在 \(mn\) 前面的情况,此时会产生 \(2\) 的贡献

为了尽量避免这种情形,我们让 \(mn\) 尽量出现在靠前的那些序列中,让 \(mx\) 出现在尽量靠后的序列中即可

#include<cstdio>
#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1e6+5;
int t,n,a[N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d",&n);
        map <int,int> mp;
        // vector <int> vec;
        for (int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]); ++mp[a[i]];
            // vec.push_back(x);
        }
        int mn=*min_element(a+1,a+n+1),mx=*max_element(a+1,a+n+1),mx_cnt=0;
        if (mn==mx)
        {
            printf("%d\n",n-1);
            for (int i=1;i<=n;++i)
            printf("%d%c",mn," \n"[i==n]);
            continue;
        }
        for (auto [val,c]:mp) mx_cnt=max(mx_cnt,c);
        int cnt_mn=mp[mn],cnt_mx=mp[mx];
        mp.erase(mn); mp.erase(mx);
        vector <int> ans;
        for (int c=1;c<=mx_cnt;++c)
        {
            vector <int> rmv,seq;
            if (mx_cnt-c+1<=cnt_mn) seq.push_back(mn);
            for (auto &[val,c]:mp)
            {
                seq.push_back(val);
                if (--c==0) rmv.push_back(val);
            }
            if (c<=cnt_mx) seq.push_back(mx);
            for (auto val:rmv) mp.erase(val);
            reverse(seq.begin(),seq.end());
            for (auto x:seq) ans.push_back(x);
        }
        reverse(ans.begin(),ans.end());
        int sum=0;
        for (int i=1;i<(int)ans.size();++i)
        sum+=ans[i-1]/ans[i];
        // sort(vec.begin(),vec.end());
        // int inc_sum=0;
        // for (int i=1;i<(int)vec.size();++i)
        // inc_sum+=vec[i-1]/vec[i];
        // if (inc_sum<sum) sum=inc_sum,ans=vec;
        printf("%d\n",sum);
        for (auto x:ans) printf("%d ",x);
        putchar('\n');
    }
    return 0;
}

I. Identical Fences

注意到题目允许有一定损失率,我们考虑以下启发式算法

把序列每 \(H\) 个位置划分为一组,我们只考虑在每一组内部找到一个最优划分的方法,最后把所有组的答案顺次拼接即可

根据数据生成规则,本机跑了下 \(H=12\) 的情况就可以得到 \(88\%\) 左右的正确率了

当然对于每组直接暴力跑出答案肯定是不行的,因此需要本机先把 \(2^H\) 种情况的最优解的表给打出来

#include <bits/stdc++.h>

constexpr int H = 12;
int n;
std::string s;

extern std::pair<int, int> hkr[1 << H];

void prep() {
    for(int i = 0; i < (1 << H); ++i) {
        for(int mk1 = 1; mk1 < (1 << H); ++mk1) {
            for(int mk2 = 1; mk2 < (1 << H); ++mk2) {
                if(mk1 & mk2) continue;
                if(__builtin_popcount(mk1) != __builtin_popcount(mk2)) continue;
                static std::vector<int> v1, v2;
                v1.clear(), v2.clear();
                for(int k = 0; k < H; ++k) {
                    if(mk1 >> k & 1) v1.emplace_back(i >> k & 1);
                    if(mk2 >> k & 1) v2.emplace_back(i >> k & 1);
                }
                if(v1 != v2) continue;
                if(__builtin_popcount(mk1) > __builtin_popcount(hkr[i].first))
                    hkr[i] = {mk1, mk2};
            }
        }
    }
    std::cerr << "std::pair<int, int> hkr[1 << H] = {\n";
    int total = 0;
    for(int i = 0; i < (1 << H); ++i) {
        std::cerr << "    /* i = " << i << " */ {" << hkr[i].first << ", " << hkr[i].second << "},\n";
        total += 2 * __builtin_popcount(hkr[i].first);
    }
    std::cerr << "};\n// Precision: " << (double)total / (double)((1 << H) * H) << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    // prep();
    std::cin >> n;
    std::cin >> s;
    // n = 100000;
    // s.resize(n);
    // std::mt19937 rng(78934);
    // for(int i = 0; i < n; ++i) s[i] = char('0' + rng() % 2);
    // std::cerr << "[s] " << s << std::endl;
    std::vector<int> ans1, ans2;
    for(int i = 0; i + H <= n; i += H) {
        int mk = 0;
        for(int j = H - 1; j >= 0; --j) mk = (mk << 1) | (s[i + j] == '1');
        auto [mk1, mk2] = hkr[mk];
        for(int j = 0; j < H; ++j) {
            if(mk1 >> j & 1) ans1.emplace_back(i + j);
            if(mk2 >> j & 1) ans2.emplace_back(i + j);
        }
    }
    std::cout << (int)ans1.size() << char(10);
    for(int i = 0; i < (int)ans1.size(); ++i) assert(s[ans1[i]] == s[ans2[i]]);
    for(int i = 0; i < (int)ans1.size(); ++i) std::cout << ans1[i] << char(i == (int)ans1.size() - 1 ? 10 : 32);
    for(int i = 0; i < (int)ans2.size(); ++i) std::cout << ans2[i] << char(i == (int)ans2.size() - 1 ? 10 : 32);
    // std::cerr << "Precision: " << (double)(ans1.size() * 2) / n << std::endl;
    return 0;
}

std::pair<int, int> hkr[1 << H] = {
    // ......
    // 此处为打表内容,具体可以见 https://qoj.ac/submission/1936838
}

J. Joyful Guided Tour

考虑用 local search 的思想构造答案

先随机一组初始解,若图中存在同色的 P3,则将中间那个点的颜色修改为其所有邻居中出现次数最少的那个

要证明这个算法最终一定能正确且快速地终止,考虑势能分析

定义某个局面的势能为图中相邻且同色的点对数目,不难发现一次修改会破坏掉至少 \(2\) 个这样的点对,并增加至多 \(1\) 个这样的点对

(因为每个点度数不超过 \(7\),根据鸽笼原理出现次数最少的颜色出现的次数至多为 \(1\)

而初始局面的势能至多为图中总边数 \(7n\),因此这个过程是可以线性时间得到一组合法解的

实现就非常 trivial 了,拿个队列模拟一下即可

#include <bits/stdc++.h>

constexpr int $n = 1'000'006;
std::mt19937 rng(114514);
int n, m;
int cl[$n];
bool enq[$n];
std::vector<int> out[$n];
std::queue<int> q;

void push(int u) {
    if(enq[u]) return ;
    int cnt = 0;
    for(auto out: out[u]) cnt += (cl[out] == cl[u]);
    if(cnt < 2) return ;
    q.push(u), enq[u] = true;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin >> n >> m;
    for(int i = 0, u, v; i < m; ++i) {
        std::cin >> u >> v;
        out[u].emplace_back(v);
        out[v].emplace_back(u);
    }
    for(int i = 1; i <= n; ++i) cl[i] = std::uniform_int_distribution{1, 4}(rng);
    for(int i = 1; i <= n; ++i) push(i);
    while(q.size() > 0) {
        int hd = q.front(); q.pop(); enq[hd] = false;
        static int cnt[5]; memset(cnt, 0, sizeof(cnt));
        for(auto out: out[hd]) cnt[cl[out]] += 1;
        int nc = std::min_element(cnt + 1, cnt + 5) - cnt;
        cl[hd] = nc; push(hd);
        for(auto out: out[hd]) push(out);
    }
    for(int i = 1; i <= n; ++i) std::cout << cl[i] << char(i == n ? 10 : 32);
    return 0;
}

K. Key Properties

队友开场写的,我题都没看

#include<bits/stdc++.h>
using namespace std;

int gcd(int a, int b) {
    return 0==b ? a : gcd(b, a%b);
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n;
    if (n%2 != 0) {
        cout << "NO\n";
    } else {
        cout << n / 2 * 3 << '\n';
        int x;
        for (x=2; x <= n/2; ++x) {
            if (1 == gcd(x, n/2)) break;
        }
        for (int i=0; i<n/2; ++i) {
            cout << (i*x % (n/2) + 1) << ' ' << ((i*x+x) % (n/2) + 1) << '\n';
        }
        for (int i=1; i<=n/2; ++i) {
            cout << i << ' ' << n/2 + i << '\n';
        }
        for (int i=n/2+1; i<n; ++i) cout << i << ' ' << i + 1 << '\n';
        cout << n << ' ' << n/2+1 << '\n';
    }
    return 0;
}

L. Letters on T-shirts

签到,我题都没看

#include<bits/stdc++.h>
using namespace std;

bool vis[3];
string str[3];
bool dfs(string ss, vector<int> vec) {
    if ("cerc" == ss) {
        cout << "YES\n";
        cout << vec.size() << '\n';
        for (int x : vec) cout << x+1 << ' ';
        cout << '\n';
        return true;
    }
    for (int i=0; i<3; ++i) if (!vis[i]){
        vis[i] = true; vec.push_back(i);
        string nstr = ss + str[i];
        // printf("dfs %d\n", i);
        if (dfs(nstr, vec)) return true;
        vis[i] = false; vec.pop_back();
    }
    return false;
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> str[0] >> str[1] >> str[2];
    if (!dfs(string(), vector<int>())) cout << "NO\n" ;
    return 0;
}

Postscript

感觉长时间不训练思维水平反而不降反升,再一次印证了我的训练降智论

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