2024初三年后集训模拟测试3
前言
难度不好说,感觉是东拼西凑的题,但是除了 \(T1\) 都相当不简单。
而且不知道为啥以后的比赛时间都少了半个小时。
-
\(T1~100pts:\)
签到题,贪心即可。
-
\(T2~70pts:\)
读假题了,是最大 \(w_i\) 不是固定 \(w_i\) ,做法是 二分答案+DP ,不过需要单调队列优化,不会这玩意儿赛后学了好久 \(qwq\) 。
但是读假题了还能拿 \(70pts\) 。
-
\(T3~0pts:\)
不会,想到了树形 \(DP\) ,但建树都没建明白。
题解给了一半,正解没给,给了个复杂度 \(O(n^2)\) 的要过 \(3e5\) ,
差评! -
\(T4~0pts:\)
本来人手骗 \(20pts\) 的,\(continue\) 加错地方了,喜提 \(0pts\) 。
题解更加抽象,教练发现快吃晚饭了还没有人 \(A\) ,直接又把代码放里面了;至今 2024年02月21日20:55:27 还是没人 \(A\) ,又搞来一份详细题解,把代码加上了注释。
可怜其良苦用心,但那篇题解码风太抽象了……
T1 排序
-
题意:
给定 \(4n\) 个数 \(s_i\) ,分别 \(n\) 组,对于每组 \(4\) 个数 \(a,b,c,d\) ,求所有组 \(|ab-cd|\) 的最大和。
-
解法:
-
导入一个常识:
已知 \(s\) ,将其拆分为 \(s=a+b\) ,若想 \(a\times b\) 尽可能小,则 \(|a-b|\) 尽可能大;若想 \(a\times b\) 尽可能大,则 \(|a-b|\) 尽可能小。
和相同周长的 \(S_{正方形}>S_{长方形}\) 是一个道理。
先将所有 \(s_i\) 排序,设 \(ab>cd\) 。
将所有 \(s_i\) 分为大的和小的两组,\(a,b\) 相邻搭配,\(c,d\) 分别从两边取即可。
直接看代码就行了,签到题。
-
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=1;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,a[N],ans,sum;
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
for(int i=1;i<=4*n;i++)
read(a[i]);
stable_sort(a+1,a+1+4*n);
for(int i=1,j=4*n;i<=n,j>=2*n+1;i++,j-=2)
ans+=a[j]*a[j-1]-a[i]*a[2*n-i+1];
cout<<ans;
}
T2 牛吃草
- 题意:
此处注意他是延伸距离最大为 \(w_i\) ,不是固定 \(w_i\) ,赛时因为这个读假题了 \(qwq\) 。
-
部分分:
-
\(70pts:\)
读假题的情况,二份答案+ \(O(n)的DP\) 。
定义 \(f[i][1]\) 为第 \(i\) 个位置上放的最大覆盖长度,反之 \(f[i][0]\) 就是不放的情况。
转移方程为:
\(f[i][1]=\max(f[i-len[i]][1],f[i-len[i]][0])+len[i];\)
\(f[i][0]=\max(f[i-1][1],f[i-1][0])\) 。
此处 \(len_i\) 为其真实的 \(w_i\) ,因为他左端点顶到头就 \(1\) 了,再往前就负了,比如不管 \(w_1\) 为多少,\(len_1\) 都 \(=1\) 。
因为读假题了, \(DP\) 自然成 \(O(n)\) 了。
复杂度最坏情况下 \(O(n\log(n))\) ,实则只要 \(f_i>=s\) 就说明满足,跳出就行了,所以真实复杂度远小于此。
点击查看代码
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=5e5+10; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int n,s,l,r,maxx,mid,ans,w,f[N][2]; struct aa { int l,len; }a[N]; bool DP(int x,int s) { for(int i=1;i<=n;i++) f[i][0]=f[i][1]=0; for(int i=1;i<=n;i++) { if(a[i].len>=x) f[i][1]=max(f[a[i].l-1][1],f[a[i].l-1][0])+a[i].len; f[i][0]=max(f[i-1][1],f[i-1][0]); if(f[i][1]>=s||f[i][0]>=s) return 1; } return 0; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<=n;i++) read(w), a[i].l=max(1ll,i-w+1), a[i].len=i-a[i].l+1, maxx=max(maxx,a[i].len); read(s); s=ceil(double(n*s/100.0)); l=1,r=maxx; while(l<=r) { mid=(l+r)>>1; if(DP(mid,s)) ans=max(ans,mid),l=mid+1; else r=mid-1; } cout<<ans; }
-
\(90pts:\)
把题读真了,发现还需要套一层循环。
循环 \(j=\{i-len_i\sim i-size\}\) ,那么转移方程为:
\(f[i][1]=\max(\{f[i][1],f[j][1]+i-j,f[j][0]+i-j\});\)
\(f[i][0]\) 和 \(70pts\) 的一样。
这样复杂度最坏就成了 \(O(n^2\log(n))\) ,虽然真实情况比这小,但还是会 \(TLE\) 两个点。
点击查看代码
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=5e5+10; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int n,s,l,r,maxx,mid,ans,w,f[N][2]; struct aa { int l,len; }a[N]; bool DP(int x,int s) { f[0][0]=f[0][1]=0; for(int i=1;i<=n;i++) { f[i][1]=0; for(int j=i-a[i].len;j<=i-x;j++) f[i][1]=max({f[i][1],f[j][1]+i-j,f[j][0]+i-j}); f[i][0]=max(f[i-1][1],f[i-1][0]); if(f[i][1]>=s||f[i][0]>=s) return 1; } return 0; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<=n;i++) read(w), a[i].l=max(1ll,i-w+1), a[i].len=i-a[i].l+1, maxx=max(maxx,a[i].len); read(s); s=ceil(double(n*s/100.0)); l=1,r=maxx; while(l<=r) { mid=(l+r)>>1; if(DP(mid,s)) ans=max(ans,mid),l=mid+1; else r=mid-1; } cout<<ans; }
-
-
正解:
在 \(90pts\) 的做法上使用单调队列优化 \(DP\) 。
∵ 题目数据范围中给了 \(w_{i-1}\geq w_i-1\) ,
∴ \(i-1-w_{i-1}\leq i-1-(w_i-1)\) ,
∴ \(i-1-w_{i-1}\leq i-w_i\) 。
由此,可以使用单调队列优化 \(DP\) 。
至于这玩意之前给跳了 \(qwq\) ,
反正会的肯定会的。点击查看代码
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=5e5+10; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int n,s,l,r,maxx,mid,ans,w,f[N][2],t,head,tail,len[N]; struct aa { int id,num; }q[N]; bool DP(int x,int s) { head=1,tail=0; f[0][1]=f[0][0]=0; for(int i=1;i<=n;i++) { f[i][0]=f[i][1]=0; if(i>=x) { t=max(f[i-x][1],f[i-x][0])+n-(i-x); while(head<=tail&&q[tail].num<t) tail--; q[++tail].num=t; q[tail].id=i-x; while(head<=tail&&q[head].id<i-len[i]) head++; } f[i][0]=max(f[i-1][1],f[i-1][0]); f[i][1]=max(f[i][1],(head>tail?0:q[head].num)+i-n); if(f[i][1]>=s||f[i][0]>=s) return 1; } return 0; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<=n;i++) read(w), l=max(1ll,i-w+1), len[i]=i-l+1, maxx=max(maxx,len[i]); read(s); s=ceil(double(n*s/100.0)); l=1,r=maxx; while(l<=r) { mid=(l+r)>>1; if(DP(mid,s)) ans=max(ans,mid),l=mid+1; else r=mid-1; } cout<<ans; }
T3 树上的宝藏
-
题意:
对于一颗数上有 \(n\) 个节点,每个节点上填入 \(1\) 或 \(0\) ,\(n-1\) 条边。
每个普通的边连接的两点最多只能有一个 \(1\) ,也就是 \([1,0],[0,1],[0,0]\) 三种情况。
每个特殊的边连接的两点最少必须有一个 \(1\) ,也就是 \([1,0],[0,1],[1,1]\) 三种情况。
求在第 \(i\) 条边为特殊边,其余为普通边的情况下可行的情况数量。
-
部分分:
-
\(30pts:\) 纯暴力。
-
\(60pts:\)
树形 \(DP\) 显然,下面说思路:
对于第 \(i\) 条边,将其两节点 \(a,b\) 分别为根拆成两棵树,在每棵树上跑树形 \(DP\) 。
定义 \(f[i][1]\) 为以 \(i\) 为根的数(子树)\(i\) 处为 \(1\) 的情况数,同样的 \(f[i][0]\) 就是 \(i\) 处为 \(0\) 的情况数。
结合对于题意的分析,若根节点为 \(1\) ,则子节点必须为 \(0\) ;若根节点为 \(0\) ,则子节点可以为 \(1\) 或 \(0\) 。
那么便有了转移方程,设 \(u,v\) 分别为根节点和叶节点:
\(f[u][1]\times =f[v][0]\) ;
\(f[u][0]\times =(f[v][1]+f[v][0])\) 。
而问题来到怎么打又儿子向父亲传的树形 \(DP\) :
先跑一遍 \(dfs\) ,处理出每个节点的 \(son\) 与 \(father\) ,然后根据此来跑树形 \(DP\) 就可以了,具体的看代码就理解了。
最后处理答案:
现在以 \(a,b\) 为根的树都处理完了,需要将他们合并。
结合上面题意的分析,特殊边时,有 \(3\) 中情况:\([1,1],[1,0],[0,1]\) 。
对应的,\(ans=f[a][1]\times f[b][1]+f[a][1]\times f[b][0]+f[a][0]\times f[b][1]\) 。
就结束了,别忘了 \(\bmod ~P=998244353\) 。
最后分析复杂度:不难发现要从 \(1\sim n-1\) 每次都要跑 \(dfs\) 和树形 \(DP\) ,每次都是 \(O(n)\) 的,这样复杂度就成了 \(O(n^2)\) 。
点击查看代码
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=3e5+10,P=998244353; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int n,x,y,tx[N],ty[N],f[N][2],ans,father[N]; vector<int>e[N],son[N]; void dfs(int x,int fa) { for(int y:e[x]) { if(y==fa) continue; father[y]=x; son[x].push_back(y); dfs(y,x); } } void dp(int x) { if(!son[x].size()) { f[x][0]=f[x][1]=1; return ; } for(int y:son[x]) dp(y), (f[x][1]*=f[y][0])%=P, (f[x][0]*=(f[y][0]+f[y][1]))%=P; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<n;i++) read(x),read(y), e[x].push_back(y), e[y].push_back(x), tx[i]=x,ty[i]=y; for(int i=1;i<n;i++) { int a=tx[i],b=ty[i]; for(int j=0;j<=n;j++) father[j]=f[j][0]=f[j][1]=1, son[j].clear(); dfs(a,b),dp(a); dfs(b,a),dp(b); ans=(f[a][1]*f[b][1])%P+(f[a][1]*f[b][0])%P+(f[a][0]*f[b][1])%P; cout<<ans%P<<endl; } }
-
-
正解:
和上面思路是类似的,但是解决了上面要跑 \(n-1\) 遍 \(dfs,DP\) 的问题。
砍断操作可以用换根 \(DP\) 优化。
-
先跑一遍正常的 \(dfs\) 和 树形 \(DP\) ,处理出 \(f[i][0||1]\) 。
-
进行换很 \(DP\) :
\(r[i][1||0]\) 表示以 \(i\) 为整棵树的根节点时选 \(i\) 和不选 \(i\) 的方案数。
根节点又 \(fa→x\) 时,之前以 \(x\) 为根的子树内对其贡献不变,需要消除之前以 \(x\) 为根的子树对以 \(fa\) 为根的子树的影响,于是得出转移方程:
\(r[x][1]=f[x][1]\times \dfrac{r[fa][0]}{f[x][1]+f[x][0]};\)
\(r[x][0]=f[x][0]\times (\dfrac{r[fa][0]}{f[x][0]+f[x][1]}+\dfrac{r[fa][1]}{f[x][0]})\) 。
-
答案处理:
对于断开的边,默认 \(a\) 为其连接的父亲节点,\(b\) 为其连接的子节点,需要消除以 \(b\) 为根节点子树对于以 \(a\) 为根的树的影响,则为:
\(ans=\dfrac{r[a][1]}{f[b][0]}\times f[b][0]+\dfrac{r[a][1]}{f[b][0]}\times f[b][1]+\dfrac{r[a][0]}{f[b][1]+f[b][0]}\times f[b][1]\) 。
-
关于 \(\bmod P:\)
只要是两个数相乘就要 \(\bmod P\),两数相加不必 \(\bmod P\) ,观察仔细,不要漏了一处需要模的地方。
至于模意义下的除法,用乘法逆元不方便,线性推还会炸空间,可以用快速幂,\(a^{p-2}≡a^1\pmod p\) ,即费马小定理。
点击查看代码
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=3e5+10,P=998244353; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int n,x,y,tx[N],ty[N],f[N][2],r[N][2],ans,father[N]; vector<int>e[N],son[N]; void dfs(int x,int fa) { for(int y:e[x]) { if(y==fa) continue; father[y]=x; son[x].push_back(y); dfs(y,x); } } int qpow(int a,int b,int p) { int ans=1; while(b>0) { if(b&1) (ans=ans*a)%=p; b>>=1; (a=a*a)%=p; } return ans%p; } void dp(int x) { if(!son[x].size()) { f[x][0]=f[x][1]=1; return ; } for(int y:son[x]) dp(y), (f[x][1]*=f[y][0])%=P, (f[x][0]*=(f[y][0]+f[y][1]))%=P; } void reroot(int x,int fa) { if(x!=1) { int f0=(r[fa][0]*qpow(f[x][0]+f[x][1],P-2,P))%P, f1=(r[fa][1]*qpow(f[x][0],P-2,P))%P; r[x][0]=(f[x][0]*(f0+f1))%P; r[x][1]=(f[x][1]*f0)%P; } for(int y:e[x]) if(y!=fa) reroot(y,x); } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<n;i++) read(x),read(y), e[x].push_back(y), e[y].push_back(x), tx[i]=x,ty[i]=y; for(int i=0;i<=n;i++) f[i][1]=f[i][0]=1; dfs(1,0); dp(1); r[1][0]=f[1][0],r[1][1]=f[1][1]; reroot(1,0); for(int i=1;i<n;i++) { int a=tx[i],b=ty[i],c; if(father[a]==b) c=a, a=b, b=c; ans=(((r[a][1]*qpow(f[b][0]%P,P-2,P))%P*f[b][1])%P+r[a][1]+((r[a][0]*qpow(f[b][1]+f[b][0],P-2,P))%P*f[b][1])%P)%P; cout<<ans%P<<endl; } }
另外的思路跳转 @Ishar-zdl 。
2024年02月21日21:53:38 ,\(T3\) 的官方题解被教练放进去了,(⊙o⊙)…,就是这份 \(\large{↑}\) 。
-
T4 MEX
点击查看抽象的官方题解
const int MAXN = 1e6 + 5;
int n, m;
int arr[MAXN];
int last[MAXN];
int nxt[MAXN];
long long answer[MAXN];
struct SegmentTree {
public:
int min, num;
long long sum;
class LazyTag {
public:
int cover, tim;
long long add;
inline LazyTag() {
cover = -1;
add = 0;
tim = 0;
}
inline LazyTag(const int cover): cover(cover) {
if (cover == -1) {
tim = 1;
}
}
} tag;
} sgt[MAXN << 2];
inline void PushUp(const int now) {
sgt[now].min = min(sgt[now << 1].min, sgt[now << 1 | 1].min);
}
void Build(const int now = 1, const int left = 0, const int right = m) {
if (left == right) {
sgt[now].num = sgt[now].min = last[left];
return;
}
Build(now << 1, left, (left + right) >> 1);
Build(now << 1 | 1, ((left + right) >> 1) + 1, right);
PushUp(now);
}
inline void Down(const SegmentTree::LazyTag tag,
const int now, const int left, const int right) {
sgt[now].sum += 1ll * sgt[now].num * tag.tim + tag.add;
if (~tag.cover) {
if (~sgt[now].tag.cover) {
sgt[now].tag.add += 1ll * sgt[now].tag.cover * tag.tim + tag.add;
} else {
sgt[now].tag.tim += tag.tim;
sgt[now].tag.add = tag.add;
}
sgt[now].num = sgt[now].min = tag.cover;
sgt[now].tag.cover = tag.cover;
} else {
if (~sgt[now].tag.cover) {
sgt[now].tag.add += 1ll * sgt[now].tag.cover * tag.tim;
} else {
sgt[now].tag.tim += tag.tim;
}
}
}
inline void PushDown(const int now, const int left, const int right) {
Down(sgt[now].tag, now << 1, left, (left + right) >> 1);
Down(sgt[now].tag, now << 1 | 1, ((left + right) >> 1) + 1, right);
sgt[now].tag = SegmentTree::LazyTag();
}
void Updata(const int now_left, const int now_right,
const int cover, const int now = 1,
const int left = 0, const int right = m) {
if (now_right < left || right < now_left) {
return;
}
if (now_left <= left && right <= now_right) {
Down(SegmentTree::LazyTag(cover), now, left, right);
return;
}
PushDown(now, left, right);
Updata(now_left, now_right, cover, now << 1, left, (left + right) >> 1);
Updata(now_left, now_right, cover,
now << 1 | 1, ((left + right) >> 1) + 1, right);
PushUp(now);
}
inline void Time() {
Down(SegmentTree::LazyTag(-1), 1, 1, m);
}
int Find(const int now_left, const int now_right,
const int val, const int now = 1,
const int left = 0, const int right = m) {
if (now_right < left || right < now_left || val <= sgt[now].min) {
return -1;
}
if (left == right && now_left <= left && right <= now_right) {
return left;
}
int result(Find(now_left, now_right, val, now << 1 | 1,
((left + right) >> 1) + 1, right));
return ~result ? result : Find(now_left, now_right,
val, now << 1, left, (left + right) >> 1);
}
void GetAnswer(const int now = 1, const int left = 0, const int right = m) {
if (left == right) {
answer[left] = sgt[now].sum;
return;
}
PushDown(now, left, right);
GetAnswer(now << 1, left, (left + right) >> 1);
GetAnswer(now << 1 | 1, ((left + right) >> 1) + 1, right);
}
int main() {
scanf("%d%d", &n, &m);
int last0 = 0;
long long answer0 = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
if (arr[i] == 0) {
answer0 += (i - last0 - 1ll) * (i - last0) >> 1;
last0 = i;
}
}
answer0 += (n - last0) * (n + 1ll - last0) >> 1;
for (int i = 0; i <= m; i++) {
last[i] = n + 1;
}
for (int i = n; i >= 1; i--) {
nxt[i] = last[arr[i]];
last[arr[i]] = i;
}
for (int i = 1; i <= m - 1; i++) {
last[i] = max(last[i], last[i - 1]);
}
Build();
for (int i = 1; i <= n; i++) {
Time();
int f = Find(arr[i], m, nxt[i]);
if (~f) {
Updata(arr[i], f, nxt[i]);
}
}
GetAnswer();
int l, r;
scanf("%d%d", &l, &r);
if (!l) {
printf("%lld ", answer0);
l = 1;
}
for (int i = l; i <= r; i++) {
printf("%lld ", answer[i] - answer[i - 1]);
}
return 0;
}
点击查看翻译版代码
#include<bits/stdc++.h>
#define int long long
#define N (1000010)
#define ls (p<<1)
#define rs (p<<1|1)
#define I i
#define J j
#define raed read
#define reaD read
#define reAD read
#define rEAD read
#define READ read
#define REAd read
#define REad read
#define Read read
#define Reda read
#define redA read
#define reDA read
#define redA read
#define itn signed
#define Itn signed
#define ITN signed
#define Int signed
#define INT signed
#define foR for
#define fot for
#define foT for
#define sort stable_sort
using namespace std;
namespace IO
{
#define ll long long
const int MAX=1<<24;
char buf[MAX],*p1=buf,*p2=buf;
char obuf[MAX],*o=obuf;
#define gc()(p1==p2&&(p2=(p1=buf)\
+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//template<typename T>
//inline T read()
inline int read()
{
int x=0;bool f=1;
char c=gc();
for(;c<48||c>57;c=gc())if(c=='-')f=0;
for(;c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c^48);
return f?x:~x+1;
}
void print(ll x){if(x>9)print(x/10);*o++=(x%10)+'0';}
void pit(ll x){if(x<0)*o++='-',x=~x+1;print(x);}
void write(ll x,char ed){pit(x);*o++=ed;}
void flush(){fwrite(obuf,o-obuf,1,stdout);}
#undef ll
}
using IO::read;using IO::write;using IO::flush;using std::complex;
inline int min(int x,int y){return y&((y-x)>>31)|x&(~(y-x)>>31);}
inline int max(int x,int y){return x&((y-x)>>31)|y&(~(y-x)>>31);}
inline void swap(int &x,int &y){x^=y^=x^=y;}
long long n,m;
void init_set()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
}
int tot,x,y;
struct aa
{
int nahida,sumeru,panzer;//min num sum
//min 最小值, num 记录区间和, sum 记录历史和
struct lazytag
{
int furina,Paimon,fucaros;//cover time add
//cover -1 不覆盖, time 乘法懒标记,记录历史被求和过多少次, add 加法懒标记
//构造函数,初始化
inline lazytag()
{furina=-1,Paimon=fucaros=0;}
//赋值
inline lazytag(int furina):furina(furina)
{if(furina==-1)Paimon=1;}
}tag;
}t[4000010];
bool tp2=true;
int disco;
int fontaine[1000010];//a
int honkai_star_rail;//last_0
int kamisato_ayaka;//answer_0
int Cyberpunk_2077[1000010];//next
int Soviet[1000010];//last
int honkai[1000010];
inline void pushup(int p)
{
t[p].nahida=min(t[ls].nahida,t[rs].nahida);
t[p].sumeru=t[ls].sumeru,t[rs].sumeru;
t[p].panzer=t[ls].panzer,t[rs].panzer;
}
void build(int p=1,int l=0,int r=m)
{
if(l==r){t[p].sumeru=t[p].nahida=Soviet[l];return;}
int mid(l+r>>1);
build(ls,l,mid),build(rs,mid+1,r);
pushup(p);
}
inline void down(aa::lazytag tag,int p,int l,int r)
{
t[p].panzer+=t[p].sumeru*tag.Paimon+tag.fucaros;
//sum=sum+num*time+add
if(~tag.furina)
{
if(~t[p].tag.furina)//下传 add
t[p].tag.fucaros+=t[p].tag.furina*tag.Paimon+tag.fucaros;
else
t[p].tag.Paimon+=tag.Paimon,//下传 time
t[p].tag.fucaros+=tag.fucaros;//下传 add
t[p].sumeru=t[p].nahida=tag.furina;
t[p].tag.furina=tag.furina;//下传 cover
}
else
{
//下传 add
if(~t[p].tag.furina)t[p].tag.fucaros+=t[p].tag.furina*tag.Paimon;
//下传 time
else t[p].tag.Paimon+=tag.Paimon;
}
}
inline void pushdown(int p,int l,int r)
{
int mid(l+r>>1);
down(t[p].tag,ls,l,mid),down(t[p].tag,rs,mid+1,r);
t[p].tag=aa::lazytag();
}
void update(int p,int ql,int qr,int l,int r,int v)
{
if(qr<l||r<ql)return;
if(ql<=l&&r<=qr)
{down(aa::lazytag(v),p,l,r);return;}
pushdown(p,l,r);
int mid(l+r>>1);
update(ls,ql,qr,l,mid,v),update(rs,ql,qr,mid+1,r,v);
pushup(p);
}
inline void PaimonDown()
{down(aa::lazytag(-1),1,1,m);}
int search(int p,int ql,int qr,int l,int r,int v)
{
if(qr<l||r<ql||v<=t[p].nahida)return -1;
//越界或比该最小值还要小。
if(l==r&&ql<=l&&r<=qr)return l;
int mid(l+r>>1);
int res(search(rs,ql,qr,mid+1,r,v));
return ~res?res:search(ls,ql,qr,l,mid,v);
}
void SetAns(int p=1,int l=0,int r=m)
{
if(l==r){honkai[l]=t[p].panzer;return;}
int mid(l+r>>1);
pushdown(p,l,r),SetAns(ls,l,mid),SetAns(rs,mid+1,r);
}
signed main()
{
init_set();
n=read();m=read();
for(signed i(1);i<=n;++i)
{
fontaine[i]=read();
if(!fontaine[i])
{
kamisato_ayaka+=(i-honkai_star_rail-1)*(i-honkai_star_rail)>>1;//对 0 进行特判。
honkai_star_rail=i;
}
if(fontaine[i]!=i%m)tp2=false;
}
if(tp2)//10pts 特判所有元素都为 i%m 的情况
{
x=read(),y=read();
disco=n*(n+1)>>1;
for(int i(x);i<=y;++i)
{
if(i==0)write(((n/m)*((m-1)*m)>>1)+(((n%m))*(n%m+1)>>1),' '),
disco-=((n/m)*((m-1)*m)>>1)+(((n%m))*(n%m+1)>>1);
else if(i==m)
write(disco,' ');
else write(((n-i+1)/m)*(m-i),' '),disco-=((n-i+1)/m)*(m-i);
}
flush();exit(0);
}
kamisato_ayaka+=(n-honkai_star_rail)*(n+1-honkai_star_rail)>>1;
for(int i(0);i<=m;++i)
Soviet[i]=n+1;//last 数组初始化。
for(int i(n);i;--i)
Cyberpunk_2077[i]=Soviet[fontaine[i]],
//next 数组存下一个 i 的位置 ,由于倒序,所以是下一个。
Soviet[fontaine[i]]=i;//之后让 last 数组记录上一个。
//未出现过 i ,last=next=n+1 。
//只出现一次,last 为第一次出现位置, nxt=n+1
//出现两次以上 , last 同上, nxt 为第二次出现位置。
for(int i(1);i<m;++i)
Soviet[i]=max(Soviet[i],Soviet[i-1]);
//无论如何, mex 为 i 时,区间内至少也要有 1 至 i-1 。
build(1,0,m);
for(int i(1);i<=n;++i)
{
PaimonDown();
int browser(search(1,fontaine[i],m,0,m,Cyberpunk_2077[i]));
//二分查找在 a[i] 与 m 之间的最大的数位置。
if(~browser)update(1,fontaine[i],browser,0,m,Cyberpunk_2077[i]);
}
SetAns(1,0,m);
x=read(),y=read();
if(!x)write(kamisato_ayaka,' '),x=1;
for(int i(x);i<=y;++i)
write(honkai[i]-honkai[i-1],' ');
flush();
return 0;
}
总结:
题不要读假。
不要随便跳过知识点,抽时间回去把落下的学了。
希望以后的官方题解能及时一点,且码风正常一点,不要再有错的题解。