二月——week3
2025.2.17
A: 离豆解渴
需要额外代价的操作至多只会执行一次。
所以共有三种情况,第一种是直接蹦到前面再花费 1 的代价向后,第二种是不进行需要额外代价的操作,这两种比较简单。
第一种直接查询前缀 \(\min\),第二种可以倍增预处理出 \(f_{i,j}\) 表示从 \(j\) 开始向前走 \(2^i\) 步到达的位置。
唯一值得考虑的是先蹦一次之后再向前到终点的情况。这种情况我赛时一直在沿袭上方第二种从后向前跳的思路,最后写了个错误的答案估算方式,但是没被卡。
从后向前倍增是困难的,从前向后倍增,设 \(g_{i,j}\) 表示 \(j\) 开始向后走 \(2^i\) 步范围内的点到 \(j\) 的最小代价,预处理出来这个之后就能很好的进行查询。
当注意到从后向前倍增由于并不确定起点而导致代价确定非常困难时应该迅速的考虑一下从前向后,而不是思考如何更换一个答案估算方式来使它正确率变高。
B:三人班咖
说一下部分分。
见到这个题我写暴力一开始的思路是把所有的操作都存起来,观察到不同的上界 \(x\) 对应的最终答案具有单调性,所以二分出最终答案能取到我们询问的数的最小值和最大值,时间复杂度 \(O(q^2\log V)\),十分地劣。
百般思考之后,可以想到一个经典的 Trick,设 \(f_i\) 表示上界为 \(i\) 时经过若干操作后的最终答案,那么我们只需要维护这个折线图就好了,而只有单点修改的时候,我们维护这个折线图只需要 \(O(q\log q)\) 的时间,可以获得部分分,很难写就是了。
类似的维护答案函数的题,求别忘了。
C: 撒萝卜无耻
比较简单,赛时在纠结不必要的点。
从后向前扫一遍操作序列,每次碰到的第一个没出现的操作就把他归为最后一行,最终可以求出最后一行的状态。
而我们最终的目的是将操作序列分给 n 行,使得每行的状态和最后一行相同。一个比较关键的性质就是一行后面递增的后缀我们并不一定要对之操作。明确这个之后同样再从后向前扫一遍,把每个操作分给对应的行,最后若所有行都能完成必要的操作,那么就合法。
2025.2.17 晚
A:重振旗鼓
并不想宣泄情绪,只是觉得 OI 赛制放这种有思维强度需要较高级算法情况巨多写起来巨麻烦数据还绑 Subtask 而且每个包都扔了七八个 corner case 的题是否有些过分?
赛时思路差不多就开写了,最后当然是坠机了。
树上链加一个数,使所有数的 \(\gcd\) 最大,容易由更相减损法想到树上差分。为了避免讨论太多情况,我们加一个值为 0 的虚根之后再差分。
这样操作其实就是选择一对有祖孙关系的点对使下方的点 \(+k\),同时使上方点 \(-k\),或者选择一个点对 \(u,v\) 以及他们的最近公共祖先 \(l\) 和最近公共祖先的父亲 \(fa_l\),使 \(u,v\) 都 \(+k\) 的同时, \(l,fa_l\) 都 \(-k\)。
最终答案是 \(n\) 个数的 \(\gcd\),所以我们最终答案的一个因数 \(p\) 在执行链加操作之前一定是 \(\ge n-4\) 个点的约数,因为我们最多只能修改四个点。
找出满足为 \(\ge n-4\) 个点的约数的质因数我们可以找出任意 5 个非零的数进行质因数分解,这样得到的质因数就包含了所有可能计入答案的质因数。
从这个角度出发,我们对每个质数 \(p\),考虑 \(p_k\) 对答案的贡献。记差分完后的数组为 \(b\)。
- 若 \(p^k\) 为所有 \(b_i\) 的约数,那么一定能计入答案。
- 若 \(p^k\) 为 \(n-2\) 个 \(b_i\) 的约数,那么找出那两个不能整除 \(p^k\) 的 \(b\),记为 \(u,v\),若 \(\text{lca}(u,v)\in \{u,v\}\) 且 \(b_u\equiv -b_v\pmod {p^k}\) ,那么可以通过链加来计入答案,要求 \(b_u+k\equiv0\pmod{p^k}\)。
- 若 \(p^k\) 为 \(n-4\) 个 \(b_i\) 的约数,找出不能整除的四个点 \(q_1,q_2,q_3,q_4\),默认按深度从小到大排序。若 \(fa_{q_2}=q_1\) 且 \(\text{lca}(q_3,q_4)=q_2\) 且 \(b_{q_3}\equiv b_{q_4},b_{q_1}\equiv b_{q_2},b_{q_3}\equiv -b_{q_2} \pmod {p^k}\) ,那么可以通过链加计入答案,要求 \(b_{q_3}+k\equiv0\pmod {p^k}\)。
对 \(k\) 的限制可以使用 unordered_map 存起来,最后我们对于每个点对 \((u,v)\) 使用中国剩余定理合并出最终的答案 \(k\)。还不够,我们要求添加的 \(k\) 还是上述第一种情况最终求出的答案的约数,而这时候模数不一定是互质的,所以要使用扩展中国剩余定理。
还没完,如果原序列相同的数的数过多,进行树上差分之后可能找不出来 5 个非零的数,不过这里的特判就有些枯燥无聊了,详见代码。
点击查看代码
#include <bits/stdc++.h>
#define fr first
#define se second
#define Un unsigned
#define LL long long
#define pb push_back
#define pii pair<int,int>
#define pLi pair<LL,int>
#define pLL pair<LL,LL>
#define __ __int128
#define LD long double
#define VE vector<LL>
#define DB double
#define Ve vector<int>
using namespace std;
inline LL read()
{
LL x = 0,f = 1;char ch = getchar();
while(!isdigit(ch)) (ch == '-') && (f = -1),ch = getchar();
while(isdigit(ch)) x = x*10+ch-48,ch = getchar();
return x*f;
}
LL qp(LL x,int y)
{
LL res = 1;
while (y)
{
if (y&1) res = res*x;
x = x*x,y >>= 1;
}
return res;
}
const int N = 5e5+5;
struct edge{int to,pre;}e[N<<1];
int las[N],cnt,siz[N],fa[N],hs[N],dep[N],top[N],ct,t[100][70],tmp[5];
LL a[N],b[N],c[100];
struct node{LL p,a,w;};
unordered_map < int,node > mul[N];
void add(int u,int v){e[++cnt] = {v,las[u]},las[u] = cnt;}
void D1(int x)
{
dep[x] = dep[fa[x]]+1,siz[x] = 1,b[x] = a[x];
for (int i = las[x],y;i;i = e[i].pre)
{
if ((y = e[i].to) != fa[x])
{
fa[y] = x,D1(y),siz[x] += siz[y],b[x] -= a[y];
if (siz[y] > siz[hs[x]]) hs[x] = y;
}
}
}
void D2(int x,int t)
{
top[x] = t;
if (hs[x]) D2(hs[x],t);
for (int i = las[x],y;i;i = e[i].pre)
if ((y = e[i].to) != fa[x] && y != hs[x]) D2(y,y);
}
int LCA(int x,int y)
{
while (top[x] != top[y]) dep[top[x]] < dep[top[y]] ? y = fa[top[y]] : x = fa[top[x]];
return dep[x] < dep[y] ? x : y;
}
void exgcd(LL a,LL b,LL &x,LL &y)
{
if (!b) return (void)(x = 1,y = 0);
exgcd(b,a%b,y,x),y -= a/b*x;
}
const LL INF = 1e18;
int main()
{
int n = read(),id = read();
for (int i = 1;i <= n;i++) a[i] = read();
for (int i = 1,u,v;i < n;i++)
u = read(),v = read(),add(u,v),add(v,u);
n++,add(n,1),D1(n),D2(n,n);
int zer = 0,T = 5;
for (int i = 1;i <= n && T;i++)
{
if (!b[i]) continue;
LL k = abs(b[i]);T--;
for (LL j = 2;j*j <= k;j++)
{
if (k%j == 0)
{
c[++ct] = j;
while (k%j == 0) k /= j;
}
}
if (k > 1) c[++ct] = k;
}
sort(c+1,c+ct+1),ct = unique(c+1,c+ct+1)-c-1;
for (int i = 1;i <= n;i++)
{
if (b[i])
{
for (int j = 1;j <= ct;j++)
{
if (b[i]%c[j] == 0)
{
LL p = 0,k = b[i];
while (k%c[j] == 0) k /= c[j],t[j][++p]++;
}
}
}
else zer++;
}
LL nans = 0,uu,vv,ww;
if (zer == n || zer == n-2 || zer == n-4)
{
if (zer == n) return printf("%lld\n1 1 %lld\n",INF,INF),0;
int p = 0;
for (int i = 1;i <= n;i++) if (b[i]) tmp[++p] = i;
sort(tmp+1,tmp+p+1,[](int x,int y){return dep[x] < dep[y];});
if (p == 2 && LCA(tmp[1],tmp[2]) == tmp[1] && b[tmp[1]]+b[tmp[2]] == 0)
{
int f = tmp[2];
while (fa[f] != tmp[1]) f = fa[f];
return printf("%lld\n%d %d %lld",b[tmp[2]]+INF,f,tmp[2],INF),0;
}
if (p == 4 && tmp[1] == fa[tmp[2]] && LCA(tmp[3],tmp[4]) == tmp[2])
{
if (b[tmp[3]] == b[tmp[4]] && b[tmp[1]] == b[tmp[2]] && b[tmp[3]]+b[tmp[2]] == 0) return printf("%lld\n%d %d %lld",b[tmp[3]]+INF,tmp[3],tmp[4],INF),0;
LL k = __gcd(abs(b[tmp[3]]-b[tmp[4]]),abs(b[tmp[1]]+b[tmp[3]]));
nans = k,uu = tmp[3],vv = tmp[4],ww = (k-b[tmp[3]]%k)%k;
}
}
LL ans = 1;
for (int i = 1;i <= ct;i++)
{
int mx = 0;
for (int j = 1;t[i][j] && t[i][j]+zer >= n-4;j++)
{
if (t[i][j]+zer == n) mx = j;
else
{
LL val = qp(c[i],j),p = 0;
if (t[i][j]+zer == n-2 && t[i][j+1]+zer != n-2)
{
for (int k = 1;k <= n;k++) if (b[k]%val) tmp[++p] = k;
sort(tmp+1,tmp+p+1,[](int x,int y){return dep[x] < dep[y];});
if (LCA(tmp[1],tmp[2]) == tmp[1] && (b[tmp[1]]+b[tmp[2]])%val == 0)
{
LL k = (b[tmp[1]]%val+val)%val,w = qp(c[i],j-mx);
int f = tmp[2];
while (fa[f] != tmp[1]) f = fa[f];
if (!mul[f].count(tmp[2])) mul[f][tmp[2]] = {val,k,w};
else
{
auto [p1,a1,w1] = mul[f][tmp[2]];
LL k1,k2,l = val*p1;exgcd(val,p1,k1,k2);
LL res = (((__)val*k1%l*a1%l+(__)p1*k2%l*k%l)%l+l)%l;
mul[f][tmp[2]] = {l,res,w1*w};
}
}
}
else if (t[i][j]+zer == n-4 && t[i][j+1]+zer != n-4)
{
for (int k = 1;k <= n;k++) if (b[k]%val) tmp[++p] = k;
sort(tmp+1,tmp+p+1,[](int x,int y){return dep[x] < dep[y];});
if (tmp[1] == fa[tmp[2]] && LCA(tmp[3],tmp[4]) == tmp[2] && (b[tmp[3]]%val+val)%val == (b[tmp[4]]%val+val)%val && (b[tmp[1]]%val+val)%val == (b[tmp[2]]%val+val)%val && (b[tmp[3]]+b[tmp[2]])%val == 0)
{
LL k = (b[tmp[1]]%val+val)%val,w = qp(c[i],j-mx);
if (!mul[tmp[3]].count(tmp[4])) mul[tmp[3]][tmp[4]] = {val,k,w};
else
{
auto [p1,a1,w1] = mul[tmp[3]][tmp[4]];
LL k1,k2,l = val*p1;exgcd(val,p1,k1,k2);
LL res = (((__)val*k1%l*a1%l+(__)p1*k2%l*k%l)%l+l)%l;
mul[tmp[3]][tmp[4]] = {l,res,w1*w};
}
}
}
}
}
ans *= qp(c[i],mx);
}
LL mx = 0,val;pii Ans;
for (int u = 1;u <= n;u++)
{
for (auto [v,q] : mul[u])
{
auto [p,a,w] = q;
if (w > mx)
{
LL k1,k2;exgcd(p,ans,k1,k2);
LL d = __gcd(p,ans);
k1 = (k1*(-a/d))%(ans/d);
if (k1 < 0) k1 += ans/d;
LL m = p/__gcd(p,ans)*ans;
a = (__)(p*k1+a)%m;
if (a < 0) a += m;
mx = w,Ans = {u,v},val = a;
}
}
}
if (nans > ans) return printf("%lld\n%lld %lld %lld",nans,uu,vv,ww),0;
if (!mx) printf("%lld\n1 1 0",ans);
else printf("%lld\n%d %d %lld\n",ans*mx,Ans.fr,Ans.se,val);
return 0;
}
2025.2.18
A: 主教
比较简单的构造,别想复杂了就行,答案一定形如下图:

B:懒标记
赋值操作可以看成先乘 0,后加 x。所以我们只有乘法和加法操作。题目要求我们维护区间和的期望和单点平方的期望。
对于线段树的结点,我们维护 \(add,mul,sum,sum^2,add^2,mul^2,muladd\) 分别表示加法标记的期望,乘法标记的期望,区间和的期望,和平方的期望,加法标记平方的期望,乘法标记平方的期望,乘法标记乘加法标记的期望,就能维护答案了。
虽然不会期望,但是觉得这是个相当无聊的题目。
2025.2.19
B:生成树
完全图生成树想到 boruvka,每次找不在同一连通块内的最近的点可以使用点分治。
具体的,点分治过程,我们把当前分治中心的子树内的点的到当前分治中心的距离拿出来,进行一个排序。
而后就变成了对每个数找出与它不在同一子树且不在同一连通块内的最小的大于等于它的数,这是一个双颜色限制问题,把同一子树内的点放在序列中连续的一段的话就只剩下了一个限制,我们可以维护区间最小值和次小值,钦定最小值和次小值的颜色不能相同就能解决这个问题,时间复杂度 \(O(n\log^3n)\)。
2025.2.20
A: 小 X 与矩形
矩形合法等价于每个点的入度都为 1,接下来只要判定每个点的入度是否为 1。
只有 RL 的合法子串很好求出,下面只说同时有 RLUD 的合法子串如何求。
枚举合法串的起点,找到从起点开始的 \(\text{RL}\) 循环后的第一个字符,若为 U 或 L 那么已经无解了。下面分 R 和 D 来讨论。
首先对每个位置求出它及后面第一个 U 的位置,记为 \(nxt_i\)。
如果为 R,找到它后面出现的第一个 U,那么这个 R 的入度就是这个 U 提供的,如下:
RLRLRLRLR????
????????U
容易发现如果 U 在更前方的 ?中出现一定不合法。这时候 U 的位置找到了,我们矩形的宽也就确定了,为 \(nxt_j-i-(j-i)=nxt_j-i\)。
如果为 D,同样找到他后面出现的第一个 U,如下:
RLRLRLDLLL????
??????RRRU
和上方同理,矩形宽为 \(nxt_j-i-(j-i+sum_{j+1})=nxt_j-(j+sum_{j+1})\)。
求出宽之后,我们记录一下当前起点,矩形的宽度,以及起点模上矩形宽度的值,后面矩形宽度和起点模矩形宽度都相同的可以放在一块处理。
之后检查是否合法可以使用字符串哈希,拿中间行举例就是它上一行的 D 加上下一行的 U 加上当前行的 L 和 R 是否和全一的序列的哈希值相同。
C: 小 X 与木棍
赛时看到模数 998244353 和像是卷积的形式直接开始拆式子卷积了,一个教训就是别拿自己的经验来肯定的判断一个题,不然做一整场整个思路都不对。
由于有对 \(a_i\) 进行的修改,所以很自然地想到求出 \(f_i\) 代表长为 \(i\) 的木棍期望出现次数。
\(f_i=2\times\sum\limits_{j>i}\frac{f_ji(j-i-1)}{\binom j3}\),最终答案是 \(\sum\limits_{i>m}a_if_i\)。
一个不怎么自然的想法是 \(f_i\) 使用矩阵维护,将 \(f_i\) 拆成 \(\sum\limits_{j>i}\frac{f_j2i(j-1)}{\binom j3}-\sum\limits_{j>i}\frac{f_j2i^2}{\binom j3}\)。
这样之后就能通过矩阵乘法来维护 \(\sum\frac{f_i(i-1)}{\binom i3}\) 和 \(\sum\frac{f_i}{\binom i3}\)。
感觉思路类似动态 dp,带修使用矩阵维护一些东西来快速更新,这个 Trick 不太常见,比较厉害。
2025.2.11
A:小凯的乘号
如果序列有序可以分治求答案,设当前区间为 \([l,r]\) ,左右区间分别为 \(L=[l,mid],R=[mid+1,r]\)。记 \(sum_{l,r}=\sum\limits_{i=l}^ra_i\)。
分类讨论:
若三个值都在 \(L\) 或 \(R\) 答案为 \(ans_L\) 或 \(ans_R\)。
次小值在 \(L\),最大值在 \(R\),贡献为 \(\sum\limits_{x=l}^{mid}\sum\limits_{y=mid+1}^ra_xa_ysum_{l,x-1}2^{y-x-1}=(\sum\limits_{x=l}^{mid}a_xsum_{l,x-1}2^{mid-x})(\sum\limits_{y=mid+1}^ra_y2^{y-mid-1})=f_Lg_R\)
最小值在 \(L\),次小值在 \(R\),贡献为 \(\sum\limits_{x=mid+1}^r\sum\limits_{y=x}^ra_xa_ysum_{l,mid}2^{\max(y-x-1,0)}=s_Lh_R\)。
那么我们每一层求出对应的 \(f,g,s,h\) 就可以 \(O(n\log n)\) 处理每次询问,当然每次要排好序。
优化的话首先我们要在值域上进行操作来避免排序,先离散化一下。
在分治的每层求出 \(f,g,s,h\) 很费时间,而分治的结构又很像我们的线段树,那么是否可以使用值域线段树来快速合并左右区间来维护出 \(f,g,s,h\) 呢。
答案是可以的,具体实现比较麻烦,还需要一些辅助数组。
点击查看代码
#include <bits/stdc++.h>
#define fr first
#define se second
#define Un unsigned
#define LL long long
#define pb push_back
#define pii pair<int,int>
#define pLi pair<LL,int>
#define pLL pair<LL,LL>
#define __ __int128
#define LD long double
#define VE vector<LL>
#define DB double
#define Ve vector<int>
using namespace std;
inline int read()
{
int x = 0,f = 1;char ch = getchar();
while(!isdigit(ch)) (ch == '-') && (f = -1),ch = getchar();
while(isdigit(ch)) x = x*10+ch-48,ch = getchar();
return x*f;
}
const int N = 3e4,P = 998244353;
int a[N],bl[N],pw[N],cnt,b[N],Ans[N<<2];
struct Que{int l,r,id;}que[N<<2];
bool cmp(Que x,Que y){return bl[x.l] == bl[y.l] ? (bl[x.l]&1) ? x.r < y.r : x.r > y.r : x.l < y.l;}
LL qp(LL x,int y)
{
LL res = 1;
while (y)
{
if (y&1) res = res*x%P;
x = x*x%P,y >>= 1;
}
return res;
}
int sum[N<<2],ct[N<<2],ans[N<<2],f[N<<2],g[N<<2],h[N<<2],s[N<<2];
LL inv2 = qp(2,P-2);
void pushup(int i)
{
ct[i] = ct[i<<1]+ct[i<<1|1];
sum[i] = (sum[i<<1]+sum[i<<1|1])%P;
f[i] = (1LL*f[i<<1]*pw[ct[i<<1|1]]+1LL*s[i<<1|1]*sum[i<<1]+f[i<<1|1])%P;
g[i] = (g[i<<1]+1LL*g[i<<1|1]*pw[ct[i<<1]])%P,
s[i] = (1LL*s[i<<1]*pw[ct[i<<1|1]]+s[i<<1|1])%P;
h[i] = (h[i<<1]+h[i<<1|1]+1LL*s[i<<1]*g[i<<1|1])%P;
ans[i] = (ans[i<<1]+ans[i<<1|1]+1LL*f[i<<1]*g[i<<1|1]+1LL*sum[i<<1]*h[i<<1|1])%P;
}
void M1(int l,int r,int p,int i)
{
if (l == r)
{
h[i] = (h[i]+1LL*g[i]*b[l]+1LL*b[l]*b[l])%P;
f[i] = (2LL*f[i]+1LL*sum[i]*b[l])%P;
g[i] = (g[i]+1LL*b[l]*pw[ct[i]])%P;
s[i] = (2LL*s[i]+b[l])%P;
sum[i] = (sum[i]+b[l])%P,ct[i]++;
ans[i] = 1LL*(P+pw[ct[i]]-1-ct[i])*b[l]%P*b[l]%P*b[l]%P;
return ;
}
int mid = (l+r)>>1;
if (p <= mid) M1(l,mid,p,i<<1);
else M1(mid+1,r,p,i<<1|1);
pushup(i);
}
void M2(int l,int r,int p,int i)
{
if (l == r)
{
ct[i]--,sum[i] = (P+sum[i]-b[l])%P;
s[i] = 1LL*(P+s[i]-b[l])*inv2%P;
g[i] = ((g[i]-1LL*b[l]*pw[ct[i]])%P+P)%P;
f[i] = ((f[i]-1LL*sum[i]*b[l])%P*inv2%P+P)%P;
h[i] = ((h[i]-1LL*g[i]*b[l]-1LL*b[l]*b[l])%P+P)%P;
ans[i] = 1LL*(P+pw[ct[i]]-1-ct[i])*b[l]%P*b[l]%P*b[l]%P;
return ;
}
int mid = (l+r)>>1;
if (p <= mid) M2(l,mid,p,i<<1);
else M2(mid+1,r,p,i<<1|1);
pushup(i);
}
int main()
{
int n = read(),q = read(),B = n/sqrt(q)+1;
pw[0] = 1;for (int i = 1;i <= n;i++) pw[i] = (pw[i-1]*2)%P;
for (int i = 1;i <= n;i++) a[i] = read(),b[++cnt] = a[i],bl[i] = (i-1)/B+1;
sort(b+1,b+cnt+1),cnt = unique(b+1,b+cnt+1)-b-1;
for (int i = 1;i <= n;i++) a[i] = lower_bound(b+1,b+cnt+1,a[i])-b;
for (int i = 1;i <= q;i++) que[i] = {read(),read(),i};
sort(que+1,que+q+1,cmp);
int l = 1,r = 0;
for (int i = 1;i <= q;i++)
{
while (r < que[i].r) M1(1,cnt,a[++r],1);
while (r > que[i].r) M2(1,cnt,a[r--],1);
while (l < que[i].l) M2(1,cnt,a[l++],1);
while (l > que[i].l) M1(1,cnt,a[--l],1);
Ans[que[i].id] = ans[1];
}
for (int i = 1;i <= q;i++) printf("%d\n",Ans[i]);
return 0;
}
B:鸽子的心情
记录一下赛时思路。
\(d[i][S]\) 为点 \(i\) 向 \(S\) 内连的边数。
\(a[i][S]\) 为 \(S\) 连成一条端点为 \(i\) 的链(默认另一个端点为 \(S\) 中的编号最小的点)的方案数。
\(b[i][S]\) 为 \(S\) 连成一条端点为 \(i\) 的链的所有方案边权和。
\(f[S]\) 表示 \(S\) 内联通且恰有一个简单环的所有方案边权和。
\(g[S]\) 表示 \(S\) 内联通且恰有一个简单环的方案数。
\(h[S]\) 表示 \(S\) 内任意连边且不成环的方案数。
\(s[S]\) 表示 \(S\) 的生成树方案数。
由 \(a[i][S]\) 和 \(b[i][S]\) 可以求出初始的有且仅有一个环的边权和和方案数,这是我们基环树的环。
除去基环树的另一部分就是我们的 \(h[S]\) ,\(h[S]\) 可以通过 \(s[S]\) 进行半在线子集卷积求出。
也就是说接下来只需要把若干棵树拼到我们的环上就能求出答案,不过这一部分还没有很好的解决办法。
正解是考虑每条边对答案的贡献次数,不太好想,把式子摆出来。
2025.2.22
A: 公司
问题难点在哪?是看上去就一眼的费用流暴力吗?是怎么都想不出来且赛时认为是正解的反悔贪心吗?
我觉得是见到带权二分图匹配就从来没向最大匹配中的 Hull 定理进行思考。对答案计数或者最优化答案等问题先考虑最终答案的形式或者满足条件之后在维护满足这个限制的情况下来进行操作。
对于任意一个区间而言,和这个区间有交的线段提供的选择次数之和一定要大于等于这个区间选择的数的和,这是我们最终解的充要条件,所以我们要在满足这个充要条件的基础上来贪心。
首先第一个要解决的问题是快速求出和这个区间有交的线段提供的次数之和,这个使用单步容斥解决。
设 \(A_i\) 为 \(\sum\limits_j[l_j\le i]v_j\),\(B_i\) 为 \(\sum\limits_j[r_j<i]v_j\),对于区间 \([l,r]\) 直接用 \(A_r-B_{l-1}\) 就是我们要求的答案。
既然要贪心,我们肯定是按 \(b_i\) 从大到小来选人,若选的人的下标为 \(i\),那么我们选择这个人的个数的上界为 \(\min(c_i,\min\limits_{r\ge i}(A_r)-\max\limits_{l\le i}(B_l))\),这个充要条件的限制使用线段树维护即可。
B:机器人
二元关系,A 则 B,A 则非 B,B 则 A,B 则非 A,是否有解,\(\text{2-SAT}\)。
观察到具有单调性之后将边排序双指针 \(O(n^2)\) 枚举两侧最大值然后使用 \(\text{2-SAT}\) 进行判定,时间复杂度 \(O(n^4)\)。
上一步是简单的,应该走到的。
一个性质是一条边加入后如果形成了偶环,那么这条边添加的限制在之前已经被限制了,直接跳过,奇环当然是直接无解跳出了。
知道这个关键性质之后有用的边只有 \(O(n)\) 个,每个进行一次二分,总时间复杂度 \(Tn^3\log n\)。
对这个再进行优化需要一个结论,长为 \(n\) 的随机排列前缀最大/小值的个数是 \(O(\log n)\) 级别的。所以这个有用的 \(O(n)\) 条边拿出来 \(shuffle\) 一遍再进行求答案,若不可能更新答案就直接跳过,期望时间复杂度 \(O(Tn^3)\) 。

浙公网安备 33010602011771号