2024牛客暑期多校训练营10

Preface

最后一场牛客多校了,本来感觉可以来个完美谢幕的结果最后 2h 和祁神双开双卡

本来开局就因为大雨导致晚开+浑身湿透,中间一直冷的发抖硬是撑了下来

J 题写法因为常数问题一直卡着十连重测;而 C 题因为有概率为 \(0\) 的情况需要繁琐的判断,最后两个题都没过

赛后我用 priority_queue 实现的可删除堆替换 set 把 J 题卡过了,我只能说真是吃了一坨大的


Surrender to My Will

签到,读懂题就行,只能说开局因为我在擦衣服导致没看题给祁神写了,不然我一秒读完题把这题秒了

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

signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    string str; cin >> str;
    int cnty=0, cntn=0, cnt_=0;
    for (int i=0; i<5; ++i){
        if ('Y'==str[i]) ++cnty;
        if ('N'==str[i]) ++cntn;
        if ('-'==str[i]) ++cnt_;
    }
    
    if (cnty>=4) cout << "1\n";
    else if (cntn>1) cout << "-1\n";
    else cout << "0\n";
    return 0;
}

std::pair

神秘模拟题,徐神开场写的我题目都没看

#include <bits/stdc++.h>

enum {
    INT, DOUBLE, PAIR  
};

struct Type {
    int t;
    Type *first, *second;
    Type (int type, Type *first = nullptr, Type *second = nullptr) {
        this->t = type;
        this->first = first;
        this->second = second;
    }
    void print() {
        if(t == INT)    std::cout << "int"; else
        if(t == DOUBLE) std::cout << "double"; else
        {
            std::cout << "pair<";
            first->print();
            std::cout << ",";
            second->print();
            std::cout << ">";
        }
    }
};

Type* parse_type(const char *&s) {
    while(*s == ' ' || *s == ',' || *s == '<' || *s == '>') s++;
    const char *p = s;
    while(std::isalpha(*s)) s++;
    if(strncmp(p, "int", s - p) == 0)    return new Type(INT);
    if(strncmp(p, "double", s - p) == 0) return new Type(DOUBLE);
    if(strncmp(p, "pair", s - p) == 0)   return new Type(PAIR, parse_type(s), parse_type(s));
    std::cout << "Unreachable\n";
    exit(1); 
}

std::map<std::string, Type*> var;

int main() {
    std::ios::sync_with_stdio(false);
    int n, q; std::cin >> n >> q;
    for(int i = 1; i <= n; ++i) {
        std::string type, name;
        std::cin >> type >> name;
        name.pop_back();
        const char* p = type.c_str();
        var[name] = parse_type(p);
    }
    while(q--) {
        std::string query; std::cin >> query;
        const char *p = query.c_str();
        while(*p && *p != '.') p++;
        Type *t = var[query.substr(0, p - query.c_str())];
        while(*p) {
            while(*p == '.') p++;
            const char *q = p;
            while(*p && *p != '.') p++;
            if(p - q == 5) t = t->first;
            else           t = t->second;
        }
        t->print();
        std::cout << char(10);
    }
    return 0;
}

Capability Expectation

好像是祁神好像第三场遇到这道概率凸包题了,结果这次有概率为 \(0\) 又没 Rush 出来

祁神赛后发现原来是忘记清空了,但赛时 WA 的红温了没看出来,只能说可惜

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

using LD = long double;
const LD eps = 1e-8;

const int N = 2005;
int t, n;
LD P[N];
struct Pt{
	int x, y;
	Pt operator-(Pt b)const{return Pt{x-b.x, y-b.y};}
	int crs(Pt b)const{return x*b.y-y*b.x;}
	int len2()const{ return x*x+y*y;}
	int quad()const{
		if (x>0 && y>=0) return 1;	
		if (x<=0 && y>0) return 2;	
		if (x<0 && y<=0) return 3;	
		if (x>=0 && y<0) return 4;	
        return -1;
	}
}pt[N];

struct Vec{
	Pt p; int qd, len2;
	int id;
	bool operator<(Vec &b)const{
		int crs = p.crs(b.p);
		return (qd!=b.qd ? qd<b.qd : (crs!= 0 ? crs>0 : (len2<b.len2)));
	}
	LD calc(Pt s)const{
        return pt[id].crs(s)*0.5L;
	}
};

