海亮02/14杂题

海亮2月14日

个人题单

T1

link

题意

传奇特级大师 \(\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

link

题意

给定长度为 \(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

link

题意

多组测试,每次给定一个 \(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

link

题意

给定一棵 \(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

link

题意

有一个 \(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

link

题意

定义一个序列的权值是最少的交换相邻元素的次数,使得序列单
峰,即 \(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
*/
posted @   Call_me_Eric  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
Live2D
欢迎阅读『海亮02/14杂题』
点击右上角即可分享
微信分享提示