隐藏页面特效

2023-2024 ACM-ICPC Latin American Regional Programming Contest

1|0Preface


这场终于找回一点感觉了,总体来说虽然有点唐但打的还不错

开场签到有点磕磕绊绊在加上前面只有我一个人在读题,因此出的比较慢

中期开始慢慢发力后祁神也放下了明天的数电考试复习过来助阵,轻松秒了一道几何

最后已经写了一个细节题L的徐神最后1h被另一个细节题G搞得屡次破防,最后也是没能调出,9题收场

比较可惜的是最后其实我搞出来了一个K的做法(基于随机),赛后写了下发现确实能过,但比赛时肯定选择冲更为稳妥的G,决策还是没问题的


2|0A. Analyzing Contracts


防AK题,弃


3|0B. Blackboard Game


被签到博弈腐乳辣

不难发现若每个数出现的次数都是3的倍数则后手必胜,否则先手必胜

#include<cstdio> #include<iostream> #include<map> #define RI register int #define CI const int& using namespace std; int n,x; map <int,int> c; int main() { //freopen("B.in","r",stdin); freopen("B.out","w",stdout); RI i; for (scanf("%d",&n),i=1;i<=3*n;++i) scanf("%d",&x),++c[x]; bool flag=1; for (auto [_,t]:c) if (t%3!=0) flag=0; return puts(flag?"N":"Y"),0; }

4|0C. Candy Rush


刚开始想了个数据分治的O(nnlogn)的做法,后面发现直接用Hash来做到O(nlogn)

区间问题很容易想到前缀和,我们这里用一个k维向量来表示前缀和,每一位上的数表示这一段前缀该值出现的次数

不难发现一个区间[l,r]合法的条件是prerprel1=k×(1,1,1,),其中k是任意自然数

而由于pre里所有数始终是不降的,我们可以将prei中的每一维都减去其中最小的数,这样就想到与要找prel1=prer

直接做显然很爆炸,但仔细一想我们可以借鉴字符串Hash的思路,直接给这个k维向量搞一个Hash值

由于这题的修改操作很简单,因此可以轻松维护,至于所有数的最小值可以用multiset乱搞一下

#include<cstdio> #include<iostream> #include<map> #include<set> #define RI register int #define CI const int& using namespace std; typedef long long LL; const int N=400005,mod1=998244353,mod2=1e9+7; int n,k,c[N],bkt[N],ans; map <LL,int> fst; multiset <int> s; struct Hasher { int x,y; inline Hasher(CI X=0,CI Y=0) { x=X; y=Y; } inline LL get_val(void) { return ((1LL*x)<<31LL)|(1LL*y); } friend inline bool operator == (const Hasher& A,const Hasher& B) { return A.x==B.x&&A.y==B.y; } friend inline Hasher operator + (const Hasher& A,const Hasher& B) { return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2); } friend inline Hasher operator - (const Hasher& A,const Hasher& B) { return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2); } friend inline Hasher operator * (const Hasher& A,const Hasher& B) { return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2); } }pw[N],cur,norm; const Hasher seed=Hasher(31,131); int main() { //freopen("C.in","r",stdin); freopen("C.out","w",stdout); RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&c[i]); for (pw[0]=Hasher(1,1),i=1;i<=k;++i) s.insert(0),pw[i]=pw[i-1]*seed,norm=norm+pw[i]; for (fst[0]=0,i=1;i<=n;++i) { s.erase(s.find(bkt[c[i]])); s.insert(++bkt[c[i]]); cur=cur+pw[c[i]]; int min_t=*s.begin(); Hasher tmp=cur-norm*Hasher(min_t,min_t); if (fst.count(tmp.get_val())) ans=max(ans,i-fst[tmp.get_val()]); else fst[tmp.get_val()]=i; } return printf("%d",ans),0; }

5|0D. Deciphering WordWhiz


签到模拟题,难点在于阅读题意

#include<cstdio> #include<iostream> #include<string> #define RI register int #define CI const int& using namespace std; const int N=1005; int n,m,ext[30]; string base,words[N]; int main() { //freopen("D.in","r",stdin); freopen("D.out","w",stdout); RI i,j; for (cin>>n,i=1;i<=n;++i) cin>>words[i]; base=words[1]; for (i=0;i<5;++i) ext[base[i]-'a']=1; for (cin>>m,i=1;i<=m;++i) { string res; cin>>res; int ans=0; auto comp=[&](string s) { string ret=""; for (RI i=0;i<5;++i) if (s[i]==base[i]) ret+="*"; else if (ext[s[i]-'a']) ret+="!"; else ret+="X"; return ret; }; for (j=1;j<=n;++j) if (comp(words[j])==res) ++ans; cout<<ans<<endl; } return 0; }

