ABC240
D
Content
依次向一个序列里插入写有数字的球,当出现连续球上数字个的相同的球时就将它们消去,维护每一次插入后的序列中球的个数。
Sol
把序列换成栈模拟就好了
Code
#include<bits/stdc++.h>
using namespace std;
const int _=2e5+5;
int n,a[_],stk[_],tp,cnt[_];
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
if(a[i]==a[stk[tp]]) cnt[i]=cnt[stk[tp]];
++cnt[i];
if(cnt[i]==a[i]){
tp-=a[i]-1;
}
else stk[++tp]=i;
cout<<tp<<endl;
}
return 0;
}
E
Content
给每个结点赋一个区间,父亲的区间必须包含所有儿子的区间,一个结点的子树不包含另外一个结点的两个点的区间交为空集,求根结点 \(1\) 的区间右端点最小值。
Sol
显然每个叶子赋单点区间,从 \(1\) 开始一直赋,然后父亲的区间由儿子的区间合并就好了,每个节点维护区间的左右端点就很好做,左端点一直取 \(\min\),右端点一直取 \(\max\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int _=2e5+5;
int n,cnt,L[_],R[_];
basic_string<int> E[_];
void dfs(int u,int fa){
L[u]=n+1,R[u]=0;
if(u>1&&E[u].size()==1) L[u]=R[u]=++cnt;
for(int v:E[u]) if(v^fa) dfs(v,u),L[u]=min(L[u],L[v]),R[u]=max(R[u],R[v]);
}
int main(){
ios::sync_with_stdio(0); cin.tie();
cin>>n;
for(int i=1;i<n;++i){
int x,y; cin>>x>>y;
E[x]+=y,E[y]+=x;
}
dfs(1,0);
for(int i=1;i<=n;++i){
cout<<L[i]<<' '<<R[i]<<'\n';
}
return 0;
}
F
Content
求序列前缀和的前缀和的最大值,这个序列会很长,但是相同元素的极长段个数是 \(2e5\) 的。
Sol
把 \(A\) 的和式写出来,发现是二次函数: \(A(cnt_{i-1}+n) - A(cnt_{i-1}) = \dfrac{n(n+1)}{2}x_i+n\cdot B(i-1)\)(\(A,B\) 如题,\(cnt_i\) 为第 \(i\) 段结束后的当前序列长度),把相同的元素合成一段,贪心一下知道只用每段考虑,所以枚举相邻的两段元素,直接二次函数求最值(左右端点,对称轴)就好了
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int _ = 2e5 + 5;
int n, m, B[_], C[2][_];
int f(int n, int i) {
return n * (n + 1) / 2 * C[0][i] + B[i-1] * n;
}
void solve() {
cin >> n >> m;
int sum = 0, ans = -1e18;
for (int i = 1; i <= n; ++i) {
cin >> C[0][i] >> C[1][i];
B[i] = B[i-1] + C[0][i] * C[1][i];
}
for (int i = 1; i <= n; ++i) {
int y = max(f(1, i), f(C[1][i], i));
if (C[0][i] < 0) {
long double p = -(C[0][i]+2.0*B[i-1])/2.0/C[0][i];
if (1 < p && p < C[1][i]) {
y = max(y, f((int)floor(p), i));
y = max(y, f((int)ceil(p), i));
}
}
ans = max(ans, y + sum);
sum += f(C[1][i], i);
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (t--) solve();
return 0;
}
G
Content
从 \((0,0,0)\) 走到 \((x,y,z)\) 花恰好 \(n\) 步,每一步只能选一个坐标 \(\pm 1\),空间是无限大的。
Sol
先考虑一维怎么做(从 \(0\) 走到 \(x\) 花恰好 \(n\) 步)
考虑先花 \(x\) 步走到终点,再在其中插入一些来回走抵消的,所以显然:
有解条件 \(n\ge x, 2|(n-x)\)
考虑最终我们会有 \(x+\dfrac{n-x}{2}\) 个 \(+1\) 和 \(\dfrac{n-x}{2}\) 个 \(-1\) 随便放,很明显只用考虑放进 \(+1\) 或 \(-1\) 中的一种就行了,方案等于 \(\dbinom{n}{\frac{n-x}{2}} = \dbinom{n}{\frac{n+x}{2}}\)
再考虑二维怎么做:
我们发现如果我们让 \((x,y) \to (x+y,x-y)\),那么这是一个满射,也就是原坐标系与新坐标系一一对应,而原来的操作是 \((\pm1,0),(0,\pm 1)\),新坐标系中是 \((1,1),(1,-1),(-1,1),(-1,-1)\)。这样对于单独的 \(x,y\) 都可以任意选了,因为另一个维度总有一个操作能够对应,所以 \(x,y\) 独立。
那么再套用一维的做法,另 \(f1(n,x)\) 表示一位中的问题,那么现在的方案就等于 \(f2(n,x,y)=f1(n,x+y)\cdot f1(n,x-y)\)。
再再考虑怎么拓展到三维:
由于二维及以前的我们都可以预处理组合数 \(O(1)\) 求出,所以只用枚举 \(z\) 轴用了 \(i\) 步,单独用一维的方法算方案,然后再将其插入到剩下的两维的方案中即可,因为 \(z\) 的操作独立于 \(x,y\),所以直接选 \(n\) 步中哪几步是 \(z\) 就好了。
\(Ans=\sum\limits_{i=0}^n f1(i,z)\cdot \binom{n}{i}\cdot f2(n-i,x,y)\)
很好的转化,如果实在想不到也可以枚举二维问题要走几个来回,然后推式子发现可以范德蒙德卷积卷掉,更朴实一点,但是核心是三维转二维,二维 \(O(1)\) 求。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int _ = 1e7 + 5, mod = 998244353;
int n, x, y, z, ans;
int fac[_], inv[_];
int C(int x, int y) {
return x < y || x < 0 || y < 0 ? 0 : fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int f1(int n, int x) {
if ((n + x) & 1) return 0;
return C(n, (n + x) / 2);
}
int f2(int n, int x, int y) {
return f1(n, x + y) * f1(n, x - y) % mod;
}
signed main() {
cin >> n >> x >> y >> z;
x = abs(x), y = abs(y), z = abs(z);
if (n < x + y + z) return cout << 0, 0;
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
for (int i = 2; i <= n; ++i) inv[i] = inv[mod % i] * (mod - mod / i) % mod;
for (int i = 2; i <= n; ++i) inv[i] = inv[i - 1] * inv[i] % mod;
for (int i = 0; i <= n; ++i) {
ans = (ans + f2(n - i, x, y) * f1(i, z) % mod * C(n, i) % mod) % mod;
}
cout << ans;
return 0;
}
偏简单的场,普及组不想推和式切到E也很轻松的。