中国地质大学(武汉)2025年冬新生赛题解

news/2025/11/19 1:02:00/文章来源:https://www.cnblogs.com/BigSmall-En/p/19239942

前情提要

本来是想写一份非常非常详细的题解的,但是毕竟不是假期而且也想赶紧写出来,于是只能尽量精炼一点了。又因为有官方题解了,所以还是尽量写点实现细节吧。按个人过题顺序来写:ALCEIDBJHGMFK。

A. A+B-Problem

输入两个整数 \(A,B(1\leq A,B\leq 10^{100})\),输出 \((A+B)\) 的个位数。

以字符串形式输入,只考虑最后的字符即可。

string s,t;
int main(){cin>>s>>t;int x=s[s.length()-1]-'0';int y=t[t.length()-1]-'0';cout<<(x+y)%10ll<<endl;return 0;
}

直接 a+b 错了一发 qwq

L. Ep13.没有爱就看不见

输出一行字符串:"It is magic."

纯签到

int main(){puts("It is magic.");return 0;
}

C. 七千四百号的回响

给一个长为 \(n\) 的序列 \(a\)\(m\) 个操作每次将 \([l,r]\) 间的数取相反数,求最后序列所有数的和。

对于最后每个位置,要么为相反数(定义为状态 \(1\)),要么为本身(定义为状态 \(0\))。

那么每次相当于对 \([l,r]\) 内的位置的状态取反,相当于 \(\text{xor } 1\)。维护一个标记数组,故对 \(l\) 位置标记 \(\text{xor }1\)\(r+1\) 位置标记 \(\text{xor } 1\),则对标记数组求前缀异或和,前缀异或和对应改位置的翻转状态。

int n;
ll a[N];
int tag[N];int main(){scanf("%d",&n);for(int i=1;i<=n;++i){scanf("%lld",&a[i]);}int T;scanf("%d",&T);while(T--){int l,r;scanf("%d%d",&l,&r);tag[l]^=1,tag[r+1]^=1;}ll sum=0;for(int i=1;i<=n;++i){tag[i]^=tag[i-1];if(tag[i]==0)sum+=a[i];else sum-=a[i];}printf("%lld\n",sum);return 0;
}

E. 不畏苦暗

解骑士临光为了驱散整片卡西米尔的黑暗,将 \(n(1 \leq n \leq 2 \times 10^5)\) 根光枪插成一排。每个光枪有一个位置 \(x_i\) 和亮度 \(v_i (1 \leq x_i, v_i \leq 1 \times 10^9)\),对任一整数位置 \(x\),第 \(i\) 个光枪对该位置的影响遵循以下规则:

  • 记该位置与光枪的距离为 \(d\) (即从 \(x_i\) 向左/右走 \(d\) 个单位能到达 \(x\))
  • \(d = 0, 1, 2...v_i - 1\) 时,光枪在该位置提供的亮度分别为 \(v_i, v_i - 1, v_i - 2\dots 1\),也就是说,每远离光枪一个位置,亮度减少 1。
  • \(d \geq v_i\) 时,该光枪不照亮该位置,即提供的亮度为 0。

某个位置的实际亮度取所有光枪对该位置提供亮度中的最大值。

临光想知道,她到底给卡西米尔提供了多少光芒,请你计算所有整数位置实际亮度的总和。

\(n\) 个光枪将数轴分成了 \((n+1)\) 个区间。对于每个区间 \([l=x_i,r=x_{i+1}-1]\),我们如果能知道其从左端点向右照的最大亮度 \(lv\) 和从右端点向左照的最大亮度 \(rv\),就可以通过数学方法 \(O(1)\) 求区间求亮度之和。具体而言,区间答案 \(ans_i\)

\[ans_i=\sum_{i=l}^{r}\max\{lv-(i-l),rv-(r-i)\} \]

\(\max\) 中左边的式子看作斜率为负直线(自变量 \(i\)),右边式子看作斜率为正直线,求交点 \(mid=\frac{lv+l-rv+r}{2}\)(当然你可以认为下取整了),则答案变成

\[ans_i=\sum_{i=l}^{mid}(lv-i+l)+\sum_{i=mid+1}^{r}(rv-r+i) \]

这两个求和都可以使用等差数列求和公式计算。对于 \(mid\) 不在 \([l,r]\) 的情况,取两个求和区间在 \([l,r]\) 的部分即可。

现在我们要知道每个位置 \(x_i\)左向右照的最大亮度 \(lv\) 和从右向左照的最大亮度 \(r\),分别从左往右扫一遍和从右往左扫一遍,根据上一个位置的最大亮度和位置及这个位置的亮度,计算位置的最大亮度即可。

对于最左和最右的区间,只需考虑一个方向亮度即可。

注意一下亮度不能为负数的情况和边界情况,具体实现见代码。

