Codeforces 1540C Converging Array

UPD:修复了原来取值范围不严谨的问题。


aiai+ai+1bi2 做个比较,发现前者小于等于后者的条件为 ai+1aibi

ai+1ai+ai+1+bi2 做个比较,发现前者大于等于后者的条件为 ai+1aibi

发现这两个条件是一样的,也就是说,一次操作的本质是:

  • 如果 ai+1aibi,则不会发生变化。
  • 如果 ai+1ai<bi,则发生变化。

更近一步的,如果发生了一次变化,那相邻两个数的差会变成什么呢?

ai+ai+1+bi2ai+ai+1bi2=bi

也就是说,相邻的两个数的差,从原本的 <bi 变成了 =bi,且这两个数的和不变

f1n 表示最终收敛的数组,必然对任意 i 都存在 fi+1fibi

并且如果 fi+1fi>bi,则整个过程中,aiai+1 之间就从来没有发生过赋值操作。

这是因为,如果发生了操作,那么这次操作后 fi+1fi 就会变得等于 bi,而且我们知道两个数的差只能从小于 bi 变成等于 bi,而不能变成大于 bi,所以最终无法满足 fi+1fi>bi 的条件。

以满足这种严格大于关系的点 i 为断点,可以发现,a1iai+1n 是完全互相不影响的。

现在要解决最棘手的一个问题,如何求出 f1 呢?

假设我们知道了第一个断点的位置是 p,那可以得到一个方程组:

{f2f1=b1f3f2=b2fpfp1=bp1i=1pfi=i=1pai

f 看成未知数,则有 p 个未知数,p 个方程!于是我们可以手动解出 f1 的值:

(f1)+(f1+b1)+(f1+b1+b2)+=i=1paif1×p+i=1p1(pi)bi=i=1paif1=i=1paii=1p1(pi)bip

sap=i=1pai,sbp=i=1p1(pi)bi,则 f1=sapsbpp

但问题是,我们并不知道 p 是多少。正确的解决方法是,将每个位置都尝试作为第一个端点,取解出来的 f1 的最小值即可,也就是 f1=min{saisbii}

首先证明 f1min{saisbii},这是因为必然存在某个前缀 p 使得 f1=sapsbpp,所以 f1 大于等于取任意 p 时的最小值。

然后证明 f1min{saisbii},这是因为对于任意前缀 p,如果 f1sapsbpp,就说明必然存在 fi+1fi>bi (i<p) 的位置。但因为 f 是保持不变的,为了使得跨度变大只能减小初值,得出 f1<sapsbpp

回到原题,现在变成了一个计数问题。即,能找到多少个序列 a,满足 0aici,且 min{saisbii}x

min 转化为任意,命题等价于,对任意前缀 i,都要满足:

saisbiixsaiix+sbi

q=1 时,不等式右边都是已知量,考虑一个 DP,用 fi,j 表示前 i 项,和为 j 的方案数。因为合法的 a 必然是一个后缀,使用前缀和优化转移即可,单次转移 O(1)

时间复杂度 O(qn2m)ma 的上限。

现在考虑多组询问,不同的 x 是否存在等价关系呢?

  • 如果存在 i,满足 ix+sbi>im,即 x>min{imsbii},答案必然为 0
  • 如果任意 i,满足 ix+sbi0,即 xmin{sbii},答案必然为 (ci+1)

现在只需要特别考虑 x(min{sbii},min{imsbii}] 即可。

于是只有 O(m) 种本质不同的询问,全部预处理丢 map 里即可。时间复杂度 O(n2m2+qlogm)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105, MOD = 1e9 + 7;
int n, f[N * N], g[N * N], b[N], sb[N], c[N];
map<int, int> ans;
int Solve(int x)
{
fill(g, g + N * N, 1);
for(int i = 1, sumc = c[1], lim; i <= n; i++, sumc += c[i])
{
lim = i * x + sb[i]; memset(f, 0, sizeof(f));
for(int j = max(0, lim); j < N * N; j++) f[j] = (g[j] - (j - c[i] - 1 >= 0 ? g[j - c[i] - 1] : 0) + MOD) % MOD;
memset(g, 0, sizeof(g)); g[0] = f[0];
for(int j = 1; j < N * N; j++) g[j] = (g[j - 1] + f[j]) % MOD;
}
return g[N * N - 1];
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
int prod = 1;
for(int i = 1; i <= n; i++) { cin >> c[i]; prod = (ll)prod * (c[i] + 1) % MOD; }
for(int i = 1; i < n; i++) cin >> b[i];
for(int i = 1; i <= n; i++) for(int j = 1; j < i; j++) sb[i] += (i - j) * b[j];
int lb = 0, rb = INT_MAX, m = *max_element(c + 1, c + n + 1);
for(int i = 1; i <= n; i++) lb = min(lb, -((sb[i] - 1) / i + 1)), rb = min(rb, (i * m - sb[i] - 1) / i + 1);
for(int i = lb; i <= rb; i++) ans[i] = Solve(i);
int q; cin >> q;
while(q--)
{
int x; cin >> x;
if(x >= lb && x <= rb) cout << ans[x] << endl;
else if(x > rb) cout << 0 << endl;
else if(x < lb) cout << prod << endl;
}
return 0;
}
posted @   syksykCCC  阅读(241)  评论(4编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
点击右上角即可分享
微信分享提示