2024“钉耙编程”中国大学生算法设计超级联赛(3)

Preface

徐神是我们的红太阳,最后 2min 切了一道极难的 string 使得在这场前期爆炸的局面最后没有崩得太难看

这场前期的开题顺序有点问题导致前 5 题出的很慢,中后期开始三人一人写一题,然后经典三开三卡

好在最后我在 WA 了五发后写对拍把 B 过了,徐神又压线过了 string,但比较可惜的是最后给祁神当小黄鸭找到了他 C 题的几个问题但没时间改了遗憾收场


深度自同构

神秘签到题,我题意都不知道

#include <bits/stdc++.h>

int f[1000001] = {1};

int main() {
    std::ios::sync_with_stdio(false);
    int n; std::cin >> n;
    for(int i = 1; i <= n; ++i) for(int j = i; j <= n; j += i) f[j] = (f[j] + f[i - 1]) % 998244353;
    for(int i = 1; i <= n; ++i) std::cout << f[i] << char(i == n ? 10 : 32);
    return 0;
}

旅行

很典的线段树分治/启发式合并优化树上 DP,但有人初值设错了瞪了 1h 最后对拍才发现问题,怎么回事呢

考虑一个 trivial 的 DP 做法,令 \(f_{i,j}\) 表示处理了 \(i\) 的子树,且上传一条端点类型为 \(j\) 的路径时最大的权值和;\(g_i\) 表示在 \(i\) 处合并路径,不上传的最大权值和

转移的时候先默认点 \(x\) 的所有儿子 \(y\) 都不上传路径,得到 \(sum=\sum_{y\in son(x)} g_y\)

对于 \(f_{x,c}\) 的更新,显然有 \(f_{x,c}=sum+\max_{y\in son(x)} f_{y,c}-g_y\),即选择从一个子树内继承路径,这样就不能获得在该子树处合并的贡献

对于 \(g_x\) 的更新,就是类似树上背包的找到两个子树的 \(f_{y,c}-g_y\) 合并即可,实现时可以对每种不同的第二位记录最大次大值进行处理

考虑优化该做法,不难发现第二维的端点类型不用显式的设出来,而是可以用 map 存储然后合并子树的时候启发式合并

同时再给每个点维护一个增量标记用来处理加减信息即可,总复杂度 \(O(n\log^2 n)\),当然也可以写 \(O(n\log n)\) 的线段树合并

#include<cstdio>
#include<iostream>
#include<map>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e18;
int t,n,c[N],w[N],x,y,tag[N],g[N],ans; vector <int> v[N]; map <int,int> f[N];
inline void DFS(CI now=1,CI fa=0)
{
    int sum=0; tag[now]=g[now]=0;
    for (auto to:v[now]) if (to!=fa) DFS(to,now),sum+=g[to];
    int ret=sum;
    auto merge=[&](CI x,CI y)
    {
        if (f[x].size()<f[y].size()) swap(f[x],f[y]),swap(tag[x],tag[y]);
        for (auto [col,val]:f[y])
        {
            if (f[x].count(col)) ret=max(ret,sum+(f[x][col]+tag[x])+(val+tag[y]));
            if (!f[x].count(col)||(f[x][col]+tag[x])<(val+tag[y])) f[x][col]=val+tag[y]-tag[x];
        }
        f[y].clear();
    };
    for (auto to:v[now]) if (to!=fa) tag[to]-=g[to],merge(now,to);
    if (f[now].count(c[now]))
    {
        ret=max(ret,sum+(f[now][c[now]]+tag[now])+w[now]);
        if ((f[now][c[now]]+tag[now])<w[now]) f[now][c[now]]=w[now]-tag[now];
    } else f[now][c[now]]=w[now]-tag[now];
    tag[now]+=sum; g[now]=ret; ans=max(ans,g[now]);
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    for (scanf("%lld",&t);t;--t)
    {
        RI i; for (scanf("%lld",&n),i=1;i<=n;++i) v[i].clear(),f[i].clear();
        for (i=1;i<=n;++i) scanf("%lld",&c[i]);
        for (i=1;i<=n;++i) scanf("%lld",&w[i]);
        for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
        ans=-INF; DFS(); printf("%lld\n",ans);
        //for (i=1;i<=n;++i) printf("%lld%c",g[i]," \n"[i==n]);
    }
    return 0;
}