int n;
array<ll,2>a[N];
ll lv[N],rv[N];
inline ll calc(ll x,ll y){if(x>y)swap(x,y);return (x+y)*(y-x+1)/2ll;
}int main(){scanf("%d",&n);for(int i=1;i<=n;++i){scanf("%lld%lld",&a[i][0],&a[i][1]);}sort(a+1,a+1+n);ll maxv=-INF;for(int i=1;i<=n;++i){maxv=maxv-(a[i][0]-a[i-1][0]);maxv=max(maxv,a[i][1]);lv[i]=maxv;}maxv=-INF;for(int i=n;i>=1;--i){maxv=maxv-(a[i+1][0]-a[i][0]);maxv=max(maxv,a[i][1]);rv[i-1]=maxv-1;}ll ans=0;for(ll i=1;i<n;++i){ll l=a[i][0],r=a[i+1][0]-1;//lv+l-x, rv-r+xll sm=(lv[i]+l-rv[i]+r)/2;if(lv[i]+l-sm>=0){if(sm>=l)ans+=calc(lv[i],lv[i]+l-min(r,sm));if(sm+1<=r)ans+=calc(rv[i],rv[i]-r+max(l,sm+1));continue;}ans+=calc(lv[i],max(0ll,lv[i]+l-r));ans+=calc(rv[i],max(0ll,rv[i]-r+l));}ans+=calc(lv[n],0);ans+=calc(rv[0],0);printf("%lld\n",ans);return 0;
}

也是拿下牛客 1a 了,这个题的确是实现大于思维。

I. 隔墙花影旧相知

给一个正整数 \(n\),将他的若干个因子排序后得到序列 \(d\),问有多少个位置 \(i\) 满足 \(d_i+1=d_{i+1}\)

\(n\leq 10^{12}\)

显然只有小于等于 \(10^6\) 的因子 \(d_{i}\) 才会参与到答案中,否则 \(d_i\times d_{i+1}=d_i(d_i+1)>10^{12}=n\)

故我们枚举每一个小于 \(10^6\) 的因子并判断是否合法即可。

int main(){ll x,ans=0;scanf("%lld",&x);for(ll i=2;i<=1000005;++i){if(x%i==0&&(x%(i-1)==0))++ans;}printf("%lld\n",ans);return 0;
}

D. 寻找哈基米

现在立希有一张导航地图,上面显示着她(S)和哈基米(T)的方位、空地(.)、建筑(#)和街道(M)。

立希可以在四个方向上移动,规则如下:

  • 普通移动:从当前格子移动到相邻的上/下/左/右格子,前提是该格子为 .ST代价为 1 步
  • 穿过街道:不能踩在 M 上,但允许跨过恰好一个相邻的 M 到它后面的格子(同一方向直线跳 2 格)。
    落点必须在网格内,且为 .ST代价也为 1 步
  • # 为不可通过的建筑,M 为街道,不可停留,只能被穿过;. 为可走空地。起点 S 和终点 T 视为可走格。

现在立希想知道她是否能到达哈基米的位置;若能到达,那么她走到哈基米位置的最少步数是多少。

广度优先搜索即可,具体实现见代码

const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};int n,m,Sx,Sy,Tx,Ty;
int dis[N][N];bool vis[N][N];
char s[N][N];int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;++i){scanf("%s",s[i]+1);for(int j=1;j<=m;++j){if(s[i][j]=='S'){Sx=i,Sy=j;s[i][j]='.';}if(s[i][j]=='T'){Tx=i,Ty=j;s[i][j]='.';}}}memset(dis,0x3f,sizeof(dis));dis[Sx][Sy]=0;vis[Sx][Sy]=1;queue<ttfa>q;q.push({Sx,Sy});while(!q.empty()){auto [x,y]=q.front();q.pop();for(int i=0;i<4;++i){int tx=x+dx[i],ty=y+dy[i];if(tx<1||tx>n||ty<1||ty>m||s[tx][ty]=='#')continue;if(s[tx][ty]=='.'){if(!vis[tx][ty]){vis[tx][ty]=1;dis[tx][ty]=dis[x][y]+1;q.push({tx,ty});}continue;}assert(s[tx][ty]=='M');tx+=dx[i],ty+=dy[i];if(tx<1||tx>n||ty<1||ty>m||s[tx][ty]=='#')continue;if(s[tx][ty]=='.'){if(!vis[tx][ty]){vis[tx][ty]=1;dis[tx][ty]=dis[x][y]+1;q.push({tx,ty});}continue;}}}if(vis[Tx][Ty])printf("%d\n",dis[Tx][Ty]);else puts("-1");return 0;
}

B. 共鸣护符

给定两个整数 \(L\)\(R\)\(1 \leq L \leq R \leq 10^5\)),设 \(n = R - L + 1\)
请构造一个长度为 \(n\) 的序列 \(v = [v_1, v_2, \ldots, v_n]\),使其为区间 \([L, R]\) 上所有整数的一个排列(即序列 \(v\)\(L, L+1, \ldots, R\) 每个数均出现且恰好出现一次),并满足如下条件:

对任意下标 \(1 \leq i < j \leq n\)

  • \(\gcd(v_i, v_j) \neq 1\),则必须有 \(v_i < v_j\)
  • \(\gcd(v_i, v_j) = 1\),则必须有 \(v_i > v_j\)

如果无法构造满足条件的序列,请输出 "NO";否则输出 "YES" 以及任意一个满足条件的序列。

  • \(n=1\),序列 \(v=[l]\) 合法

  • \(n=2\),由于相邻的两个数 \(\gcd\) 必为 \(1\),序列 \([l+1,l]\) 合法。

  • \(n=3\),分为两种情况。

    1. \(\gcd(l,l+2)=1\)(即 \(l\) 为奇数),则序列 \([l+2,l+1,l]\) 合法。
    2. \(\gcd(l,l+2)\neq 1\)(即 \(l\) 为偶数),找不到合法的序列,因为 \(l\) 应该在 \(l+2\) 前面,而根据相邻两数前后的结论,\(l\)\(l+1\) 后面,\(l+1\)\(l+2\) 后面。
  • \(n\geq 4\),可以发现序列一定同时包含 \(n=3\) 两种情况的子序列,由于第二种情况无法满足,故所有 \(n\geq 4\) 的序列无法满足。

int main(){int Test;scanf("%d",&Test);while(Test--){ll l,r;scanf("%lld%lld",&l,&r);if(r-l+1>=4){puts("NO");}else if(r-l+1>=3){if(__gcd(l,r)==1){puts("YES");printf("%lld %lld %lld\n",r,r-1,r-2);}else puts("NO");}else if(r-l+1>=2){puts("YES");printf("%lld %lld\n",r,l);}else{puts("YES");printf("%lld\n",l);}}return 0;
}

J. 伊波恩·弗塔根的抄本

阿卡姆城有 \(n\) 名活人祭品,伊格城有 \(m\) 名活人祭品。献祭规则如下:

假设 \(i, j\) 为阿卡姆城的两位祭品,\(k, l\) 为伊格城的两位祭品,如果纽带已经连接 \((i, k)\)\((i, l)\)\((j, k)\),则允许产生 \((j, l)\) 的连接。

在你来之前,两座城邦已经产生了 \(k\) 条纽带的连接。为表公平,两座城邦的大祭司将轮流执行献祭,阿卡姆城先手献祭。每次献祭可以选择一条允许连接的纽带进行连接。