6|0E. Elevated Profits


题目都没看,防AK题+1


7|0F. Forward and Backward


徐神开场写的一个Easy题,我题目都没看

#include <bits/stdc++.h> using llsi = long long signed int; int check(llsi n, llsi base) { std::vector<llsi> num; while(n) num.push_back(n % base), n /= base; for(size_t i = 0; i < num.size(); ++i) if(num[i] != num[num.size() - i - 1]) return 0; return 1; } int main() { llsi n; std::cin >> n; std::vector<llsi> ans; llsi ue = std::min(n, 1000000ll); for(llsi i = ue; i >= 2; --i) if(check(n, i)) ans.push_back(i); for(llsi i = 1; i < ue; ++i) { llsi base = (n - i) / i; if(base && base > 1000000ll && n % base == i && n / base == i) ans.push_back(base); } std::sort(ans.begin(), ans.end()); if(ans.size()) for(size_t i = 0; i < ans.size(); ++i) std::cout << ans[i] << char(i == ans.size() - 1 ? 10 : 32); else std::cout << "*\n"; return 0; }

8|0G. GPS on a Flat Earth


这题思路很简单,就是不断地将所有斜45度的正方形求交,最后剩下来的点集就是答案

但因为一些不可名状的细节这题很容易写挂,徐神应该还在补这个题代码就先坑着


9|0H. Health in Hazard


祁神知道我们写不来几何特意跑过来秒了这道题

这题我题意都不知道,做法是二分+半平面交,感觉这两个算法结合的题目确实比较多

#include<bits/stdc++.h> using namespace std; #define int long long #define LD long double const LD eps = 1e-8; const LD INF = 1e9; int sgn(LD x){return fabs(x)<=eps ? 0 : (x > eps ? 1 : -1);} struct Pt{ LD x, y; Pt operator-(Pt b)const{return Pt{x-b.x, y-b.y};} Pt operator+(Pt b)const{return Pt{x+b.x, y+b.y};} Pt operator*(LD b)const{return Pt{x*b, y*b};} LD crs(Pt b)const{return x*b.y-y*b.x;} LD len()const{return sqrtl(x*x+y*y);} int quad()const{ if (sgn(x)>0 && sgn(y)>=0){return 1;} if (sgn(x)<=0 && sgn(y)>0){return 2;} if (sgn(x)<0 && sgn(y)<=0){return 3;} if (sgn(x)>=0 && sgn(y)<0){return 4;} } }; struct Line{ Pt p, v; Line(){} Line(Pt _p, Pt _v):p(_p), v(_v){} bool operator<(Line b)const{ if (v.quad()!=b.v.quad()) return v.quad()<b.v.quad(); else{ LD crs = v.crs(b.v); return sgn(crs)!=0 ? sgn(crs)>0 : sgn(v.crs(b.p-p))< 0; } } }; const Line bd[4] = { Line(Pt{-INF, -INF}, Pt{1, 0}), Line(Pt{INF, -INF}, Pt{0, 1}), Line(Pt{INF, INF}, Pt{-1, 0}), Line(Pt{-INF, INF}, Pt{0, -1}), }; Pt pt_l_l(Line a, Line b){return a.p+a.v*(b.v.crs(a.p-b.p)/(a.v.crs(b.v)));} bool halfPlane(vector<Line> &ln, vector<Pt> &res){ for (int i=0; i<4; ++i) ln.push_back(bd[i]); sort(ln.begin(), ln.end()); int cur=0; for (int i=1; i<(int)ln.size(); ++i){ if (sgn(ln[cur].v.crs(ln[i].v))!=0) ln[++cur]=ln[i]; } ln.erase(ln.begin()+cur+1, ln.end()); int sz = ln.size(); vector<Line> Ql(sz+5); vector<Pt> Qp(sz+5); int frl=0, edl=-1, frp=0, edp=-1; Ql[++edl] = ln[0]; for (int i=1; i<sz; ++i){ while (frp<=edp && sgn(ln[i].v.crs(Qp[edp]-ln[i].p))<=0) --edp, --edl; while (frp<=edp && sgn(ln[i].v.crs(Qp[frp]-ln[i].p))<=0) ++frp, ++frl; Qp[++edp] = pt_l_l(ln[i], Ql[edl]); Ql[++edl] = ln[i]; } while (frp<=edp && sgn(Ql[frl].v.crs(Qp[edp]-Ql[frl].p)<=0)) --edp, --edl; if (frl<edl) Qp[++edp] = pt_l_l(Ql[edl], Ql[frl]); if (edp-frp<2) return false; else{ res.clear(); res.insert(res.end(), Qp.begin()+frp, Qp.begin()+edp+1); return true; } } const int N = 2e5+5; int n; LD D; Line ln[N]; bool check(int x){ vector<Line> vec; for (int i=1; i<=x; ++i) vec.push_back(ln[i]); vector<Pt> ch; halfPlane(vec, ch); int sz=ch.size(); bool ok=false; for (int i=0; i<sz; ++i){ // printf("i=%lld (%Ld %Ld)\n", i, ch[i].x, ch[i].y); if (sgn((ch[i]).len()-D)>0){ ok=true; break; } } return ok; } signed main(){ scanf("%lld%Lf", &n, &D); for (int i=1; i<=n; ++i){ int x1, y1, x2, y2; scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2); Pt a=Pt{x1, y1}, b=Pt{x2, y2}; if (sgn((b-a).crs(Pt{0, 0}-a)<0)) swap(a, b); ln[i] = Line(a, b-a); } // for (int i=1; i<=n; ++i){ // printf("i=%lld (%Lf %Lf)(%Lf %Lf)\n", i, ln[i].p.x, ln[i].p.y, ln[i].v.x, ln[i].v.y); // } if (check(n)){ printf("*\n"); }else{ int L=1, R=n; while (L<R){ int M = L+(R-L)/2; if (check(M)) L=M+1; else R=M; } printf("%lld\n", L); } return 0; }