游走

神秘分讨题,奖励祁神吃一坨大的

很容易发现除了 \(p_i=1\) 的信息外,其余的信息已经唯一地确定了酒鬼的移动,只要检查两次目击间的时间能否 cover 距离,以及信息间 \(p_i+q_i\) 的奇偶性相同即可

但现在问题是对于 \(p_i=1\) 的信息需要特别处理,可以不用保证奇偶性必须一样,分多种情况讨论即可

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

void solve(){
    map<int, int> ifo;
    int n, m; cin >> n >> m;
    bool bad=false;
    int tnz = 1e9+5;
    int mov1 = -1;
    int parity = -1;
    int odd1=-1, even1=0;
    for (int i=1; i<=m; ++i){
        int opt; cin >> opt;
        if (0==opt){
            int x, t; cin >> x >> t;
            if (bad) continue;

            if (ifo.count(t)){
                if (ifo[t]!=x) bad=true;
                continue;
            }

            ifo[t] = x;

            if (-1==parity){
                if (1==x){
                    if (t%2==1) odd1 = max(odd1, t);
                    else even1 = max(even1, t);
                    continue;
                }
                parity = (t+x)%2;
				tnz = t;
                bool meet=false;
                for (auto [t1, x1] : ifo){
                    if (t1==t){
						meet=true;
					}
                    if ((t1+x1)%2!=parity){
                        if (!meet) mov1 = t1;
                        else{bad=true; break;}
                    }
                }
                if (bad) continue;
            }
			
            auto it = ifo.find(t);

            auto checkback = [&]() -> bool {
                auto it1 = it; ++it1;
                if (it1!=ifo.end()){
                    auto [t1, x1] = *it1;
                    if (abs(t-t1) < abs(x-x1)) return false;
                }
                return true;
            };

            auto checkfront = [&]() -> bool {
                if (t > tnz){
                    auto it1 = it; --it1;
                    auto [t1, x1] = *it1;
                    if (abs(t-t1) < abs(x-x1)) return false;
                }else if (t > mov1){
                    if (t-mov1 < x) return false;
                }else if (t < mov1) return false;
                return true;
            };

            if (t<tnz){
                if (1==x){
                    if ((t+x)%2!=parity) mov1 = max(t, mov1);
                    if (mov1 > tnz){ bad=true; continue;}
                    int xnz = ifo[tnz];
                    int x1=1, t1 = ((t+x)%2==parity ? t : t+1);
                    if (abs(t1-tnz) < abs(x1-xnz)){ bad=true; continue;}
                }else{
                    if ((t+x)%2!=parity){ bad=true; continue;}
                    if (!checkback() || !checkfront()){ bad=true; continue;}
                    tnz = t;
                }
            }else{
                if ((t+x)%2!=parity){ bad=true; continue;}
                if (!checkback() || !checkfront()){ bad=true; continue;}
            }
        }else if (2==opt){
			if (bad){cout << "bad\n"; continue;}
            if (-1==parity) cout << "inf\n";
            else{
                int xnz = ifo[tnz];
                cout << tnz-xnz+1 << '\n';
            }
        }else if (1==opt){
			if (bad){cout << "bad\n"; continue;}
			if (-1==parity) cout << (odd1 > even1 ? even1+1 : odd1+1) << '\n';
			else cout << ((mov1+2)%2==parity ? mov1+1 : mov1+2) << '\n';
		}
    } 
}
	
signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t; while (t--) solve();
    return 0;
}


游戏

神秘概率题,前面的转化都能想到,然后一转换成随机游走模型就歇逼了,鉴定为弃疗


数论