如果最终某位大祭司已经没有允许的连接可供选择,那他便失去了觐见古神的机会。问谁能获胜。

这张图最后的边数是有限的,因此只需要判断连边次数的奇偶性即可,奇数次先手赢,偶数次后手赢。

这是一张二分图,记左边的点为 \(u_{1\sim n}\),记右边的点为 \(v_{1\sim m}\)

如果存在边 \(u_x\to v_y\)\(u_x\to v_z\),则我称这个点对 \((v_y,v_z)\) 被“激活”了,并加入到一个激活的集合中,如果有 \(u_w\) 和这个激活的集合中的任意一个点 \(v\) 有边,则 \(u_w\) 可以跟这个集合中的所有点 \(v\) 连边。

这个被激活的集合不止可以包含两个点,如果点对 \((v_1,v_2)\)\((v_2,v_3)\) 被激活,他们会进入同一个集合中,所有与这一个集合中任意一个点有边的 \(u\),都能和这个集合中所有的点连边。这样这个集合就是一个完全二分图中右侧的所有点。

我们枚举左端点,把所有能激活的右端点都激活,并把他们分配在若干个集合中(可以使用并查集维护)。对于一个大小为 \(y\) 集合,找出与之相连的左端点个数 \(x\),则这张子图最后的边个数为 \(x\times y\),又可以知道原来的边数,则能计算出新加的边数。

具体实现见代码。

int n,m,k,fa[N];
inline int fidf(int x){return x==fa[x]?x:fa[x]=fidf(fa[x]);
}
inline void merge(int x,int y){x=fidf(x),y=fidf(y);if(x==y)return;fa[y]=x;
}
vector<int>tar[N],bak[N];
vector<int>lis[N];int tot=0,num[N],idx[N];
int main(){scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=m;++i)fa[i]=i;for(int i=1;i<=k;++i){int u,v;scanf("%d%d",&u,&v);tar[u].push_back(v);bak[v].push_back(u);}for(int i=1;i<=n;++i){if(tar[i].size()>1){for(int j=1;j<(int)tar[i].size();++j){merge(tar[i][j],tar[i][j-1]);}}}for(int i=1;i<=m;++i){int x=fidf(i);++num[x];for(auto u:bak[i]){lis[x].push_back(u);}}ll ans=0;for(int i=1;i<=m;++i){if(num[i]<=1)continue;ll val=lis[i].size();sort(lis[i].begin(),lis[i].end());lis[i].erase(unique(lis[i].begin(),lis[i].end()),lis[i].end());ll cnt=lis[i].size();ans+=cnt*num[i]-val;}//printf("%lld\n",ans);if(ans&1ll)puts("Arkham");else puts("Yightek");return 0;
}

H. 魔女之旅

给一个序列长为 \(n\) 的序列 \(a\),求所有长度在 \([L,R]\) 范围的连续区间的最大平均值。

二分答案,判断当前二分出来的 \(val\) 能否小于等于某一个区间的平均值,如果可以,则看答案能否更大,否则答案一定更小。现在考虑如何判断 \(val\) 是否合法,考虑求前缀和 \(s_i=\sum_{i=1}^{i}a_i\),则如果 \(val\) 合法,存在一个区间 \([l+1,r]\) 满足

\[\frac{s_r-s_l}{r-l}\geq val\\ (s_r-val\times r)-(s_l-val\times l)\geq 0 \]

现在定义 \(b_x=s_x-val\times x\),问题转化为是否存在两个位置 \(0\leq l<r\leq n\)\(b_l<b_r\),与此同时有限制 \(r-l\in[L,R]\)。假设当前考虑 \(b_r\),实际上是找 \([r-R,r-L]\) 范围内的 \(b_i\) 最小值看看能否小于等于 \(b_r\)

一个连续平移范围内的最小值,可以使用单调队列,还有很多别的做法。具体见时间。