10|0I. Inversions


一个Easy数数题,根据逆序对的统计方法手玩一下推个式子即可

对于原来的某个元素si,设原串中在它之后并且小于si的元素个数为A,整个原串小于si的元素个数为B

si的贡献就是首项为A,公差为B,项数为n的等差数列之和

#include<cstdio> #include<iostream> #include<cstring> #define int long long #define RI register int #define CI const int& using namespace std; const int N=100005,mod=1e9+7,inv2=(mod+1)/2; int n,t,bkt[30],A[N],B[N],ans; char s[N]; signed main() { //freopen("I.in","r",stdin); freopen("I.out","w",stdout); RI i,j; for (scanf("%s%lld",s+1,&t),n=strlen(s+1),i=n;i>=1;--i) { for (j=s[i]-'a'-1;j>=0;--j) A[i]+=bkt[j]; ++bkt[s[i]-'a']; } for (i=1;i<=n;++i) { for (j=s[i]-'a'-1;j>=0;--j) B[i]+=bkt[j]; (ans+=(2LL*A[i]+(t-1)*B[i])%mod*(t%mod)%mod*inv2%mod)%=mod; } return printf("%lld",ans),0; }

11|0J. Journey of the Robber


本来想了个比较复杂的点分树做法,后面在徐神提醒下发现定序之后就很简单,遂爬上去20min写完一发入魂

首先树上距离经典考虑点分治,但这题我们不直接来搞而是先把点分树建出来

考虑按编号从大到小依次将点激活,我们询问一个点的时候只要沿着这个点在点分树上一直向上跳,在每个点处查询到子树内距离最近的点即可

由于这题问的是最小因此不需要容斥来自自己子树的也不会错,因此修改也是类似地往上更新即可

如果用O(1)求LCA的话这题复杂度就是一个log的,但时限4.5s不用白不用直接倍增走起

#include<cstdio> #include<iostream> #include<vector> #include<utility> #define RI register int #define CI const int& #define fi first #define se second using namespace std; const int N=100005,INF=1e9; typedef pair <int,int> pi; int n,x,y,ans[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<18;++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) { RI i; if (dep[x]<dep[y]) swap(x,y); for (i=19;i>=0;--i) if (dep[anc[x][i]]>=dep[y]) x=anc[x][i]; if (x==y) return x; for (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]; pi mi[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),fa[rt]=now,solve(rt); } inline void divide(CI n) { for (RI i=1;i<=n;++i) mi[i]=pi(INF,0); mx[rt=0]=INF; ots=n; getrt(); solve(rt); //for (RI i=1;i<=n;++i) if (fa[i]) printf("%d->%d\n",fa[i],i); } } using namespace Point_Divide_Tree; int main() { //freopen("J.in","r",stdin); freopen("J.out","w",stdout); RI i; for (scanf("%d",&n),i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x); for (T.DFS(),divide(n),i=n;i>=1;--i) { if (i!=n) { pi res=pi(INF,0); for (x=i;x;x=fa[x]) res=min(res,pi(mi[x].fi+T.getdis(i,x),mi[x].se)); ans[i]=res.se; } else ans[i]=n; for (x=i;x;x=fa[x]) mi[x]=min(mi[x],pi(T.getdis(i,x),i)); } for (i=1;i<=n;++i) printf("%d ",ans[i]); return 0; }