好像是个神秘复杂度分析题,感觉不太可做,弃疗


字符串

徐神伟大,无需多言,这题我题都没看,一个劲地模徐神就完事了

#include <bits/stdc++.h>

constexpr int $n = 1000005;

using llsi = long long signed int;

class SAM {
    void dfs(int cur) {
        loop[cur][0] = fa[cur];
        for(int i = 1; i <= 20; ++i) loop[cur][i] = loop[loop[cur][i - 1]][i - 1];

        for(auto ch: ch[cur]) dfs(ch);
    }

public:
    int loop[$n][21];
    std::vector<int> ch[$n];
    int go[$n][2], fa[$n], len[$n], occ[$n], las, newcnt;
    llsi dp[$n];
    int pos[$n], pre[$n][21];

    void init(int n) {
        las = newcnt = 1;
        for(int i = 0; i < 2; ++i) go[1][i] = 0;
    }

    void build_loop() {
        for(int i = 1; i <= newcnt; ++i) ch[i].clear();
        for(int i = 2; i <= newcnt; ++i) ch[fa[i]].push_back(i);
        
        dfs(1);
    }

    void calc_occ(const std::string &s) {
        memset(occ, 0, sizeof(int) * (newcnt + 1));
        for(int i = 0, cur = 1; i < s.size(); ++i)
            assert(cur),
            occ[cur = go[cur][s[i] - 'a']] ++;
        
        auto d = [&](auto self, int cur) -> void {
            for(auto ch: ch[cur]) if(ch) self(self, ch), occ[cur] += occ[ch];
        };

        d(d, 1);
        
        std::vector<bool> locked(newcnt + 1, false);

        auto fetch_next = [&](int i) {
            for(int j = 0; j < 2; ++j) if(go[i][j] && occ[go[i][j]] == occ[i]) return go[i][j];
            return 0;
        };

        for(int i = 1; i <= newcnt; ++i) for(int j = 0; j < 2; ++j) {
            int n; if((n = fetch_next(i))) locked[n] = true;
        }


        for(int i = 1; i <= newcnt; ++i) if(!locked[i]) {
            dp[i] = len[fa[i]]; pos[i] = 0;
            memset(pre[i], 0, sizeof(pre[i]));
            for(int cur = i, nxt = fetch_next(cur); nxt; cur = nxt, nxt = fetch_next(cur)) {
                dp[nxt] = dp[cur] + len[fa[nxt]];
                pos[nxt] = pos[cur] + 1;
                pre[nxt][0] = cur;
                for(int i = 1; i <= 20; ++i) pre[nxt][i] = pre[pre[nxt][i - 1]][i - 1];
            }
        }

        return ;
    }

    int pop(int start, int l) {
        for(int i = 20; ~i; --i) if(len[loop[start][i]] >= l) start = loop[start][i];
        return start;
    }

    std::pair<int, int> pop_range(int start, int i) {
        int res = pop(start, i);
        return {len[res], len[res] + 1};
    }

    void insert(char a) {
        int c = a - 'a';
        int p = las;
        int np = las = ++newcnt;
        len[np] = len[p] + 1;
        for(int i = 0; i < 2; ++i) go[np][i] = 0;
        for(; p && !go[p][c]; p = fa[p]) go[p][c] = np;
        if(!p) {
            fa[np] = 1;
            return ;
        }
        int q = go[p][c];
        if(len[q] == len[p] + 1) {
            fa[np] = q;
            return ;
        }
        int nq = ++newcnt;
        len[nq] = len[p] + 1;
        for(int i = 0; i < 2; ++i) go[nq][i] = go[q][i];
        fa[nq] = fa[q];
        fa[np] = fa[q] = nq;
        for(; p && go[p][c] == q; p = fa[p]) go[p][c] = nq;
        return ;
    }
} S1, S2;