LD ans=0;
void solve(vector<Vec> &vec, int s){
	sort(vec.begin(), vec.end());
	int sz=vec.size();
	for (int i=0; i<sz; ++i) vec.push_back(vec[i]), vec[sz+i].qd+=4;
	// for (int i=0; i<2*sz; ++i) printf("%lld ", vec[i].id); puts("");
	LD res=1;
	for (int i=0, j=1; i<sz; ++i){
		Vec ts = vec[i]; ts.p.x = -ts.p.x; ts.p.y = -ts.p.y; ts.qd+=2; ts.len2 = (int)1e18+5;
        j = max(i+1, j);
        bool ok=true;
		while (j<=2*sz-1 && vec[j]<ts){
            if (1.L-P[vec[j].id] <= eps){
                ok=false; break;
            }
            res *= (1.L-P[vec[j].id]);
            ++j;
        }
        // printf("i=%lld j=%lld ok=%d\n", i, j, ok);
        if (!ok){
            i = j-1;
			res = 1.0l;
            continue;
        }
        ans += P[s]*P[vec[i].id]*res*vec[i].calc(pt[s]);
        if (j>i+1) res /= (1-P[vec[i+1].id]);
        else res = 1;
	}
}


signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
    cout << setiosflags(ios::fixed) << setprecision(10);
	cin >> t;
	while (t--){
        // printf("t=%lld\n", t);
		cin >> n;
		for (int i=1; i<=n; ++i){
			cin >> P[i] >> pt[i].x >> pt[i].y;
			P[i] = (1-P[i]);
		}
        if (n<=2){
            cout << "0\n";
            continue;
        }
		
		ans = 0;
		for (int i=1; i<=n; ++i){
			vector<Vec> vec;
			for (int j=1; j<=n; ++j) if (i!=j){
				Pt tmp = pt[j]-pt[i];
				vec.push_back(Vec{pt[j]-pt[i], tmp.quad(), tmp.len2(), j});
			}
			solve(vec, i);
		}
		cout << ans << '\n';
	}
	return 0;
}

Is it rated?

被徐神一眼看穿秒了

注意到 \(k\ge 0.1\),因此拿计算器算一下会发现只有最后约 \(400\)​ 场比赛能产生贡献

因此倒着大力 DP,令 \(f_{i,j}\) 表示考虑 \(i\sim n\) 的比赛,选中其中 \(j\) 场得到的最大贡献,其中第二维小于某个定值

#include <bits/stdc++.h>

using real = long double;

constexpr int $n = 100005;
constexpr int S = 500;

real p[$n], suf[$n][S + 1], kk[$n];

void work() {
    int n, m, r0; real k;
    std::cin >> n >> m >> k;
    for(int i = 0, P = 0; i <= n; ++i) std::cin >> P, p[i] = P;
    
    p[0] /= k;
    
    kk[0] = 1.L;
    for(int i = 1; i <= n; ++i) kk[i] = kk[i - 1] * (1.L - k);
    
    real ans = 0;
    for(int i = 1; i <= S; ++i) suf[n + 1][i] = -1e30;
    for(int i = 1; i <= n; ++i) suf[i][0] = 0;
    for(int i = n; i >= 0; --i) for(int j = 1; j <= S; ++j) {
        if(n - i + 1 - j > m) continue;
        suf[i][j] = std::max(p[i] * kk[j - 1] + suf[i + 1][j - 1], suf[i + 1][j]);
        if(i == 0 || j == S) ans = std::max(ans, suf[i][j]);
    }
    std::cout << ans * k << char(10);
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cout << std::fixed << std::setprecision(12);
    int T; std::cin >> T; while(T--) work();
}

Collinear Exception

考虑大力维护每个位置是否可加入点,如果可以加入则考虑将该点和之前加入的所有点之间直线上的点都设为不可加入

由于能加入的点个数大约是 \(O(n)\) 的,并且在枚举删除点时跑不满,因此复杂度为不满的 \(O(n^3)\),实测可以 299ms 通过

#include <bits/stdc++.h>

int n, count = 0;
bool forbidden[1001][1001];
std::vector<std::pair<int, int>> succ;
std::string res;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin >> n;
    res.reserve(n * n);
    for(int i = 1, x, y; i <= n * n && count <= n * n; ++i) {
        std::cin >> x >> y;
        if(forbidden[x][y]) {
            res.push_back('0');
            continue;
        }
        res.push_back('1');
        for(auto [px, py]: succ) {
            int dx = x - px, dy = y - py, g = std::__gcd(std::abs(dx), std::abs(dy));
            dx /= g, dy /= g;
            for(int cx = x, cy = y; count <= n * n && 0 < cx && cx <= n && 0 < cy && cy <= n; cx -= dx, cy -= dy)
                if(forbidden[cx][cy] == false) forbidden[cx][cy] = true, count += 1;// std::cout << "cx, cy = " << cx << ", " << cy << char(10);
            for(int cx = x, cy = y; count <= n * n && 0 < cx && cx <= n && 0 < cy && cy <= n; cx += dx, cy += dy)
                if(forbidden[cx][cy] == false) forbidden[cx][cy] = true, count += 1;
        }
        succ.emplace_back(x, y);
//         for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
//             std::cout << forbidden[i][j] << char(j == n ? 10 : 32);
//         std::cout << "count = " << count << char(10);
//         std::cout << "-----------\n";
    }
    while(res.size() < n * n) res.push_back('0');
    std::cout << res << std::endl;
    return 0;
}


