海亮02/14杂题
海亮2月14日
T1
题意
传奇特级大师 \(\mathsf E \color{red}\mathsf{ntropyIncreaser}\) 有一个 \(n\times m\) 的矩形纸片,她将其放置在一个平面直角坐标系中,使其左下角在 \((0,0)\),右上角在 \((n,m)\) 位置。
她每次会均匀随机选择一条平行于坐标轴、经过坐标均为整数的点,且穿过(不能是经过边界)纸片的直线,沿此方向将纸片裁开,并扔掉裁剪线的下侧或右侧的部分。
她想要你求出期望多少次操作后,剩下纸片的面积小于 \(k\)。你只需要求出答案模 \(10^9+7\) 的结果。
有多组数据,每次给出 \(n,m,k\),保证所有 \(n\) 之和、所有 \(m\) 之和不超过 \(10^6\)。
题解
考虑将选择顺序看作一个排列,然后看哪些位置是有效的。
我们发现,一个横(竖)线 \(i\) 有效当且仅当以下两个条件都成立:
- 排在 \(i\) 前面的横(竖)线没有在 \([1,i-1]\) 范围内的。
- 排在 \(i\) 前面的竖(横)线没有在 \([1,\lfloor\frac{S}{i}\rfloor]\) 范围内的。
发现出现这种情况的概率是 \(\frac{1}{i}\times\frac{1}{\min(n,\frac{S}{i})}\) 的(这里算的是横线,竖线记得换下 \(n\))。
然后枚举一下就好了。
代码
#include<bits/stdc++.h> #define int long long using namespace std; bool stmemory; namespace Call_me_Eric{ inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e6 + 10, mod = 1e9 + 7; int inv[maxn]; int qpow(int x,int a){ int res = 1; while(a){ if(a & 1)res = 1ll * res * x % mod; x = 1ll * x * x % mod;a >>= 1; } return res; } int n, m, S; void main(){ inv[1] = 1; for(int i = 2;i < maxn;i++){inv[i] = (mod - mod / i) * 1ll * inv[mod % i] % mod;} auto solve = [](){ n = read(); m = read(); S = read() - 1; if(n * m <= S){puts("0");return;} int ans = 1; for(int i = 1;i < n;i++){ int j = min(S / i,m);if(j == m)continue; ans += inv[i + j];if(ans >= mod)ans -= mod; } for(int i = 1;i < m;i++){ int j = min(S / i,n);if(j == n)continue; ans += inv[i + j];if(ans >= mod)ans -= mod; } printf("%lld\n",ans); return; }; int T = read();while(T--)solve(); return; } }; bool edmemory; signed main(){ // freopen("tmp.in","r",stdin); auto stclock = clock(); Call_me_Eric::main(); auto edclock = clock(); cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n"; cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n"; return 0; }
T2
题意
给定长度为 \(n\) 的数列 \(\{a_i\}\) 和两个参数 \(k, s\),将 \(\{a_i\}\) 划分成 \(k\) 段,最大化和 \(\geq s\) 的段数。
第一行三个数 \(n, k, s\),第二行 \(n\) 个数 \(\{a_i\}\)。
输出一个数,代表和不小于 \(s\) 的段的数量。
\(1 \leq k \leq n \leq 250000, 1 \leq A_i \leq 10^9, 1 \leq s \leq 10^{15}\)。
题解
最开始的思路是wqs二分+直接DP,但是发现这个东西没有凸性。
然后没想到二分至少有 \(mid\) 个段总和 \(\geq s\) 的答案。
然后发现现在需要求函数 \(g(x)\) 表示 \(\geq s\) 的段数有 \(x\) 个时,能够分出的最多段数。
发现这个东西不好求,那么改下定义,设 \(g(x)=\sum_{i=1}^xr_i-l_i\),即总长度-最多段数。
然后发现这东西是凸的,可以wqs二分。
然后就没了。
代码
#include<bits/stdc++.h> #define int long long using namespace std; bool stmemory; namespace Call_me_Eric{ inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 3e5 + 10; int n, k, s; int sum[maxn], pre[maxn]; pair<int,int> f[maxn]; bool check(int lim){ int l = 1,r = n, mid = 0, res = 0; auto work = [](int x){ for(int i = 1;i <= n;i++){ f[i] = f[i - 1];if(pre[i]){f[i] = min(f[i],make_pair(f[pre[i] - 1].first + (i - pre[i]) - x,f[pre[i] - 1].second + 1));} } }; while(l <= r){ mid = l + r >> 1; work(mid); if(f[n].second <= lim){l = mid + 1;res = mid;} else r = mid - 1; } work(res); return f[n].first + res * lim <= n - k; } void main(){ n = read(); k = read(); s = read(); for(int i = 1,l = 0;i <= n;i++){ sum[i] = read() + sum[i - 1]; while(sum[i] - sum[l] >= s)l++; pre[i] = l; } int l = 1, r = k, ans = 0; while(l <= r){ int mid = l + r >> 1; if(check(mid)){l = mid + 1,ans = mid;} else r = mid - 1; } printf("%lld\n",ans); return; } }; bool edmemory; signed main(){ auto stclock = clock(); Call_me_Eric::main(); auto edclock = clock(); cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n"; cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n"; return 0; }
T3
题意
多组测试,每次给定一个 \(S\) ,你需要构造一个三角形满足以下条件。
- 该三角形的三个顶点都为格点
- 该三角形的面积为 \(\frac{S}{2}\)
- 该三角形内恰好只包含一个边长为1的正方形且该正方形的顶点也为格点(正方形的边可以和三角形的边或顶点重合)
题解
发现 \(S\) 是偶数好做,只要让底长度是 \(2\),然后高能够每次增加 \(1\),并且里面只有一个正方形,那么就赢了。
发现让底的两个点分别是 \((0,0),(2,0)\) 即可,然后剩下一个点只要在 \(x-y=0\) 这条直线上,就能保证 \((1,0)\) 的右上方有一个正方形,且其他的位置都没有正方形。
现在难的是 \(S\) 是奇数的时候。
我们不妨强制设其中一个点是 \((0,0)\),剩下两个点分别是 \((x_1,y_1),(x_2,y_2)\),那么三角形的面积就是 \(S=|x_1y_2-x_2y_1|\)
发现这东西是奇数,就说明 \(x_1y_2,x_2y_1\) 中有且仅有一个是奇数。
不妨设 \((x_1,y_1)=(3,1)\),那么就有 \(3y_2-x_2=S\)。
然后你发现 \(x_2,y_2\) 奇偶性不同,不妨让 \(y_2=x_2+1\)。
那么就有 \(2x_2=S-1\),然后你发现这个 \(x_2\) 是个整数可求,同时 \(y_2\) 也出来了。
然后就没了。
代码
#include<bits/stdc++.h> using namespace std; bool stmemory; namespace Call_me_Eric{ inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } void main(){ int T = read(); while(T--){ int S = read(); if(S == 2 || (S < 9 && (S & 1))){puts("No");continue;} puts("Yes"); if((S & 1) == 0){printf("0 0 2 0 %d %d\n",S / 2,S / 2);} else{printf("0 0 3 1 %d %d\n",(S - 9) / 2 + 3,(S - 9) / 2 + 3 + 1);} } return; } }; bool edmemory; signed main(){ auto stclock = clock(); Call_me_Eric::main(); auto edclock = clock(); cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n"; cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n"; return 0; }
T4
题意
给定一棵 \(n\) 个点的树,第 \(i\) 个点的权值是 \(a_i\)。
有两种操作,每次给一条链上的每个点权值加 \(w\),或者给一个子树的每个点权值加 \(w\)。
每次操作之后求出此时的带权重心。如果有多个选择深度最小的。
\(n,Q \leq 10^5\)
题解
注:这里QOJ
上的题目对于修改的要求是每次只增加 \(1\),不是增加任意值,与这里不完全相符,但无论怎样都可以做。
首先对于修改点权,显然记录树剖的访问顺序就可以同时修改子树和链。
然后考虑如果现在知道每个点的点权,怎么找重心。
首先我们知道,重心是使 \(\sum_{i=1}^n(dist(u,i)\times w_i)\) (这里设为 \(F(u)\))最小的点 \(u\)。
然后就有这样一个性质:深度最浅的重心,其子树大小一定严格大于总权值的一半。
如何证明?如果有一个点的子树权值大小小于总权值的一半,那么其父亲的 \(F()\) 值一定比这个点的 \(F()\) 值小,证明显然。
然后我们先设想将每个点拆成 \(val_i\) 个连在一起的点,那么因为重心子树权值大小一定大于总权值的一半,那么其子树一定会包含中点。
然后倍增跳父亲直到第一次子树权值大小大于总权值的一半,那么这个点就是最浅深度重心。
代码
#include<bits/stdc++.h> #define int long long using namespace std; bool stmemory; namespace Call_me_Eric{ inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e5 + 10; int n, q; vector<int> edg[maxn]; int fa[21][maxn], siz[maxn], son[maxn], dep[maxn]; void dfs1(int u,int f){ fa[0][u] = f;siz[u] = 1;dep[u] = dep[f] + 1; for(int i = 1;i <= 20;i++)fa[i][u] = fa[i - 1][fa[i - 1][u]]; for(int v : edg[u]){ if(v != f){ dfs1(v, u); siz[u] += siz[v]; son[u] = siz[v] > siz[son[u]] ? v : son[u]; } } } int dfn[maxn], low[maxn], idx, top[maxn], rev[maxn]; void dfs2(int u,int t){ top[u] = t;dfn[u] = ++idx;rev[idx] = u;low[u] = siz[u] + dfn[u] - 1; if(!son[u])return; dfs2(son[u],t); for(int v : edg[u])if(v != fa[0][u] && v != son[u])dfs2(v,v); } struct Segment_Tree{ struct node{ int sum, l ,r, tag; node(int sum = 0,int l = 0,int r = 0,int tag = 0 ):sum(sum),l(l),r(r),tag(tag){} }d[maxn << 2]; node mergenode(node l,node r){return node(l.sum + r.sum,l.l,r.r);} inline void change(int p,int tag){d[p].tag += tag;d[p].sum += (d[p].r - d[p].l + 1) * tag;} inline void pushdown(int p){change(p << 1,d[p].tag);change(p << 1 | 1,d[p].tag);d[p].tag = 0;} void build(int l,int r,int p){ if(l == r){d[p] = node(0,l,r,0);return;} int mid = l + r >> 1; build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1); d[p] = mergenode(d[p << 1],d[p << 1 | 1]); } void update(int l,int r,int s,int t,int p,int upd){ if(s <= l && r <= t){change(p,upd);return;} int mid = l + r >> 1;pushdown(p); if(s <= mid)update(l,mid,s,t,p << 1, upd); if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,upd); d[p] = mergenode(d[p << 1],d[p << 1 | 1]); } node query(int l,int r,int s,int t,int p){ if(s <= l && r <= t)return d[p]; int mid = l + r >> 1;pushdown(p); if(t <= mid)return query(l,mid,s,t,p << 1); if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1); return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1)); } void update(int l,int r,int upd){update(1,n,l,r,1,upd);} int query(int l,int r){return query(1,n,l,r,1).sum;} int find(int l,int r,int p,int s){ if(l == r)return l; int mid = l + r >> 1;pushdown(p); if(d[p << 1].sum >= s)return find(l,mid,p << 1,s); else return find(mid + 1,r,p << 1 | 1,s - d[p << 1].sum); } }tree; void main(){ n = read();int u, v, opt; for(int i = 1;i < n;i++){ u = read();v = read(); edg[u].push_back(v);edg[v].push_back(u); } dfs1(1, 0);dfs2(1,1);tree.build(1,n,1); // for(int i = 1;i <= n;i++)printf("i = %lld,dfn = %lld,low = %lld,siz = %lld,son = %lld\n",i,dfn[i],low[i],siz[i],son[i]); q = read(); while(q--){ opt = read(); u = read(); if(opt == 1){tree.update(dfn[u],low[u],1);} else{ v = read(); while(top[u] != top[v]){ if(dep[top[u]] < dep[top[v]])swap(u, v); tree.update(dfn[top[u]],dfn[u],1); u = fa[0][top[u]]; } if(dfn[u] > dfn[v])swap(u, v); tree.update(dfn[u],dfn[v],1); } int sum = tree.d[1].sum / 2 + 1; u = rev[tree.find(1,n,1,sum)]; if(tree.query(dfn[u],low[u]) >= sum){printf("%lld\n",u);continue;} // printf("q = %lld,u = %lld\n",q,u); for(int i = 20;i + 1;i--){ if(fa[i][u] && tree.query(dfn[fa[i][u]],low[fa[i][u]]) < sum) u = fa[i][u]; // printf("u = %lld,i = %lld\n",u,i); } printf("%lld\n",fa[0][u]); } return; } }; bool edmemory; signed main(){ auto stclock = clock(); Call_me_Eric::main(); auto edclock = clock(); cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n"; cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n"; return 0; } /* 7 1 6 1 7 7 3 3 2 7 5 5 4 4 1 2 1 4 1 6 2 6 7 */
T5
题意
有一个 \(n \times m\) 的网格,上面有 \(k\) 个宝石,\(b\) 个炸弹。
一个炸弹可以横着爆或者竖着爆,并炸掉一行/一列的宝石,并引爆一行/一列的其他炸弹。
你可以选择每个炸弹的爆炸方向,然后引爆一个炸弹。问链式反应结束时至多能炸掉几个宝石。
\(n,m \le 3000\)
题解
发现直接做没啥思路,先找找性质。
发现,假设一行中有一个炸弹以行的方式引爆,那么剩下的一定都以列的方式引爆。
那么就有一个想法,将行和列拆开,每个炸弹看作将行列连起来的边。
然后去找一些生成图的性质。
发现,如果引爆一个炸弹,相当于选择一条边,并走向其中一个端点,然后这条边不再可用。
然后显然发现,如果对于一个联通块,其中存在一个环,那么所有的点都能走遍,否则只会有一个叶子节点走不到(联通还没有环当然是树啦),然后分类讨论下统计下答案即可。
代码
#include<bits/stdc++.h> using namespace std; bool stmemory; namespace Call_me_Eric{ inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 1e4 + 10; int n, m; char ch[maxn / 3][maxn / 3]; vector<int> edg[maxn]; int fa[maxn], siz[maxn], cntedg[maxn]; int deg[maxn], val[3][maxn]; int getf(int x){return fa[x] == x ? x : fa[x] = getf(fa[x]);} void main(){ n = read(); m = read();read(); read(); for(int i = 1;i <= n + m;i++){fa[i] = i;siz[i] = 1;cntedg[i] = 0;} for(int i = 1;i <= n;i++){ scanf("%s",ch[i] + 1); for(int j = 1;j <= m;j++){ if(ch[i][j] == 'b'){ deg[i]++;deg[j + n]++; int u = getf(i),v = getf(j + n); if(u != v){fa[u] = v;siz[v] += siz[u];cntedg[v] += cntedg[u];} cntedg[v]++; } } } for(int i = 1;i <= n;i++){ for(int j = 1;j <= m;j++){ if(ch[i][j] == 'k'){ int u = getf(i), v = getf(j + n); if(u == v){val[0][u]++;} else{val[1][u]++;val[1][v]++; val[2][i]++;val[2][j + n]++;} } } } int ans = 0; for(int i = 1;i <= n + m;i++){ if(getf(i) == i && cntedg[i] >= siz[i])ans = max(ans,val[0][i] + val[1][i]); // printf("1 : ans = %d,i = %d,getf = %d\n",ans,i,getf(i)); } for(int i = 1;i <= n + m;i++){ if(deg[i] == 1){int f = getf(i);ans = max(ans,val[0][f] + val[1][f] - val[2][i]);} // printf("2 : ans = %d,i = %d\n",ans,i); } printf("%d\n",ans); return; } }; bool edmemory; signed main(){ auto stclock = clock(); Call_me_Eric::main(); auto edclock = clock(); cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n"; cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n"; return 0; } /* 7 3 4 5 bbb ..b ..b k.. k.. k.. k.. 3 3 4 2 k.. kk. bbk */
T6
题意
定义一个序列的权值是最少的交换相邻元素的次数,使得序列单
峰,即 \(a_1 < \dots < a_k > \dots > a_n\) 或 \(a_1 > \dots > a_k < \dots < a_n\)。
给定一个长度为 \(n\) 的数组 \(a\),保证没有重复元素,你需要求出每个前缀的权值。
\(n \le 2 × 10^5\)
题解
首先离散化下,然后变成排列。
然后想到只需要管单谷的情况,单峰的情况将所有的数字 \(a_i\gets n-a_i+1\) 即可。
然后考虑单谷的情况怎么做。
对于一个排列 \(a_{1,2,\dots,n}\),其谷值一定是确定的。
我们先假设已经确定了每个数字在谷的左右,那么答案就是将谷左边的数字去相反数然后求逆序对的个数。
先假设所有的数字都在右边,然后考虑每个数在添加到哪个数字的时候,被划分到左边。
从小到大考虑数字,发现可以倍增解决。
然后就没了。
代码
#include<bits/stdc++.h> #define ll long long using namespace std; bool stmemory; namespace Call_me_Eric{ inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e5 + 10; int n, a[maxn]; vector<int> vec; struct BIT{ int c[maxn];void init(){memset(c,0,sizeof(c));} inline int lowbit(int x){return x & (-x);} int qry(int x){int s = 0;for(;x;x -= lowbit(x))s += c[x];return s;} void upd(int x,int add){for(;x <= n;x += lowbit(x))c[x] += add;} }tr; int L[maxn], id[maxn]; ll ans[maxn]; vector<int> del[maxn]; void main(){ n = read(); for(int i = 1;i <= n;i++)vec.push_back(a[i] = read()); sort(vec.begin(),vec.end());vec.erase(unique(vec.begin(),vec.end()),vec.end()); for(int i = 1;i <= n;i++)a[i] = lower_bound(vec.begin(),vec.end(),a[i]) - vec.begin() + 1; memset(ans,0x3f,sizeof(ans)); auto solve = [](){ tr.init();for(int i = 1;i <= n;i++){L[i] = tr.qry(a[i] - 1);tr.upd(a[i],1);id[a[i]] = i;} tr.init();for(int i = 1;i <= n;i++)del[i].clear(); for(int i = 1;i <= n;i++){ int p = id[i], c = p; for(int j = 20;j + 1;j--){ int x = c + (1 << j); if(x <= n && tr.qry(x) - L[p] <= L[p])c = x; } del[c + 1].push_back(i);tr.upd(p,1); } tr.init();ll res = 0; for(int i = 1;i <= n;i++){ for(int j : del[i])tr.upd(j,-1); res += tr.qry(n) - tr.qry(a[i]); ans[i] = min(ans[i],res);tr.upd(a[i],1); } return; }; solve();for(int i = 1;i <= n;i++)a[i] = n - a[i] + 1;solve(); for(int i = 1;i <= n;i++)printf("%lld\n",ans[i]); return; } }; bool edmemory; signed main(){ auto stclock = clock(); Call_me_Eric::main(); auto edclock = clock(); cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n"; cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n"; return 0; } /* 9 11 4 5 14 1 9 19 8 10 */
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】