int n,L,R;
ll a[N],s[N],v[N];
int q[N*2];
inline bool check(ll x){ll minv=0;int l=1,r=0;for(ll i=1;i<=n;++i){int j=i-L;if(j>=0){while(l<=r&&i-q[l]>R)++l;while(l<=r&&v[q[r]]>v[j])--r;q[++r]=j;}ll val=s[i]-x*i;v[i]=val;//printf("%lld %lld\n",val,minv);if(l<=r&&val-v[q[l]]>=0)return 1;}return 0;
}int main(){scanf("%d%d%d",&n,&L,&R);for(int i=1;i<=n;++i){scanf("%lld",&a[i]);s[i]=s[i-1]+a[i];}ll l=0,r=1e10,ans=-1;while(l<=r){ll mid=(l+r)>>1;if(check(mid))l=mid+1,ans=mid;else r=mid-1;}printf("%lld\n",ans);return 0;
}

G. 猫猫虫困境III

在二维整数网格 \(Z^2\) 上,有 \(n\) 只猫猫虫,第 \(i\) 只初始位于非负整数坐标 \((x_i, y_i)\)
平面上固定有一个黑洞,坐标为 \((0, 0)\)
此外,平面上给定 \(m\) 个出口,出口的坐标分别为非负整数坐标 \((u_j, v_j)\)

时间离散为 \(t = 0, 1, 2, \ldots\),所有未离开的猫猫虫在每个整数时刻顺序进行如下三个阶段:

  1. 主动移动阶段:每只猫猫虫可以选择不动,或沿轴向主动移动一个单位(将 \(x\) 增减 1 或将 \(y\) 增减 1,两者不可同时进行)。假设猫猫虫此刻坐标为 \((x, y)\),那么其在该阶段可以移动到 \(\{(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)\}\) 中的一个位置。

  2. 吸引阶段:对于仍未离开的猫猫虫,黑洞会将其同时在两个坐标方向上各向黑洞靠近一个单位。若其此时位置为 \((x, y)\),则更新为 \((x - \text{sgn}(x), y - \text{sgn}(y))\)
    其中 \(\text{sgn}(x)\) 为符号函数。具体地,当 \(x < 0\) 时,\(\text{sgn}(x) = -1\); 当 \(x = 0\) 时,\(\text{sgn}(x) = 0\); 当 \(x > 0\) 时,\(\text{sgn}(x) = 1\)

  3. 判定阶段:判定此时所有还未离开的猫猫虫的位置,所有与出口位置重合的猫猫虫立即离开。一旦猫猫虫离开,便不再参与任何阶段。

多只猫猫虫互不影响;出口可以被多只猫猫虫在不同或相同时间使用。不要求出口两两不同;多只猫猫虫可以有相同的初始位置;猫猫虫的初始位置可以与出口重合,但不代表其可以直接离开。

你的任务是判断对每一只猫猫虫,是否存在一种策略,使其在有限时间内离开。

首先明确,由于坐标为负的地方没有出口,则将一个猫猫虫移动到坐标为负的地方是无意义且浪费步数的。

对于 \((x,y)(x>0,y>0)\) 的猫猫虫,如果主动移动阶段执行 \(x\leftarrow x+1\),则黑洞阶段会执行 \(x\leftarrow x-1,y\leftarrow y-1\),最终的效果为 \(y\leftarrow y-1\);如果主动阶段执行 \(y\leftarrow y+1\),则最终的效果 \(x\leftarrow x-1\)。也就是说,我们每次至少将横坐标或纵坐标减小 \(1\)(故主动将横纵坐标减小是不优的)。这样它可以走到所有横纵坐标均小于等于它的出口(除开这个位置本身)。

对于位于坐标轴上的猫猫虫,我们进行类似的分析,发现它只能原地不动,或者向原点移动一个单位。这样它可以走到所有横纵坐标均小于等于它的出口(包括这个位置本身)。

现在问题转化为,有若干个出口位置为 \((x',y')\),对于一个查询 \((x,y)\),要找是否有一个出口满足 \((0\leq x'\leq x,0\leq y'\leq y)\)

那么我们对所有横坐标排序(并将其离散化),求出口纵坐标的前缀最小值,对一个查询 \((x,y)\),看看前缀最小值是否小于 \(y\) 即可。

注意我们说对于不在坐标轴上的猫猫虫,由于与猫猫虫初始位置重合的出口无法走到,因此需要同时查询 \((x-1,y)\)\((x,y-1)\),其中有一个合法就合法。

int n,m;
array<ll,2>a[N],b[N];
ll lis[N*2],minv[N*2];int tot;int main(){memset(minv,0x3f,sizeof(minv));scanf("%d%d",&n,&m);for(int i=1;i<=n;++i){scanf("%lld%lld",&a[i][0],&a[i][1]);lis[++tot]=a[i][0];}for(int i=1;i<=m;++i){scanf("%lld%lld",&b[i][0],&b[i][1]);lis[++tot]=b[i][0];}sort(lis+1,lis+1+tot);tot=unique(lis+1,lis+1+tot)-lis-1;for(int i=1;i<=m;++i){int loc=lower_bound(lis+1,lis+1+tot,b[i][0])-lis;minv[loc]=min(minv[loc],b[i][1]);}//minv[0]=INF;for(int i=1;i<=tot;++i){minv[i]=min(minv[i],minv[i-1]);}for(int i=1;i<=n;++i){int loc=lower_bound(lis+1,lis+1+tot,a[i][0])-lis;if(a[i][0]==0){if(minv[loc]<=a[i][1])puts("YES");else puts("NO");continue;}if(a[i][1]==0){if(minv[loc]==0)puts("YES");else puts("NO");continue;}if(minv[loc-1]<=a[i][1]||minv[loc]<a[i][1])puts("YES");else puts("NO");}return 0;
}

M. 如果是勇者辛美尔的话

感觉官方题解写得真没毛病,这里直接复制粘贴,然后我说点细节即可。

问题等价转化为所有的激活点都落在某个半圆内,即激活节点集合的极角最大相邻间隔小于等于 \(\pi\) 的概率是多少。

考虑如何计算公式:为了不重复计算,我们令每个枚举的点为逆时针顺序最后激活的点。那么以该点逆时针旋转 \(\pi\) 的区域,该区域的所有点都不能被激活;再次旋转 \(\pi\) 回到该点的区域,该区域的所有点激活不激活无所谓,即

\[\sum_{i=1}^{n} p_i \prod_j^{\theta_j \in [\theta_i, \theta_i + \pi]} (1 - p_j) \]

连乘用前缀积维护即可。注意前缀积存在 \(0\) 时的处理。总复杂度为 \(O(n \log n)\)

实现时,将角度排序并把原序列角度 \(+2\pi\) 后复制一次,那么我们假设逆时针区域最后激活的点为 \(i\),计算时只需考虑下标大于 \(i\) 的点。

实际上题解中公式也并不完全严谨:

  • 要考虑所有点均不被激活的情况,概率为 \(\prod_{i=1}^{n}(1-p_i)\)

  • 不同点的角度可能相同,为了不重复计算答案,因此还要对点排序后编号,即要加上限制条件 \(j>i\),最后变成

    \[\prod_{i=1}^{n}(1-p_i) +\sum_{i=1}^{n} p_i \prod_{j+1}^{\theta_{j} \in [\theta_i, \theta_i + \pi]} (1 - p_j) \]

由于输入角度只有三位小数,故不用考虑 \(\theta+\pi\) 的精度问题。其余具体见代码

好的我又来多写几句了,因为我发现真的有人不会处理前缀积存在 \(0\) 的情况。对于 \((1-p_i)=0\) 的位置,如果它在要求的区间中,则答案为 \(0\),否则应该不影响答案,将他设成 \(1\) 即可;然后再额外开一个数组记录前缀中 \(0\) 的位置的个数,就可以判断区间中是否存在 \(0\) 了。当然如果有更好的写法也欢迎分享。

int n;
struct node{double ag;ll p;
}a[N];
inline bool cmp(node x,node y){return x.ag<y.ag;
}
double b[N];
ll p[N],mul[N],cnt[N];
int main(){scanf("%d",&n);for(int i=1;i<=n;++i){ll x=0,y=0;scanf("%lf%lld%lld",&a[i].ag,&x,&y);a[i].p=(x%P)*fpr(y%P)%P;}sort(a+1,a+1+n,cmp);mul[0]=1;for(int i=1;i<=2*n;++i){if(i<=n)b[i]=a[i].ag,p[i]=a[i].p;else b[i]=b[i-n]+2*pi,p[i]=p[i-n];if(p[i]==1){cnt[i]=cnt[i-1]+1;mul[i]=mul[i-1];}else{cnt[i]=cnt[i-1];mul[i]=mul[i-1]*(1-p[i]+P)%P;}}ll ans=(cnt[n]>=1?0:mul[n]);for(int i=1;i<=n;++i){int l=i+1,r=upper_bound(b+1,b+1+2*n,b[i]+pi)-b-1;if(b[l]-b[i]>pi||l>r){ans=(ans+p[i])%P;continue;}if(cnt[r]-cnt[l-1]>=1){ans+=0;continue;}ans=(ans+p[i]*mul[r]%P*fpr(mul[l-1])%P)%P;}printf("%lld\n",ans);return 0;
}

感觉是全场出得最好的一题,尽管这个前缀积为 \(0\) 有点烦。然后赛时读错题了写了个原点被三角形覆盖的期望次数,直接导致想复杂了。

F.邵接待之战

大模拟,题面太长了

如果出错了的话,可以再读一边题面,感觉把坑点都提到了。

直接被大模拟吓退了,其实看了下也不是很复杂,但还是懒得写,最后为了写题解也还是写了一下,很丑而且没封装。

#include <bits/stdc++.h>using namespace std;typedef long long ll;
typedef pair<ll,ll>ttfa;struct node{ll mx,x,my,y;int skip,cnt;
}a[2];
queue<ttfa>q[2];inline void addy(int i,ll v){a[i].y=min(a[i].my,a[i].y+v);
}
inline void dely(int i,ll v){a[i].y=max(0ll,a[i].y-v);
}
inline void del(int i,ll v){a[i].x=max(0ll,a[i].x-v);a[i].y=max(0ll,a[i].y-v);
}char instr[10];int main(){cin.sync_with_stdio(0);cin.tie(0);for(int i=0;i<2;++i){scanf("%lld%lld",&a[i].mx,&a[i].my);a[i].x=a[i].mx,a[i].y=a[i].my;}int R;scanf("%d",&R);for(int r=1;r<=R;++r){int c1,c2;scanf("%d%d",&c1,&c2);for(int i=1;i<=c1;++i){ll val;scanf("%s%lld",instr,&val);if(a[0].skip)continue;if(instr[0]=='A')q[0].push({1,val}),++a[0].cnt;if(instr[0]=='D')q[0].push({2,val});if(instr[0]=='H')q[0].push({3,val});}for(int i=1;i<=c2;++i){ll val;scanf("%s%lld",instr,&val);if(a[1].skip)continue;if(instr[0]=='A')q[1].push({1,val}),++a[1].cnt;if(instr[0]=='D')q[1].push({2,val});if(instr[0]=='H')q[1].push({3,val});}while(1){if(q[0].empty()&&q[1].empty())break;if(q[0].empty()&&a[1].cnt==0)break;if(q[1].empty()&&a[0].cnt==0)break;ll o0=0,c0=0,o1=0,c1=0;if(!q[0].empty())o0=q[0].front().first,c0=q[0].front().second;if(!q[1].empty())o1=q[1].front().first,c1=q[1].front().second;{if(o0==1||o0==2)q[0].pop();if(o1==1||o1==2)q[1].pop();//printf("action %d %d %d %d\n",o0,c0,o1,c1);a[0].cnt-=(o0==1);a[1].cnt-=(o1==1);if(o0==0){if(o1==1)del(0,c1);if(o1==3)q[1].pop();}if(o0==1){if(o1==1||o1==0){if(c0>c1)del(1,c0);if(c0<c1)del(0,c1);}if(o1==2){if(c0>c1)del(1,c0-c1);if(c0<c1)dely(0,c1-c0);}if(o1==3){if(c0>c1){del(1,c0);q[1].pop();}else addy(1,c1);}}if(o0==2){if(o1==1){if(c0>c1)dely(1,c0-c1);if(c0<c1)del(0,c1-c0);}if(o1==2){if(c0>c1)dely(1,c0);if(c0<c1)dely(0,c1);}if(o1==3){if(c0>c1){dely(1,c0);q[1].pop();}else addy(1,c1);}}if(o0==3){if(o1==0)q[0].pop();if(o1==1){if(c1>c0){del(0,c1);q[0].pop();}else addy(0,c0);}if(o1==2){if(c1>c0){dely(0,c1);q[0].pop();}else addy(0,c0);}if(o1==3){if(c0>c1){addy(0,c0);q[1].pop();}if(c0<c1){addy(1,c1);q[0].pop();}if(c0==c1){q[0].pop(),q[1].pop();}}}}if(a[0].x==0||a[1].x==0)goto finish;if(a[0].y==0){if(!a[0].skip)a[0].skip=r;a[0].cnt=0;while(!q[0].empty())q[0].pop();}if(a[1].y==0){if(!a[1].skip)a[1].skip=r;a[1].cnt=0;while(!q[1].empty())q[1].pop();}}if(a[0].skip&&r>a[0].skip){a[0].skip=0;a[0].y=a[0].my;}if(a[1].skip&&r>a[1].skip){a[1].skip=0;a[1].y=a[1].my;}}finish:printf("%lld %lld\n",a[0].x,a[1].x);return 0;
}

K. 黄金魔女的谜题

给定一棵以节点 \(i\) 为根的有限树, 树上有两名选手:Alice 和 Bob。初始时 Alice 位于节点 \(A\), Bob 位于节点 \(B\)(保证 \(A \neq B\)),两人轮流行动,Alice 先手。

  • 祖先 / 后代:均相对根 1 定义某节点的子树包含其自身及所有后代;某节点的祖先指从该节点沿父指针向上所能到达的所有节点(不含自身),其中与其距离为 1 的祖先称为父节点。
  • 标记:Alice 维护自己的标记集合 \(M_A\), Bob 维护自己的标记集合 \(M_B\), 两者相互独立, 初始两集合为空。一名选手若将某节点加入自己的集合,并不影响对方是否可以在其回合将同一节点加入其集合。
  • 代价:当某位选手在其行动中标记了若干节点时,需要支付这些被其标记节点权值之和,记为该选手在本局的总代价。移动到祖先的操作不需要支付代价。

行动规则:

在自己的回合中,当前选手(位于节点 \(u\))必须选择并执行以下两种操作之一:

  1. 向上跳:任选一个祖先节点 \(p\)(可为直接父节点,也可为更高层祖先),将自己的位置从 \(u\) 跳至 \(p\)。此操作不产生代价。若 \(u\) 无祖先(即 \(u = 1\)),则该操作不可选。

  2. 向下标记并跳:任选一个未被该选手标记的节点 \(x\),要求 \(x\) 属于当前节点 \(u\)子树(包含 \(u\)),支付代价 \(w[x]\) 将节点 \(x\) 加入该选手的标记集合,并将自己的位置从 \(u\) 跳至 \(x\)

注:操作 2 中的"未被标记"仅指该名选手自己的标记集合中不包含该节点;对手是否标记过该节点不作限制。

胜负判定:

  • 当执行完某名选手的一次行动后,若两名选手的位置处于同一节点,则执行这次行动的选手立即获胜,对局结束。
  • 如果轮到某名选手行动时,他没有任何合法行动(既不能向上跳,也无可标记的子树节点),则其对手立即获胜,对局结束。

可以证明,对局一定在有限步内结束。

在双方都以"使自己获胜"为目标且都采取最优策略的前提下:

  1. 判断谁能保证获胜(Alice 或 Bob)。
  2. 输出获胜者为确保胜利所需支付的最小总代价, 若获胜者在最优策略下无需进行任何标记操作,则代价为 0。这里只统计最终获胜者在其最优取胜策略下需要支付的总代价;对手的代价不计入。

首先,如果 \(lca(A,B)=B\),则 Bob 是 Alice 的祖先,Alice 可以执行操作 1 无代价地一步干掉 Bob。

如果 \(lca(A,B)=A\),则 Bob 是 Alice 子树中的一个点,Alice 是必胜的,并且一种获胜方法的代价是 \(w[B]\),但不一定是最优的,我们暂且按下不表。


对于其他的情况,记 \(lca(A,B)=C\)。可以发现,一旦有一名玩家率先走到了 \(C\)\(C\) 的祖先,另一名玩家还在 \(C\) 的某棵子树中,则这名玩家可以直接向上跳直接获胜。也就是说,对于任意一名玩家,他们都希望在自身所处的 \(C\) 的某棵子树(不含 \(C\))中尽量多走,直到对手走到 \(C\) 及其祖先。那么我们就要计算两名玩家在所属的 \(C\) 的子树中最多能走的步数 \(step_A\)\(step_B\)。(注意是 \(C\) 的某棵子树,而不是以 \(A\) 或以 \(B\) 为根的子树,因为两名玩家只要不走到 \(C\) 或更上都行。)如果 \(step_A>step_B\) 则 Alice 获胜,若 \(step_A\geq step_B\),则 Bob 获胜。

\(A\) 能向上走到的最浅节点为 \(a\)\(a\)\(C\) 的儿子),\(B\) 能向上走到的最浅节点为 \(b\),则 Alice 只能在以 \(a\) 为根的子树内走,Bob 只能在 \(b\) 为根的子树内走,直到一方走不了然后失败。

我们要尽量增加向上走的步数,因为他是无代价的,显然对于连续上多步,不如一步一步上。对于玩家 Alice,先让 \(A\)\(dep_{A}-dep_{a}\)无代价且最多步上到 \(a\) 节点,然后之后每次都可以选择 \(a\) 子树内的某个点 \(x\) 向下跳 \(1\) 步,花费代价 \(w_x\),接着无代价地走 \(dep_{x}-dep_a\) 步回到 \(a\) 节点,直到所有 \(a\) 子树内的点都被标记。这样进行总地操作次数最多,为 \(dep_A-dep_a+\sum_{x\in subtree(a)} (dep_x-dep_a+1)\)。对 Bob 也进行同样的操作。

可以粗略地这样理解:可以一开始花费 \(0\) 代价走 \(dep_A-dep_a\) 步,之后选择每次花费 \(w_x\) 代价 \(dep_{x}-dep_{a}+1\) 步,选择共有 \(siz_a\) 种。假设 Alice 能够获胜,那么她则要最小化走一定步数时的代价,这就变成了一个背包的问题。背包的大小是 \(n^2\) 级别的,而加入的物品个数也为 \(siz_a\) 个为 \(n\) 级别,直接背包是 \(O(n^3)\) 无法接受。发现 \(w\leq 100\),我们将问题转化为求花费代价为 \(w\) 时,最多能走多少步 \(l\)(原先为求走 \(l\) 步时,最小的花费 \(w\)),则背包空间优化为 \(nw\),时间复杂度为 \(O(n^2w)\),可以通过本题。


现在我们需要继续考虑 \(lca(A,B)=A\) 的情况了,如果 Alice 走到一个新位置 \(A’\),满足 \(lca(A',B)\) 不为 \(A'\)\(B\),我们可以根据刚刚的策略来计算代价。显然 Alice 只能向下走,但我们仍然不能枚举每一个 \(A'\),跑上面的方法,这样复杂度达到了 \(O(n\cdot n^2w)\)

可以发现走到 \(A'\) 的代价为 \(w_{A'}\),步数为 \(1\),然后这个时候我们走到 \(A'\) 能向上走到的最浅节点 \(a'\),无代价走步数 \(dep_{A'}-dep_{a'}\)。相当于走 \(dep_{A'}-dep_{a'}+1\) 步,花费代价 \(w_{A'}\),并且现在对于 \(a'\) 子树中的选择,不能再选择走到 \(A'\) 点。那么对于子树 \(a'\) 而言,无论一开始选择走到哪一个 \(A'\in subtree(a')\),始终可以看作每次选择花费 \(w_x\) 代价 \(dep_{x}-dep_{a'}+1\) 步,\(x\) 为子树 \(a'\) 中的所有点

则我们只需考虑 \(B\to A\) 的这条链上所有与链相连的子树即可。对于一颗子树 \(a'\),Alice 最多可以在其中走 \(\sum_{x\in subtree(a')}(dep_x-dep_{a'}+1)\) 次;而 Bob 能走上的最浅节点即为 \(b'\)\(b'\) 仍是链上的点,然后 \(b'\)\(a'\) 的父亲相同,为 \(lca(A',B)\),需要计算 Bob 能走的最多次数,才能判断输赢并计算最小代价。


现在来考虑一下实现,一个经典结论就是

\[\sum_{x\in subtree(u)}(dep_x-dep_u+1)=\sum_{x\in subtree(u)} {siz_x} \]

记录 \(sum_{u}=\sum_{x\in subtree(u)}siz_x\),一个子树 \(u\) 内能走的最大步数(不考虑一开始往上无代价走的步数)就为 \(sum_u\),一开始遍历树时就可以计算出来,然后后续查步数就可以 \(O(1)\) 计算。

然后我们设计一个函数计算一个子树内走 \(l\) 步时最小的花费 \(w\)(不考虑一开始往上无代价走)。就是一个刚才我们所说的 \(O(n^2w)\) 背包。对于额外的无代价走步数,对这个背包答案进行下标的变化即可。具体实现见代码。

typedef long long ll;
typedef pair<int,int>ttfa;
const int N=2003,W=100;
const ll llINF=0x3f3f3f3f3f3f3f3f;
const int INF=0x3f3f3f3f;int siz[N],dep[N],sum[N],n,A,B,a[N],fa[N];
vector<int>tar[N];void dfs(int u,int f){siz[u]=1;fa[u]=f;dep[u]=dep[f]+1;for(auto v:tar[u]){if(v==f)continue;dfs(v,u);sum[u]+=sum[v];siz[u]+=siz[v];}sum[u]+=siz[u];
}inline int __lca(int x,int y){if(dep[x]>dep[y])swap(x,y);while(dep[y]>dep[x])y=fa[y];while(x!=y)x=fa[x],y=fa[y];return x;
}int dp[N*N],lis[N],tot,f[N*W];
void getlis(int u,int f){lis[++tot]=u;for(auto v:tar[u]){if(v==f)continue;getlis(v,u);}
}
inline void calc(int rt){tot=0;getlis(rt,fa[rt]);for(int i=0;i<=sum[rt];++i)dp[i]=INF;/* n^3 做法dp[0]=0;for(int i=1,m=0;i<=tot;++i){int c=dep[lis[i]]-dep[rt]+1,v=a[lis[i]];m+=c;for(int j=m;j>=1;--j){dp[j]=min(dp[j],dp[max(0,j-c)]+v);}}*/// 正解 n^2w 做法for(int i=0;i<=W*tot;++i)f[i]=0;f[0]=0;for(int i=1,m=0;i<=tot;++i){int w=a[lis[i]],v=dep[lis[i]]-dep[rt]+1;m+=w;for(int j=m;j>=w;--j){f[j]=max(f[j],f[j-w]+v);}}for(int i=0;i<=W*tot;++i)dp[f[i]]=min(dp[f[i]],i);for(int i=sum[rt]-1;i>=0;--i)dp[i]=min(dp[i+1],dp[i]);
}int main(){scanf("%d%d%d",&n,&A,&B);for(int i=1;i<n;++i){int u,v;scanf("%d%d",&u,&v);tar[u].push_back(v);tar[v].push_back(u);}for(int i=1;i<=n;++i)scanf("%d",&a[i]);dfs(1,0);int lca=__lca(A,B),xx=0,yy=0,mov_A=0,mov_B=0;//printf("%d\n",lca);if(lca==B){puts("Alice");puts("0");return 0;}if(lca==A){int ans=a[B];xx=B;while(xx!=A){mov_B=sum[xx];for(auto u:tar[fa[xx]]){if(u==fa[fa[xx]]||u==xx)continue;calc(u);mov_A=sum[u];if(mov_A>mov_B)ans=min(ans,dp[mov_B+1]);}xx=fa[xx];}puts("Alice");printf("%d\n",ans);return 0;}xx=A,yy=B;while(fa[xx]!=lca)xx=fa[xx];while(fa[yy]!=lca)yy=fa[yy];mov_A=sum[xx]+dep[A]-dep[xx];mov_B=sum[yy]+dep[B]-dep[yy];if(mov_A>mov_B){puts("Alice");calc(xx);printf("%d\n",dp[max(0,mov_B+1-(dep[A]-dep[xx]))]);}else{puts("Bob");calc(yy);printf("%d\n",dp[max(0,mov_A-(dep[B]-dep[yy]))]);}return 0;
}

为什么我要提一嘴 \(O(n^3)\) 的做法,因为这个入一开始没想到要 \(O(n^2w)\),结果优化了一下还真过了,在代码中也有体现。

最后

写了好久,写到中间的时候觉得实在浪费时间,但是有的时候写着写着卡壳了,发现确实是自己有些细节还没想明白,那么看来这篇题解还是有他存在的意义的。最后感谢 CUG 出题组和志愿者的付出,希望以后能打上现场赛()。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/969526.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么一定能是三级缓存?

为什么一定能是三级缓存?为什么一定能是三级缓存?一、先定一个最小 Demo 场景 1. A / B 循环依赖 + AOP // 接口只是为了看得更清晰,可有可无 public interface AService {void foo(); }public interface BService …

KEYDIY KD B25-3 3-Button Universal Flip Remote Key for Hyundai - 5pcs/lot

Solving Hyundai Key Woes: The KEYDIY KD B25-3 Universal Flip Remote Key Problem: The Frustration of Hyundai Key Replacements For European and American automotive repair shops and car owners, finding a …

linux c 编程 入门

你好!欢迎来到Linux C编程入门的指导。Linux C编程是操作系统开发和系统级编程的重要组成部分,它允许开发者直接操作硬件、管理资源、编写高性能的程序等。一、Linux C编程入门基础 1. C语言基础变量类型:int, char…

linux c 目录 存在

要检查 Linux 系统中某个目录是否存在,可以使用以下几种常用命令: 方法 1:使用 ls 命令 ls -r /path/to/directory-r:递归显示(如果目录下有子目录)。 /path/to/directory:要检查的目录路径。方法 2:使用 test…

2025年吨包厂家联系电话推荐:专业采购指南与名录

在工业包装领域,吨包袋作为重要的集装容器,其质量与供应商选择直接影响企业的物流效率与成本控制。随着2025年制造业的持续发展,寻找可靠的吨包厂家成为众多采购人员的核心需求。本文旨在汇总当前市场上信誉良好、服…

2025年吨包厂家联系电话推荐:高效采购与业务对接指南

在现代物流与包装行业中,吨包袋作为大宗货物运输的重要工具,其质量与供应商的服务直接关系到企业的运营效率与成本控制。随着2025年市场需求的不断增长,选择一家可靠的吨包厂家成为众多企业的迫切需求。本文旨在为广…

2025年弱碱性水品牌联系电话推荐:优质品牌与联系渠道

引言 随着人们对健康饮水意识的不断提高,弱碱性水因其天然矿物质含量和适宜的pH值受到越来越多消费者的关注。为了帮助用户快速找到可靠的弱碱性水品牌联系方式,本文汇总了2025年推荐的弱碱性水品牌联系电话,并提供…

电商知识库概念预备

以下是整合两次回答的 200个二手电商专业词汇,按 商品属性、交易行为、平台规则、物流售后、支付金融、数据运营、细分领域 七大维度分类整理,覆盖奢侈品、3C数码、直播电商等场景: 一、商品属性类(50个)成色描述…

2025年弱碱性水品牌联系电话推荐:精选推荐与使用指南

随着人们对健康饮水的关注度不断提升,弱碱性水因其天然的特性受到越来越多消费者的青睐。寻找可靠且优质的弱碱性水品牌联系方式成为许多人的需求。本文旨在汇总2025年市场上值得信赖的弱碱性水品牌联系电话,为您的选…

2025年富锶水品牌联系电话推荐:优质水源与联系方式

随着人们对健康饮水意识的不断提升,富锶水因其富含对人体有益的矿物质元素而受到越来越多消费者的青睐。无论是家庭日常饮用、办公需求还是户外出行,选择一款优质的富锶水品牌成为许多人的关注焦点。为了帮助大家快速…

2025年蒸汽发生器品牌电话推荐:高效联系与选购指南

随着工业发展和节能需求的提升,蒸汽发生器作为关键设备,在供热、生产等领域发挥着重要作用。许多用户在选择蒸汽发生器品牌时,希望快速联系到可靠供应商,获取专业咨询或定制服务。本文针对2025年的市场需求,汇总了…

2025年蒸汽发生器品牌电话推荐:高效沟通与实用建议

随着工业节能和环保要求的不断提高,蒸汽发生器作为热能设备的重要组成部分,市场需求日益增长。为了帮助用户快速找到可靠的蒸汽发生器品牌及联系方式,本文汇总了2025年推荐的五大品牌电话,并附上详细的使用建议。无…

2025年数码印花厂家联系电话推荐:快速对接生产资源指南

在纺织服装行业快速发展的今天,数码印花技术以其灵活性强、精度高、环保节能等优势,成为面料印花的重要选择。无论是服装品牌、贸易公司还是独立设计师,寻找可靠的数码印花厂家是实现创意设计、保证产品质量的关键环…

2025年吨包厂家联系电话推荐:高效联系与选择指导

对于需要采购吨包或集装袋的企业来说,找到可靠的供应商至关重要。吨包作为大宗货物包装的主要形式,其质量、价格和交货周期直接影响采购成本和运营效率。许多用户在寻找吨包厂家时,往往面临信息分散、联系方式不准确…

2025年数码印花厂家联系电话推荐:专业团队与生产实力

在纺织服装行业快速发展的今天,寻找可靠、高效的数码印花厂家成为许多企业与设计师的核心需求。无论是为了小批量定制、新品打样,还是大规模生产,一个专业的合作伙伴不仅能保障产品质量,更能优化生产流程,节约时间…

2025年11月电磁吸盘厂家排名参考:多维度数据与用户评价汇总

作为工业制造领域的关键设备,电磁吸盘的选择直接影响生产效率和加工精度。许多用户可能是机械加工企业负责人、设备采购专员或生产工程师,他们需要在磨床、铣床等机床上安装电磁吸盘以固定导磁性工件。这类用户通常面…

2025年富锶水品牌联系电话推荐:实用联系信息汇总

随着人们对健康饮水意识的不断提升,富锶水作为富含多种矿物质元素的天然饮用水,越来越受到消费者的青睐。2025年,市场上涌现出众多富锶水品牌,为帮助大家快速找到可靠的联系方式,本文汇总了当前较具代表性的几家富…

2025年蒸汽发生器品牌电话推荐:高效联系与建议

在工业生产和能源应用领域,蒸汽发生器作为关键设备,其品牌选择与售后服务联系渠道对用户至关重要。随着2025年技术发展和市场需求变化,用户对蒸汽发生器品牌的电话联系方式需求日益增长,尤其是寻求高效、可靠的设备…

2025年11月电磁吸盘厂家排名指南:基于用户需求与行业数据评价

作为工业制造领域的关键设备,电磁吸盘广泛应用于机床加工、物流搬运、冶金等行业,其性能直接影响生产效率和作业安全。许多用户在选购时面临诸多挑战:一是市场厂家众多,资质参差不齐,难以快速识别可靠供应商;二是…

2025年数码印花厂家联系电话推荐:合作前必读联系指南

在纺织服装行业快速发展的今天,数码印花技术以其灵活、高效、高品质的特点,成为众多品牌和贸易公司的首选。无论是进行新品打样还是大批量生产,找到一家技术过硬、产能稳定、服务优质的数码印花厂家至关重要。然而,…