All-in at the Pre-flop

徐神大胆猜测答案就是 \(\frac{a}{a+b},\frac{b}{a+b}\),然后我搞了个蒙特卡洛检验了下感觉挺对的,交上去就过了可海星

from math import pow

MOD = 998244353
def ksm(a, b):
    global MOD
    c = 1
    while b > 0:
        if b & 1:
            c = c * a % MOD
        a = a * a % MOD
        b >>= 1
    return c

a, b = map(int, input().split())

c = ksm(a + b, MOD - 2)

print("%d %d" % (a * c % MOD, b * c % MOD))

Riffle Shuffle

赛时一直在写 J 都没时间想这个题,赛后看了 Tutorial 发现还是很有意思的

难点在于要想到倒着操作,并用拆分上升序列的方法来构造,具体方法和证明都参照题解即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int t,n;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d",&n);
        vector <int> tar(n),p(n);
        for (RI i=0;i<n;++i) tar[i]=i;
        for (RI i=0;i<n;++i) scanf("%d",&p[i]),--p[i];
        vector <string> ans;
        while (p!=tar)
        {
            vector <int> pos(n);
            for (RI i=0;i<n;++i) pos[p[i]]=i;
            vector <pair <int,int>> seqs;
            for (RI i=0,j;i<n;i=j+1)
            {
                for (j=i;j+1<n&&pos[j+1]>pos[j];++j);
                seqs.push_back({i,j});
            }
            //for (auto [l,r]:seqs) cout<<l<<' '<<r<<endl;
            string tmp=string(n,'B');
            for (RI i=0;i<seqs.size();i+=2)
            for (RI j=seqs[i].first;j<=seqs[i].second;++j) tmp[pos[j]]='A';
            ans.push_back(tmp);
            vector <int> np;
            for (RI i=0;i<n;++i) if (tmp[i]=='A') np.push_back(p[i]);
            for (RI i=0;i<n;++i) if (tmp[i]=='B') np.push_back(p[i]);
            p=np;
        }
        reverse(ans.begin(),ans.end());
        cout<<(int)ans.size()<<'\n';
        for (auto s:ans) cout<<s<<'\n';
    }
    return 0;
}

Doremy's Starch Trees

沟槽的我的线段树合并好像跑的还没启发式合并 set 快,赛时卡了半天一直十连重测给我整红温了,赛后随便改了个东西上去就过了

这题思路其实不难想,考虑在 \(Y\) 树上从叶子往上处理,每次就是判断 \(X\) 树上的某个点是否和一个集合内的点有边

考虑把一个集合内的点在 \(X\) 上的邻居存储起来,即需要一个数据结构支持在集合内加数;合并两个集合;判断一个数是否在集合中

这里用线段树合并处理一下,然后注意到题目只要求找一个合法的根,因此如果某次操作出现不合法的点,则其要么为 \(Y\) 的根;要么说明改组数据无解

在定下根后的原问题可以用并查集+ set 解决,最后总复杂度 \(O(n\log n)\),常数巨大