int main() {
    std::ios::sync_with_stdio(false);
    int t; std::cin >> t; while(t--) {
        std::string s; std::cin >> s;
        int n = s.size();
        S1.init(n), S2.init(n);
        for(int i = 0; i < n; ++i) S1.insert(s[i]);
        for(int i = n - 1; i >= 0; --i) S2.insert(s[i]);

        std::vector<int> e1(n), e2(n);
        for(int i = 0, cur = 1; i < n; ++i)
            e1[i] = cur = S1.go[cur][s[i] - 'a'];
        for(int i = n - 1, cur = 1; i >= 0; --i)
            e2[i] = cur = S2.go[cur][s[i] - 'a'];

        S1.build_loop(), S2.build_loop();
        S1.calc_occ(s);
        llsi lastans = 0;
        int q; std::cin >> q; while(q--) {
            llsi op, l, r;
            std::cin >> op >> l >> r;
            l = (l + lastans - 1) % n;
            r = (r + lastans - 1) % n;
            // std::cerr << "l, r = " << l << ", " << r << char(10);
            llsi L = r - l + 1;
            if(op == 1) {
                auto [l1, l2] = S1.pop_range(e1[r], L);
                auto [r1, r2] = S2.pop_range(e2[l], L);
                lastans = (l1 - L + 1) * (r1 - L + 1);
            } else {
                int hkr = S1.pop(e1[r], L);
                // std::cerr << "dp = " << S1.dp[hkr] << ", pos = " << S1.pos[hkr] << ", occ = " << S1.occ[hkr] << char(10);
                auto pre = S1.pre;
                auto fa = S1.fa;
                auto len = S1.len;
                int d = L, cur = hkr, offset = 0;
                for(int i = 20; ~i; --i) {
                    int nxt = pre[cur][i];
                    if(nxt == 0) continue;
                    if(len[fa[nxt]] >= d - (1 << i)) continue;
                    d -= (1 << i);
                    offset |= 1 << i;
                    cur = pre[cur][i];
                }
                // std::cerr << "dp[" << cur << "] = " << S1.dp[cur] << char(10);
                lastans = L * (offset + 1) - (S1.dp[hkr] - S1.dp[pre[cur][0]]) - (llsi(offset + 1) * (offset) / 2);
            }
            std::cout << lastans << char(10);
        }
    }
    return 0;
}

单峰数列

把原数组上的信息转化到差分数组上,不难发现每个条件的检验都很 trivial 了

