比赛链接: https://ac.nowcoder.com/acm/contest/11166
还在补题,但是好多,补不完了qwq。
已完成:A (B) D F I J
A - Alice and Bob
题意简介
两堆石子,分别是 和
个,Alice 先取,Bob 后取。每次,可以从其中一堆中取走
(
) 个,然后从另一堆中取走
(
) 。轮到某个人时石子都没了,那个人就会输掉。现在给你 10000 组询问,
,要你输出每组询问谁会赢。
思路分析
想不出正解,但是先暴力算一遍再说。记 表示第一堆石子剩下
,第二堆石子剩下
的情况下先手方的状态,
表示必胜 ,有:
因为发现必败态只有 2719 种,于是存下来,每次询问直接二分查找。
上面是比赛时用的打表思路。
经过后续的听讲,我们知道这道题可以利用每个 i 只唯一对应一个必败态来搞事,而且据出题人说可以跑得飞快,于是我就自己敲了一个。
思路就是用 存下对于每个
,与之唯一对应的必败态
(当然也可能不存在,记为
)。
然后再根据这些必败态,枚举倍数来排除必胜态,找到下一个必败态。
理论复杂度是 的,但因为减了不少枝,所以跑得飞快,500 来毫秒左右。
但是我并没有理解出题人说的 也可以跑出来需要怎么做,因为我无法省去这个二维数组,求大佬教教我qwq。
解题代码
#include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std; const int N = 5005; int n, m, Q[N]; bool vis[N][N]; int main() { memset(Q, -1, sizeof(Q)); Q[0] = 0; for(int i=0; i<=5000; i++) { if(Q[i] != -1) { for(int j=Q[i]+1; j<=5000; j++) { for(int k=i; k<=5000; k+=j-Q[i]) vis[k][j] = 1; } continue; } for(int j=i-1; j>=0; j--) { if(Q[j] == -1) continue; for(int k=Q[j]; k<=5000; k+=i-j) vis[i][k] = 1; } for(int j=i+1; j<=5000; j++) { if(!vis[i][j]) { Q[i] = j, Q[j] = i; break; } } if(Q[i] != -1) { for(int j=Q[i]+1; j<=5000; j++) { for(int k=i+j-Q[i]; k<=5000; k+=j-Q[i]) vis[k][j] = 1; } } } int t; scanf("%d", &t); while(t--) { scanf("%d%d", &n, &m); if(Q[n] != -1 && Q[n] == m) puts("Bob"); else puts("Alice"); } return 0; }
B - Ball Dropping
题意简介
如上图,给你 ,问你球是否会从漏斗里掉出去。如果不会,求图上红边的长度。
数据保证 ,
,
。
思路分析
队友切的。几何题一生之敌qwq。
TODO
通过代码见:提交记录
C - Cut the Tree
题意简介
TODO
思路分析
TODO
D - Determine the Photo Position
题意简介
给你一个只含 0 和 1 的 的矩阵,表示相片上有的地方站了学生有的地方没站。现在告诉你有
个老师站成一排,要求你把这一排老师完整地、不能拆分或旋转地,插入到相片地空白处。问你有多少种插入方式。
思路分析
签到。求出所有的连续的 0 段的 0 的数目 c,每个连续 0 段对答案的贡献是 。
解题代码
#include <cstdio> #include <algorithm> #include <cmath> using namespace std; const int N=2005; int n,m; char s[N][N],s2[N]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf(" %s",s[i]+1); scanf(" %s",s2+1); int ans=0; for(int i=1;i<=n;i++){ int c0=0; for(int j=1;j<=n+1;j++){ if(s[i][j]!=s[i][j-1]){ if(s[i][j]!='0'){ ans+=max(0,c0-m+1); c0=0; }else{ c0=1; } }else if(s[i][j]=='0') c0++; } } printf("%d",ans); return 0; }
E - Escape along Water Pipe
题意简介
TODO
思路分析
TODO
F - Find 3-friendly Integers
题意简介
一个整数被称为 3友好整数 ,当且仅当可以从它的十进制表示中找到一个连续子串,这个连续子串的表示的数字是 3 的倍数。
给你 10000 组询问,问你 到
里有多少个整数满足上述要求。(
)
思路分析
我们知道,一个数是三的倍数,那么它的各位数之和也是三的倍数。
想要方便直观地各位数之和,那么我们可以给每一位数都模个 3 。
我们知道,想要一个数想要不满足题意,它的各个数字模 3 后首先不能是 0 。
所以,我们试着用数字 1 和 2 来构造不满足题意的数,不难发现,根本无法构造出 4 位数以上的数字。
所以,对于位数少的,我们直接预处理出那些数字满足题意,对于位数大的部分,它一定是满足题意的,直接 就完事了。
解题代码
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int N=1e6; int vis[N]; inline void calc(int x){ static int dig[1000]; int cn=0; int ux=x; while(x){ dig[++cn]=x%10; x/=10; } for(int i=1;i<=cn;i++) dig[i]+=dig[i-1]; for(int i=1;i<=cn;i++) for(int j=i;j<=cn;j++){ if((dig[j]-dig[i-1])%3==0){ vis[ux] = 1; return; } } return; } int main(){ int t; scanf("%d",&t); vis[0]=1; for(int i=1;i<=N;i++) { calc(i); vis[i]+=vis[i-1]; } while(t--){ ll L,R; scanf("%lld%lld",&L,&R); ll ans=0; if(R<=N) ans=vis[R]-vis[L-1]; else if(L<=N) ans=R-N+vis[N]-vis[L-1]; else ans=R-L+1; printf("%lld\n",ans); } return 0; }
G - Game of Swapping Numbers
题意简介
TODO
思路分析
TODO
H - Hash Function
题意简介
给你一个大小为 的没有重复数字的集合
,问你使集合内所有数对
取模的答案两两不同的最小
。
,
。
思路分析
比赛时开桶暴力瞎搞的。数据加强了之后烂了。不会多项式,下一个。
TODO
I - Increasing Subsequence
题意简介
Alice 和 Bob 轮流对一个排列进行取数。每次取都要比上一个被取的数字更大。同时,对于同一个人,他取数时除了要满足比上一个数大外,还要求新取的数要在自己上一次取的数的右边。如果这一轮里一个人可以取的数不止一个,他会随机等概率拿一个出来。求游戏进行的轮次的期望值。
思路分析
比赛时有一个 O(n^3) 的思路,但是没时间了,细节也没搞清楚,于是没搞出来,赛后重新复盘了一下。
我们记 为 Alice 选了
次,Bob 选了
次的概率。
显然的, 时,当前这个状态最后一次取的人是 Alice(因为每次取数要比上一次取数更大),所以这个状态必然是由
转移过来的。且根据题意,我们可以知道,这个状态必须满足
,和
。
而从 转移到
,意味着 Alice 在
往后的位置中所有能选的位置里,恰好挑中了
。
于是,我们有转移:,其中
表示
往后的位置中能选的位置的数量,且
必须满足上面分析的条件(搞进公式里太复杂了,自已意会一下好了qwq)。
同理,对于 ,我们有转移:
有了这个东西之后,轮次期望的转移就变得非常明显了。
我们记 为 Alice 选了
次,Bob 选了
次的轮次期望。
于是,对于 有:
。
小于的情况类似。
接下来考虑如何实现。
首先,题目 ,因此我们只能是
,甚至不能带
。发现需要求逆元的
小于
,所以我们先预处理出
到
的所有逆元。
对于 的快速求解,我们可以开个二维数组,用
,表示从下标
开始的后缀里,值小于等于
的数量(相当于对每个后缀开了一个桶)。
接下来,仔细观察转移的式子,我们可以发现,大于的状态必然从小于的状态转移过来,而且转移过程与 (
)无关,所以我们可以维护类似前缀和的东西。
具体实现见代码。
解题代码
代码中的注释部分为 的写法。
#include <cstdio> typedef long long ll; const ll mod = 998244353, N = 5005; int n; ll A[N], P[N], f[N][N], cnt[N][N], p[N][N], sp1[N], sf1[N], sp2[N][N], sf2[N][N], inv[N]; inline char getc() { static char buf[1<<14], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<14, stdin), p1 == p2) ? EOF : *p1++; } inline ll scan() { ll x = 0; char ch = 0; while(ch < 48) ch = getc(); while(ch >= 48) x = x * 10 + ch - 48, ch = getc(); return x; } inline ll _pow(ll x, ll p) { ll ans = 1; while(p) { if(p&1) ans = ans * x %mod; x = x * x % mod; p >>= 1; } return ans; } inline ll _inv(ll x) { return _pow(x, mod - 2); } inline ll add(ll a, ll b) { return a + b >= mod ? a + b - mod : a + b; } int main() { n = scan(); for(int i=1; i<=n; i++) { A[i] = scan(); P[A[i]] = i; inv[i] = _inv(i); } for(int i=1; i<=n; i++) { for(int j=i; j<=n; j++) { cnt[i][A[j]]++; } for(int j=1; j<=n; j++) { cnt[i][j] += cnt[i][j-1]; } } p[0][0] = 1; for(int i=1; i<=n; i++) { p[i][0] = f[i][0] = inv[n]; int _c = cnt[1][n] - cnt[1][A[i]]; sp2[i][0] = p[i][0] * inv[_c] % mod; sf2[i][0] = (f[i][0] + 1 * p[i][0]) * inv[_c] % mod; for(int j=1; j<=n; j++) { // 计算 f[i][j] 和 p[i][j] if(A[i] > A[j]) { /*for(int k=1; k<i; k++) { if(A[k] < A[j]) { int c = cnt[k+1][n] - cnt[k+1][A[j]]; p[i][j] += p[k][j] * _inv(c) % mod; p[i][j] %= mod; f[i][j] += (f[k][j] + 1 * p[k][j]) * _inv(c) % mod; f[i][j] %= mod; } }*/ p[i][j] = add(p[i][j], sp1[j]); f[i][j] = add(f[i][j], sf1[j]); // 维护 sp2 和 sf2 int c = cnt[j+1][n] - cnt[j+1][A[i]]; sp2[i][j] = (sp2[i][j] + p[i][j] * inv[c]) % mod; sf2[i][j] = (sf2[i][j] + (f[i][j] + p[i][j]) * inv[c]) % mod; } if(A[i] < A[j]) { /*for(int k=0; k<j; k++) { if(A[k] < A[i]) { int c = cnt[k+1][n] - cnt[k+1][A[i]]; p[i][j] += p[i][k] * _inv(c) % mod; p[i][j] %= mod; f[i][j] += (f[i][k] + 1 * p[i][k]) * _inv(c) % mod; f[i][j] %= mod; } }*/ p[i][j] = add(p[i][j], sp2[i][j-1]); f[i][j] = add(f[i][j], sf2[i][j-1]); } sp2[i][j] = add(sp2[i][j], sp2[i][j-1]); sf2[i][j] = add(sf2[i][j], sf2[i][j-1]); } // 维护前缀和 for(int j=0; j<=n; j++) { if(A[i] < A[j]) { int c = cnt[i+1][n] - cnt[i+1][A[j]]; sp1[j] = (sp1[j] + p[i][j] * inv[c]) % mod; sf1[j] = (sf1[j] + (f[i][j] + p[i][j]) * inv[c]) % mod; } } } ll res = 0; for(int i=1; i<=n; i++) for(int j=0; j<=n; j++) { if(A[i] > A[j]) { int c = cnt[j+1][n] - cnt[j+1][A[i]]; if(c == 0) res = add(res, f[i][j]); } if(A[j] > A[i]) { int c = cnt[i+1][n] - cnt[i+1][A[j]]; if(c == 0) res = add(res, f[i][j]); } } printf("%lld", res); return 0; }
J - Journey among Railway Stations
题意简介
个车站,每个车站有一个开启时间和一个关闭时间。如果提早到了,需要等到开启时间才能走,如果晚到了,就再也过不去了。
相邻车站有一条从左到右的单向道路,表示需要走多少时间才能从一个车站到另一个车站。
现在有两种操作,一种询问:
修改车站的起始时间。
修改车站间路的长度。
询问能否从第
个车站移动到第
个车站并通过。
思路分析
看一眼数据范围: 。还有修改和查询。是数据结构哒!
然后我就溜了。
补题的时候听了下讲解。这道题是个很巧妙的线段树题。
我们可以观察到,不管是多长的区间,我们画出进入区间的时间与离开区间的最早时间的函数,你总能发现这样的三部分:
早来了,在移动过程中需要经过等待,最终出区间的时间一样。
中间不需要任何等待,出区间的时间随入区间的时间单调递增,且斜率为 1。
来晚了,过不去了。
不难发现,我们只要维护三个值,就可以推出完整的函数图像:
wait: 在这个时间之前来,一定需要等待
late: 在这个时间之后来,一定过不去
time: 从 wait 这个时间前来,会在什么时候离开(也就是可能的最早离开时间)。
具体的维护和初始化,画画图,仔细想想、分类讨论一下可以搞出来(语文能力差,表达不出来)。可以参考代码的 merge
函数,可能写得有点丑qwq。
因为都是单点修改,所以每次改完后递归到底,往上一路 maintain 就完事了。
解题代码
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int N = 1e6 + 5; #define ls (u<<1) #define rs ((u<<1)+1) struct Node { ll wait, late, time; Node(): wait(0), late(0), time(0) {} }Tr[N<<2]; int s[N], e[N], cost[N], n, q; inline void debug(Node x) { printf("Wait: %lld, late: %lld, time: %lld\n", x.wait, x.late, x.time); } inline void output(int u, int l, int r) { if(l > r) return; printf("For [%d, %d]: ", l, r); debug(Tr[u]); if(l == r) return; int mid = (l + r) >> 1; output(ls, l, mid); output(rs, mid + 1, r); } inline Node merge(Node A, Node B, ll w) { Node C = Node(); if(A.time == -1 || B.time == -1) return C.time = -1, C; ll ti = A.time + w; if(ti > B.late) return C.time = -1, C; if(ti >= B.wait) C.wait = A.wait; else { C.wait = A.wait + (B.wait - ti); ti = B.wait; } C.time = B.time + (ti - B.wait); C.late = min(A.late, C.wait + (B.late - ti)); return C; } inline void maintain(int u, int l, int r) { int mid = (l + r) >> 1; Tr[u] = merge(Tr[ls], Tr[rs], cost[mid]); } void build(int u, int l, int r) { if(l == r) { Tr[u].wait = s[l]; Tr[u].late = e[l]; Tr[u].time = s[l]; return; } int mid = (l + r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); return maintain(u, l, r); } Node query(int u, int l, int r, int ql, int qr) { if(l > qr || r < ql) return Node(); if(ql <= l && r <= qr) return Tr[u]; int mid = (l + r) >> 1; if(l > qr || mid < ql) return query(rs, mid + 1, r, ql, qr); if(mid + 1 > qr || r < ql) return query(ls, l, mid, ql, qr); return merge(query(ls, l, mid, ql, qr), query(rs, mid + 1, r, ql, qr), cost[mid]); } void update(int u, int l, int r, int x) { if(l > x || r < x) return; if(l == r) { Tr[u].wait = s[l]; Tr[u].late = e[l]; Tr[u].time = s[l]; return; } int mid = (l + r) >> 1; update(ls, l, mid, x); update(rs, mid + 1, r, x); return maintain(u, l, r); } int main() { int T; scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i=1; i<=n; i++) scanf("%d", s+i); for(int i=1; i<=n; i++) scanf("%d", e+i); for(int i=1; i<n; i++) scanf("%d", cost+i); build(1, 1, n); // puts("======================="); // output(1, 1, n); // puts("======================="); scanf("%d", &q); while(q--) { int opt; scanf("%d", &opt); if(opt == 0) { int l, r; scanf("%d%d", &l, &r); Node ans = query(1, 1, n, l, r); puts(ans.time == -1 ? "No" : "Yes"); } if(opt == 1) { int i, w; scanf("%d%d", &i, &w); cost[i] = w; update(1, 1, n, i); } if(opt == 2) { int i, _s, _e; scanf("%d%d%d", &i, &_s, &_e); s[i] = _s, e[i] = _e; update(1, 1, n, i); } /* if(opt) { puts("======================="); output(1, 1, n); puts("======================="); } */ } } return 0; }
K - Knowledge Test about Match
题意简介
TODO
思路分析
TODO