#include<cstdio>
#include<iostream>
#include<set>
#include<vector>
#include<utility>
#include<cctype>
#include<queue>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef pair <int,int> pi;
const int N=1e6+5;
int t,n,x,y,rt[N],fa[N]; vector <int> A[N],B[N]; set <int> T2[N]; bool is_ok;
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        #define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
        char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[30];
    public:
        inline FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        Tp inline void write(T x,const char ch='\n')
        {
            if (x<0) pc('-'),x=-x;
            RI ptop=0; while (pt[++ptop]=x%10,x/=10);
            while (ptop) pc(pt[ptop--]+48); pc(ch);
        }
        inline void flush(void)
        {
            fwrite(Fout,1,Ftop-Fout,stdout);
        }
        #undef tc
        #undef pc
}F;
inline void merge(set <int>& A,set <int>& B) //A<-A+B
{
    if (A.size()<B.size()) swap(A,B);
    for (auto& x:B) A.insert(x); B.clear();
}
inline int getfa(CI x)
{
    return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
inline void DFS(CI now,CI anc=0)
{
    if (!is_ok) return;
    set <int> rst;
    for (auto to:B[now]) if (to!=anc)
    {
        DFS(to,now);
        if (!is_ok) return;
    }
    for (auto x:A[now]) rst.insert(getfa(x));
    for (auto to:B[now]) if (to!=anc)
    {
        if (!rst.count(getfa(to))) is_ok=0;
        if (!is_ok) return;
    }
    for (auto to:B[now]) if (to!=anc) fa[getfa(to)]=getfa(now);
}
inline bool check(CI rt)
{
    for (RI i=1;i<=n;++i) fa[i]=i;
    is_ok=1; DFS(rt); return is_ok;
}
class Segment_Tree
{
    private:
        int ls[N*40],rs[N*40],idx;
    public:
        #define TN CI l=1,CI r=n
        #define LS l,mid
        #define RS mid+1,r
        inline void clear(void)
        {
            for (RI i=1;i<=idx;++i) ls[i]=rs[i]=0; idx=0;
        }
        inline void update(int& now,CI pos,TN)
        {
            if (!now) now=++idx; if (l==r) return; int mid=l+r>>1;
            if (pos<=mid) update(ls[now],pos,LS); else update(rs[now],pos,RS);
        }
        inline int merge(CI x,CI y,TN)
        {
            if (!x||!y) return x|y; if (l==r) return x; int mid=l+r>>1;
            ls[x]=merge(ls[x],ls[y],LS); rs[x]=merge(rs[x],rs[y],RS); return x;
        }
        inline int query(CI now,CI pos,TN)
        {
            if (!now) return 0; if (l==r) return 1; int mid=l+r>>1;
            return pos<=mid?query(ls[now],pos,LS):query(rs[now],pos,RS);
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
class Deletable_Heap
{
	private:
		priority_queue <pi,vector <pi>,greater <pi>> add,del;
	public:
		inline void clear(void)
		{
			while (!add.empty()) add.pop();
			while (!del.empty()) del.pop();
		}
		inline void insert(const pi& it)
		{
			add.push(it);
		}
		inline void remove(const pi& it)
		{
			del.push(it);
		}
		inline bool empty(void)
		{
			while (!add.empty()&&!del.empty()&&add.top()==del.top()) add.pop(),del.pop();
			return add.empty();
		}
		inline pi top(void)
		{
			while (!add.empty()&&!del.empty()&&add.top()==del.top()) add.pop(),del.pop();
			return add.top();
		}
		inline void pop(void)
		{
			if (!add.empty()) add.pop();
		}
}HP;
int main()
{
	//freopen("35.in","r",stdin); freopen("J.out","w",stdout);
    for (F.read(t);t;--t)
    {
        F.read(n); for (RI i=1;i<=n;++i) A[i].clear(),B[i].clear(),T2[i].clear(),rt[i]=0;
        for (RI i=2;i<=n;++i)
        {
            x=i; F.read(y); A[x].push_back(y); A[y].push_back(x);
            SEG.update(rt[x],y); SEG.update(rt[y],x);
        }
        for (RI i=2;i<=n;++i)
        {
            x=i; F.read(y); B[x].push_back(y); B[y].push_back(x);
            T2[x].insert(y); T2[y].insert(x);
        }
        HP.clear(); int RT;
        for (RI i=1;i<=n;++i) HP.insert({T2[i].size(),i});
        while (!HP.empty())
        {
            auto [deg,now]=HP.top(); HP.pop();
            if (deg!=1) { RT=now; break; }
            int to=*T2[now].begin();
            //printf("now = %d; to = %d\n",now,to);
            if (!SEG.query(rt[now],to))
            {
                if (check(now)) RT=now; else RT=-1; break;
            }
            HP.remove({T2[to].size(),to});
            T2[to].erase(now);
            HP.insert({T2[to].size(),to});
            rt[to]=SEG.merge(rt[to],rt[now]);
        }
        F.write(RT); SEG.clear();
    }
    return F.flush(),0;
}

Doremy's IQ 2

首先不难发现最优策略一定是先往正方向走,到某个位置后在反向走,即最多回头一次

钦定初始时先往正方向走,考虑枚举走到哪个位置,接下来回头最多能拿几个数可以二分+贪心检验

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int t,n,x;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d",&n); vector <int> pos,neg; int zero=0;
        for (RI i=1;i<=n;++i)
        {
            scanf("%d",&x);
            if (x==0) ++zero; else
            if (x>0) pos.push_back(x);
            else neg.push_back(-x);
        }
        sort(pos.begin(),pos.end());
        sort(neg.begin(),neg.end());
        int ans=0;
        auto solve=[&](void)
        {
            for (RI i=0;i<pos.size();++i)
            {
                if ((int)pos.size()-1-i>=pos[i])
                {
                    int l=0,r=neg.size()-1,ret=-1;
                    while (l<=r)
                    {
                        int mid=l+r>>1;
                        if (-pos[i]+(int)neg.size()-1-mid>=neg[mid])
                        ret=mid,l=mid+1; else r=mid-1;
                    }
                    ans=max(ans,(i+1)+(ret+1));
                }
            }
        };
        solve(); swap(pos,neg);
        solve(); printf("%d\n",ans+zero);
    }
    return 0;
}

Tada!

这题的数据范围一看就是要爆搜启动了,但怎么优雅地搜算是个难点

考虑把转移关系看作一张图,则点数为 \(10^n\),每个点有 \(n\times (n+1)\) 种转移状态

如果我们定死一个起点,暴力分层图跑出它经过 \(0,1,2,\dots, 50\) 步后能到达的点集,这个复杂度就比较大了,再乘上 \(m\) 一眼跑不过

不妨转换思路,我们枚举可能的最终状态,注意到从 \(025\to 014\) 的过程其实和 \(000\to 099\) 的过程完全等价,因此通过偏移把起点全部移到全为 \(0\) 的初始状态,这样就只用搜索一次了

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,M=55;
const int pw10[6]={1,10,100,1000,10000,100000};
int t,n,m,vis[M][N],dis[M]; char s[M][10]; vector <int> v[N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m); int mxd=0,tot=pw10[n];
        for (RI i=0;i<tot;++i)
        {
            v[i].clear(); static int tmp[10]; int x=i;
            for (RI j=n;j>=1;--j) tmp[j]=x%10,x/=10;
            for (RI j=1;j<=n;++j) for (RI k=j;k<=n;++k)
            {
                static int nxt[10];
                for (RI l=1;l<=n;++l) nxt[l]=tmp[l];
                for (RI l=j;l<=k;++l) nxt[l]=(nxt[l]+1)%10;
                x=0; for (RI l=1;l<=n;++l) x=x*10+nxt[l];
                v[i].push_back(x);
                for (RI l=1;l<=n;++l) nxt[l]=tmp[l];
                for (RI l=j;l<=k;++l) nxt[l]=(nxt[l]+9)%10;
                x=0; for (RI l=1;l<=n;++l) x=x*10+nxt[l];
                v[i].push_back(x);
            }
        }
        for (RI i=1;i<=m;++i) scanf("%s%d",s[i]+1,&dis[i]),mxd=max(mxd,dis[i]);
        for (RI i=0;i<=mxd;++i) memset(vis[i],0,tot*sizeof(int));
        vis[0][0]=1;
        for (RI d=0;d<mxd;++d)
        {
            for (RI i=0;i<tot;++i) if (vis[d][i])
            for (auto to:v[i]) vis[d+1][to]=1;
        }
        int ans=-1; bool is_many=0;
        for (RI i=0;i<tot;++i)
        {
            static int tar[10]; int x=i;
            for (RI j=n;j>=1;--j) tar[j]=x%10,x/=10;
            bool is_ok=1;
            for (RI j=1;j<=m;++j)
            {
                static int tmp[10];
                for (RI k=1;k<=n;++k) tmp[k]=(tar[k]-(s[j][k]-'0')+10)%10;
                x=0; for (RI k=1;k<=n;++k) x=x*10+tmp[k];
                if (!vis[dis[j]][x]) { is_ok=0; break; }
            }
            if (is_ok)
            {
                if (ans==-1) ans=i;
                else { is_many=1; break; }
            }
        }
        if (is_many) puts("MANY"); else
        if (ans==-1) puts("IMPOSSIBLE"); else
        {
            static int tmp[10]; int x=ans;
            for (RI i=n;i>=1;--i) tmp[i]=x%10,x/=10;
            for (RI i=1;i<=n;++i) putchar(tmp[i]+'0');
            putchar('\n');
        }
    }
    return 0;
}

Postscript

这场后面打的确实有问题,感觉完全能出 \(9\) 题的,只能说中午那场大雨全责

posted @ 2024-08-15 19:39  空気力学の詩  阅读(367)  评论(0编辑  收藏  举报