判断单峰时可以线段树上二分找到第一个差分值 \(\le 0\) 的位置,然后检验后面一段是否单减即可

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e18;
int n,a[N],d[N],q,opt,l,r,x;
class Segment_Tree
{
    private:
        int mx[N<<2],mn[N<<2];
        inline void pushup(CI now)
        {
            mx[now]=max(mx[now<<1],mx[now<<1|1]);
            mn[now]=min(mn[now<<1],mn[now<<1|1]);
        }
    public:
        #define TN CI now=1,CI l=1,CI r=n+1
        #define LS now<<1,l,mid
        #define RS now<<1|1,mid+1,r
        inline void build(TN)
        {
            if (l==r) return (void)(mx[now]=mn[now]=d[l]);
            int mid=l+r>>1; build(LS); build(RS); pushup(now);
        }
        inline void update(CI pos,CI mv,TN)
        {
            if (l==r) return (void)(mx[now]+=mv,mn[now]+=mv); int mid=l+r>>1;
            if (pos<=mid) update(pos,mv,LS); else update(pos,mv,RS); pushup(now);
        }
        inline int query_min(CI beg,CI end,TN)
        {
            if (beg<=l&&r<=end) return mn[now]; int mid=l+r>>1,ret=INF;
            if (beg<=mid) ret=min(ret,query_min(beg,end,LS));
            if (end>mid) ret=min(ret,query_min(beg,end,RS));
            return ret;
        }
        inline int query_max(CI beg,CI end,TN)
        {
            if (beg<=l&&r<=end) return mx[now]; int mid=l+r>>1,ret=-INF;
            if (beg<=mid) ret=max(ret,query_max(beg,end,LS));
            if (end>mid) ret=max(ret,query_max(beg,end,RS));
            return ret;
        }
        inline int query_pos(CI beg,CI end,TN)
        {
            if (r<beg||l>end) return -1;
            if (mn[now]>0) return -1;
            if (l==r) return l;
            int mid=l+r>>1,res=query_pos(beg,end,LS);
            if (res!=-1) return res;
            return query_pos(beg,end,RS);
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
signed main()
{
    RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
    for (i=1;i<=n+1;++i) d[i]=a[i]-a[i-1];
    for (SEG.build(),scanf("%lld",&q);q;--q)
    {
        scanf("%lld%lld%lld",&opt,&l,&r); int pos;
        switch (opt)
        {
            case 1:
                scanf("%lld",&x); SEG.update(l,x); SEG.update(r+1,-x); break;
            case 2:
                if (l==r) puts("1"); else
                puts(SEG.query_min(l+1,r)==0&&SEG.query_max(l+1,r)==0?"1":"0");
                break;
            case 3:
                if (l==r) puts("1"); else
                puts(SEG.query_min(l+1,r)>0?"1":"0");
                break;
            case 4:
                if (l==r) puts("1"); else
                puts(SEG.query_max(l+1,r)<0?"1":"0");
                break;
            case 5:
                pos=SEG.query_pos(l+1,r);
                if (pos==-1||pos==l+1) puts("0"); else
                puts(SEG.query_max(pos,r)<0?"1":"0");
                break;
        }
    }
    return 0;
}

比特跳跃

徐神写的神秘优化建图题,我题意都不知道

#include <bits/stdc++.h>

using llsi = long long signed int;

void work() {
    int n, m; llsi k;
    std::cin >> n >> m >> k;
    int nn = n;
    for(int i = 0; i <= 20; ++i) n |= n >> i;
    std::vector<std::vector<std::pair<int, llsi>>> out(2 * n + 1);
    for(int i = 1, u, v, t; i <= m; ++i) {
        std::cin >> u >> v >> t;
        out[u].emplace_back(v, t);
        out[v].emplace_back(u, t);
    }
    for(int i = 1; i <= n; ++i) {
        out[i].emplace_back(i + n, i * k);
        out[i + n].emplace_back(i, 0);
        for(int j = 0; j < 20; ++j) {
            int nxt = i ^ (1 << j);
            if(nxt < i) {
                out[i + n].emplace_back(nxt + n, 0);
            } else if(nxt <= n) {
                out[i + n].emplace_back(nxt + n, k << j);
            }
        }
    }
    std::vector<llsi> dis(2 * n + 1, 0x3f3f3f3f3f3f3f3fll);
    std::vector<bool> vis(2 * n + 1, false);
    std::priority_queue<std::pair<llsi, int>> pq;
    pq.push({0, 1}), dis[1] = 0;
    while(!pq.empty()) {
        auto [_dis, cur] = pq.top(); pq.pop();
        if(vis[cur]) continue;
        vis[cur] = true;
        for(auto [out, len]: out[cur]) if(dis[cur] + len < dis[out]) {
            dis[out] = dis[cur] + len;
            pq.push({-dis[out], out});
        }
    }
    for(int i = 2; i <= nn; ++i) std::cout << dis[i] << char(i == n ? 10 : 32);
}

int main() {
    std::ios::sync_with_stdio(false);
    int t; std::cin >> t; while(t--) work();
    return 0;
}

圣芙蕾雅

不可做题,弃疗


绘世之卷

开场看的就是这两个中二感爆棚的题,结果都是不可做题


抓拍

挺有意思的一个题

考虑把四个边界关于时间 \(t\) 的式子找出来,以 \(x\) 的最小值为例,我们先找出往西走的人的横坐标的最小值

如果存在横坐标更小的往其它方向走的人,那么 \(x\) 的最小值会在某个时刻变更表达式,因此可以得到一个分段函数

由于题目要求的是周长,所以任何时刻每条边长都是关于时间 \(t\) 的一次函数,因此找到分段函数所有分界点代回去暴力检验即可

#include<cstdio>
#include<iostream>
#include<set>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e18;
const int dx[4]={1,-1,0,0},dy[4]={0,0,-1,1};
int n,x[N],y[N],mnx[4],mxx[4],mny[4],mxy[4]; char s[10],ch[N];
signed main()
{
    RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
    scanf("%lld%lld%s",&x[i],&y[i],s),ch[i]=s[0];
    auto trs=[&](const char& ch)
    {
        switch (ch)
        {
            case 'E': return 0;
            case 'W': return 1;
            case 'S': return 2;
            case 'N': return 3;
            default: return -1;
        }
    };
    for (i=0;i<4;++i) mnx[i]=mny[i]=INF,mxx[i]=mxy[i]=-INF;
    for (i=1;i<=n;++i)
    {
        int tp=trs(ch[i]);
        mnx[tp]=min(mnx[tp],x[i]);
        mxx[tp]=max(mxx[tp],x[i]);
        mny[tp]=min(mny[tp],y[i]);
        mxy[tp]=max(mxy[tp],y[i]);
    }
    set <int> key; key.insert(0);
    if (mxx[0]!=-INF)
    {
        int mx=max(mxx[2],mxx[3]);
        if (mxx[0]<mx) key.insert(mx-mxx[0]);
        mx=mxx[1];
        if (mxx[0]<mx) key.insert((mx-mxx[0])/2),key.insert((mx-mxx[0]+1)/2);
    }
    if (mnx[1]!=INF)
    {
        int mn=min(mnx[2],mnx[3]);
        if (mnx[1]>mn) key.insert(mnx[1]-mn);
        mn=mnx[0];
        if (mnx[1]>mn) key.insert((mnx[1]-mn)/2),key.insert((mnx[1]-mn+1)/2);
    }
    if (mny[2]!=INF)
    {
        int mn=min(mny[0],mny[1]);
        if (mny[2]>mn) key.insert(mny[2]-mn);
        mn=mny[3];
        if (mny[2]>mn) key.insert((mny[2]-mn)/2),key.insert((mny[2]-mn+1)/2);
    }
    if (mxy[3]!=INF)
    {
        int mx=max(mxy[0],mxy[1]);
        if (mxy[3]<mx) key.insert(mx-mxy[3]);
        mx=mxy[2];
        if (mxy[3]<mx) key.insert((mx-mxy[3])/2),key.insert((mx-mxy[3]+1)/2);
    }
    int ans=INF;
    for (auto t:key)
    {
        int res=0,xl=INF,xr=-INF,yl=INF,yr=-INF;
        for (i=1;i<=n;++i)
        {
            int tp=trs(ch[i]),nx=x[i]+t*dx[tp],ny=y[i]+t*dy[tp];
            xl=min(xl,nx); xr=max(xr,nx);
            yl=min(yl,ny); yr=max(yr,ny);
        }
        ans=min(ans,(xr-xl)+(yr-yl));
    }
    return printf("%lld",2LL*ans),0;
}

死亡之组

签到题,简单分类讨论即可

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

const int N = 1e5+5;
int t, n, L, D, A[N], id[N];
signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> t;
    while (t--){
        cin >> n >> L >> D;
        for (int i=1; i<=n; ++i) cin >> A[i], id[i]=i;
        sort(id+2, id+n+1, [&](int a, int b){return A[a]<A[b];});
        if (A[1]>=L){
            if (A[id[4]]<L && A[1]-A[id[2]]>D){
                cout << "Yes\n";
            }else cout << "No\n";
        }else{
            if (A[id[3]]<L && A[id[n]]-min({A[id[1]], A[id[2]], A[id[3]]})>D){
                cout << "Yes\n";
            }else cout << "No\n";
        }
    }
    return 0;
}

Postscript

一周四场多校还是有点爽歪歪的说

posted @ 2024-07-26 19:49  空気力学の詩  阅读(885)  评论(0编辑  收藏  举报