HGOI20190808 省常中互测1
Problem A sum
给出$n$个元素的序列$\{a_i\}$,求出两个不相交连续子序列的最大元素和。
即对于$1 \leq A \leq B \leq C \leq D \leq n$最大化 $\sum\limits_{i=A}^B a_i + \sum\limits_{i=C}^D a_i $
对于$100\%$的数据满足 $1 \leq n \leq 10^5$ , $0 \leq |a_i| \leq 10^9$
Sol: 考虑一个$O(n^2)$暴力,枚举分割点,左右各求一遍最大连续字段和,然后相加求max。
显然,上述做法内层$O(n)$的枚举没有必要,我们可以均摊$O(1)$求出前缀、后缀最大连续字段和。
于是上述暴力算法可以优化到$O(n)$
# include <bits/stdc++.h> # define int long long using namespace std; const int N=1e5+10; int a[N],n,f[N],b[N]; signed main() { scanf("%lld",&n); for (int i=1;i<=n;i++) scanf("%lld",&a[i]); int Max=-0x3f3f3f3f,ret=0; for (int i=1;i<=n;i++) { if (ret<0) ret=0; ret+=a[i]; Max=max(Max,ret); f[i]=Max; } Max=-0x3f3f3f3f,ret=0; for (int i=n;i>=1;i--) { if (ret<0) ret=0; ret+=a[i]; Max=max(Max,ret); b[i]=Max; } int ans=-0x3f3f3f3f; for (int i=1;i<=n-1;i++) ans=max(f[i]+b[i+1],ans); printf("%lld\n",ans); return 0; }
Problem B sequence
维护一个初始全为0的$n$个元素的数列$a_i$,有$m$次操作,每一次可以对区间$l,r$加一个等差数列。
格式为 :"l r s e"表示在$i \ in [l,r] , l < r $对其加一个等差数列,保证等差数列每一项都是整数。
最后询问最终的元素异或和。
对于$100\%$的数据,$1 \leq n,m \leq 5 \times 10^5$,序列中的元素任意时刻都不超过$long \ long$范围
Sol: 对数列的差分数组进行维护,区间$[l,r]$加等差数列就相当于对起始点加一个$s$,对终止点后一位减去$e$
对区间$[l+1,r]$加一个公差$d$。显然可以再对差分数组维护一个差分然后用树状数组完成。
最后求2遍前缀和即可。
复杂度是$O(n \ log_2 \ n)$
# include<bits/stdc++.h> # define int long long using namespace std; const int N=5e5+10; int c[N],n,m,w[N]; inline int read() { int X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } # define lowbit(x) ((x)&(-x)) void update(int x,int y){for (;x<=n;x+=lowbit(x)) c[x]+=y;} int query(int x){int ret=0;for (;x;x-=lowbit(x)) ret+=c[x]; return ret;} void modify(int l,int r,int d){update(l,d); update(r+1,-d);} signed main() { n=read();m=read(); for (int i=1;i<=m;i++) { int l=read(),r=read(),s=read(),e=read(); int d=(e-s)/(r-l); modify(l,l,s); modify(l+1,r,d); modify(r+1,r+1,-e); } for (int i=1;i<=n;i++) w[i]=query(i); for (int i=1;i<=n;i++) w[i]+=w[i-1]; int ans=0; for (int i=1;i<=n;i++) ans^=w[i]; printf("%lld\n",ans); return 0; }
Problem C mod
给定正整数$n$,定义一个长度为$n$ 的排列$A$ 的价值$w_A$:
若不存在长度为$n$ 且字典序比$A$大的排列,则$w=0$;
否则令$B$为长度为$n$的字典序恰好比$A$大1的排列,$w$ 为$A$ 和$B$不相同的位数的个数。
给定$n$和$p$,求所有长度为$n$的排列的$w$的和对$p$取模的结果。
对于$100\%$的数据满足$n \leq 10^7$
Sol : 考虑枚举$1$个元素,可能的取值是$[1,n]$,
而$[2,n]$的问题就变成了一个子问题。 注意到此时还没有计算进的情况,还需要加上$n(n-1)$
然后若$n$为偶数,那么需要减去一个$d = 2$的等差数列,于是我们的dp方程就会是:
$f_ i = i \times f_{i-1} + i \times (i-1) - (i\%2==0)?(i-2):0 $
复杂度是$O(n)$
# include <bits/stdc++.h> # define int long long using namespace std; int f[2],n,p; signed main() { int T; scanf("%lld",&T); while (T--) { scanf("%lld%lld",&n,&p); f[0]=f[1]=0; int t=1; for (int i=2;i<=n;i++) { t^=1; f[t]=(i&1)?(f[t^1]*i+i*(i-1))%p:(f[t^1]*i+i*(i-1)-i+2)%p; } printf("%lld\n",(f[t]+p)%p); } return 0; }
Problem D merge
维护$n$个元素$[a_1,a_n]$,初始所有元素都属于不同的集合。
维护两个操作:
1 x y 将$a_x$元素所在集合$S_x$ 与$a_y$元素所在元素$S_y$ 合并
2 x y 将$a_x$元素所在集合$S_x$中所有元素$+y$
对于$100\%$的数据$n\leq 5\times 10^5$
Sol: 对于第一个操作,直接找出$S_x$的代表元素(也就是该子树的树根),然后在根节点累加标记。
对于第二个操作,直接新增一个节点,把$S_x$的代表元素和$S_y$的代表元素并到这个节点上即可。
然后最后形成的是一棵森林,然后对于森林里的每一棵树从根节点开始做一遍dfs求出前缀和即是答案。
注意到这样子每一次暴力向上跳找代表元素这个过程可以用并查集压缩向上跳的路径,原来的树形结构是不能变化的。
这样子复杂度就是$O(n+m)$了。
# include <bits/stdc++.h> # define int long long using namespace std; const int N=1e6+10; struct rec{ int pre,to;}a[N<<1]; int f[N],ans[N],d[N],tot,n,m,head[N],du[N],g[N]; bool vis[N]; int father(int x) { if (f[x]==x) return x; //return father(f[x]); return f[x]=father(f[x]); } void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } void dfs(int u) { vis[u]=1; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (vis[v]) continue; ans[v]=ans[u]+d[v]; dfs(v); } } signed main() { scanf("%lld%lld",&n,&m); for (int i=1;i<=n+m;i++) f[i]=g[i]=i; int cnt=n; while (m--) { int op,x,y; scanf("%lld%lld%lld",&op,&x,&y); if (op==1) { int fx=father(x),fy=father(y); ++cnt; g[fx]=f[fx]=cnt; g[fy]=f[fy]=cnt; } else { int fx=father(x); d[fx]+=y; } } for (int i=1;i<=cnt;i++) if (i!=g[i]) adde(i,g[i]),adde(g[i],i),du[i]++; for (int i=1;i<=cnt;i++) if (du[i]==0&&!vis[i]) ans[i]=d[i],dfs(i); for (int i=1;i<=n;i++) printf("%lld ",ans[i]); puts(""); return 0; }