12|0K. Keen on Order


玄学题,如果比赛时候知道随机真能跑过我早就爬上去20min秒了这个题了

首先注意到当k较大时显然总存在某个排列满足题意,因为直觉感觉构造一个不存在某个排列不是其子序列的序列长度是O(k2)级别的

因此不妨直接数据分治,当k>20时直接大力随机排列并检验,而k20时可以用经典的状压来处理

fmask表示已经在排列中填了状态为mask的数时匹配到原序列的最靠后的位置,转移的话非常trivial,这部分是O(2k×k)

#include<cstdio> #include<iostream> #include<vector> #include<algorithm> #include<cstdlib> #include<ctime> #define RI register int #define CI const int& using namespace std; const int N=305; int n,k,a[N],nxt[N][N],vis[N],f[1<<25],pre[1<<25]; int main() { //freopen("K.in","r",stdin); freopen("K.out","w",stdout); RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]); for (i=0;i<=n+1;++i) for (j=0;j<=n+1;++j) nxt[i][j]=n+1; for (i=0;i<=n;++i) for (j=i+1;j<=n;++j) nxt[i][a[j]]=min(nxt[i][a[j]],j); if (k>=20) { int ans[N]; for (i=1;i<=k;++i) ans[i]=i; for (srand(time(0));;) { random_shuffle(ans+1,ans+k+1); int lst=0; for (i=1;i<=k;++i) lst=nxt[lst][ans[i]]; if (lst>n) { for (i=1;i<=k;++i) printf("%d ",ans[i]); break; } } return 0; } for (i=0;i<(1<<k);++i) pre[i]=-1; for (f[0]=0,i=1;i<(1<<k);++i) { for (j=0;j<k;++j) if ((i>>j)&1) if (nxt[f[i^(1<<j)]][j+1]>f[i]) f[i]=nxt[f[i^(1<<j)]][j+1],pre[i]=j; } if (f[(1<<k)-1]<=n) return puts("*"),0; vector <int> ans; for (int S=(1<<k)-1;S;S=S^(1<<pre[S])) ans.push_back(pre[S]+1); reverse(ans.begin(),ans.end()); for (auto x:ans) printf("%d ",x); return 0; }

当然题解中给出了一种比较科学的构造方法,就是先找一个数使得其在原序列中的第一次出现位置尽可能靠后

然后第一个位置放这个数,接下来再找之前没出现过的并且在这之后的第一次出现位置尽可能靠后的数,以此类推

不难发现这样找出的序列长度是k+k1++1级别的,因此也可以通过

#include<cstdio> #include<iostream> #include<vector> #include<algorithm> #define RI register int #define CI const int& using namespace std; const int N=305; int n,k,a[N],nxt[N][N],vis[N],f[1<<25],pre[1<<25]; int main() { //freopen("K.in","r",stdin); freopen("K.out","w",stdout); RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]); for (i=0;i<=n+1;++i) for (j=0;j<=n+1;++j) nxt[i][j]=n+1; for (i=0;i<=n;++i) for (j=i+1;j<=n;++j) nxt[i][a[j]]=min(nxt[i][a[j]],j); if (k>=20) { int lst=0; for (i=1;i<=k;++i) { int mx=0,pos=-1; for (j=1;j<=k;++j) if (!vis[j]&&nxt[lst][j]>mx) mx=nxt[lst][j],pos=j; vis[pos]=1; lst=nxt[lst][pos]; printf("%d ",pos); if (lst>n) break; } for (i=1;i<=k;++i) if (!vis[i]) printf("%d ",i); return 0; } for (i=0;i<(1<<k);++i) pre[i]=-1; for (f[0]=0,i=1;i<(1<<k);++i) { for (j=0;j<k;++j) if ((i>>j)&1) if (nxt[f[i^(1<<j)]][j+1]>f[i]) f[i]=nxt[f[i^(1<<j)]][j+1],pre[i]=j; } if (f[(1<<k)-1]<=n) return puts("*"),0; vector <int> ans; for (int S=(1<<k)-1;S;S=S^(1<<pre[S])) ans.push_back(pre[S]+1); reverse(ans.begin(),ans.end()); for (auto x:ans) printf("%d ",x); return 0; }

13|0L. Latam++


徐神我们的红太阳,开场秒了这个题,后面发现原来这是我们队这场写的过的人最少的一个题了

大致思路就是拿个栈来模拟,但需要讨论并且细节巨多,在数次破防后徐神总算是过了这个题

#include <bits/stdc++.h> using llsi = long long signed int; llsi ans = 0; bool calc(const char *&s) { llsi res = 0; llsi stack = 0; bool is_legal = true; char pre = '('; if(*s == ')') return false; for(;; /* std::cerr << *s << ' ' << is_legal << char(10) */) switch(*s) { case '\0': // std::cerr << "FUCK\n"; return false; case ')': s += 1; // std::cerr << "is_legal = " << is_legal << char(10); // std::cerr << "ans = " << ans << char(10); return is_legal && (islower(pre) || pre == ')'); case '+': case '-': case '*': case '/': if(!islower(pre) && pre != ')') is_legal = false, stack = 0; pre = *s++; break; case '(': s += 1; if(islower(pre) || pre == ')') { is_legal = false; if(!calc(s)) stack = 0; else ans += stack = 1; } else { if(calc(s)) { ans += stack += 1; } else { is_legal = false; stack = 0; } } pre = ')'; break; default: if(!islower(pre) && pre != '+' && pre != '-' && pre != '*' && pre != '/' && pre != '(') stack = 0, is_legal = false; pre = *s++; ans += stack += 1; } } int main(void) { std::ios::sync_with_stdio(false); std::string s; std::getline(std::cin, s); const char *ss = s.c_str(); while(*ss) { while(*ss && *ss == ')') ss += 1; calc(ss); } std::cout << ans << std::endl; return 0; }

14|0M. Meeting Point


小清新图论题,但感觉还是略显套路

首先以G为起点跑出一个最短路数组dis1,再以P为起点,并且将G从图中删去后再跑出一个最短路数组dis2

考虑枚举最后的终点T,首先显然要满足dis1P=dis1T,不然G就不是PT的中点了

其次还要检验是否所有PT的最短路都经过G,这点等价于dis2T>dis1P+dis1T

用Dijkstra预处理后枚举每个点依次判断即可

#include<cstdio> #include<iostream> #include<queue> #define int long long #define RI register int #define CI const int& #define Tp template <typename T> using namespace std; typedef pair <int,int> pi; const int N=100005,INF=1e18; int n,m,S,G,x,y,z,cnt; 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++) char Fin[S],*A,*B; public: 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())); } #undef tc }F; struct Graph { vector <pi> v[N]; int dis[N],vis[N]; inline void addedge(CI x,CI y,CI z) { v[x].push_back(pi(y,z)); v[y].push_back(pi(x,z)); } inline void Dijkstra(CI s) { for (RI i=1;i<=n;++i) dis[i]=INF; priority_queue <pi> hp; hp.push(pi(dis[s]=0,s)); while (!hp.empty()) { int now=hp.top().second; hp.pop(); if (vis[now]) continue; vis[now]=1; for (auto [to,w]:v[now]) if (dis[to]>dis[now]+w) hp.push(pi(-(dis[to]=dis[now]+w),to)); } } }A,B; signed main() { //freopen("M.in","r",stdin); freopen("M.out","w",stdout); RI i; for (F.read(n),F.read(m),F.read(S),F.read(G),i=1;i<=m;++i) { F.read(x); F.read(y); F.read(z); A.addedge(x,y,z); if (x!=G&&y!=G) B.addedge(x,y,z); } for (A.Dijkstra(G),B.Dijkstra(S),i=1;i<=n;++i) if (A.dis[S]==A.dis[i]&&B.dis[i]>2LL*A.dis[S]) printf("%lld ",i),++cnt; if (!cnt) puts("*"); return 0; }

15|0Postscript


拉美场果然适合给长时间没训练的人提信心


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/17957266.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(473)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
历史上的今天:
2023-01-10 末日下的恋歌——《秽翼的尤斯蒂娅》杂谈
点击右上角即可分享
微信分享提示