zoukankan      html  css  js  c++  java
  • 「NOIP 2020」微信步数(计数)

    「NOIP 2020」微信步数(Luogu P7116)

    题意:

    有一个 (k) 维场地,第 (i) 维宽为 (w_i),即第 (i) 维的合法坐标为 (1, 2, cdots, w_i)

    小 C 有一个长为 (n) 的行动序列,第 (i) 个元素为二元组 ((c_i, d_i)),表示这次行动小 C 的坐标由 ((x_1, x_2, cdots, x_{c_i}, cdots, x_k)) 变为 ((x_1, x_2, cdots, x_{c_i} + d_i, cdots, x_k))

    小 C 会事先将行动序列重复无限次。

    接下来,小 C 会以场地中的每个整点为起点,按照行动序列走直到走出场地。小 C 想知道他一共会走几步。你只要求出答案对 (10^9 + 7) 取模的结果。

    (1 le n le 5 imes 10^5, 1 le k le 10, 1 le w_i le 10^9, d_i in {1, -1})

    思路:

    首先可以发现,走出场地的时刻就是路径的(高维)Rounding Box 超出场地范围的时刻。换句话说,我们只关心路径中每一维坐标的最小、最大值。

    把起点分两类讨论:走不超过 (n) 步就走出去的,和不属于这一类的。

    对于第一类,我们枚举走 (i) 步恰好走出去。方案数就是前 (i - 1) 步的 Rounding Box 在范围内的方案数,减去前 (i) 步的 Rounding Box 还在范围内的方案数。对于两者,合法的起点都在一个(高维)矩形里,可以用乘法计算方案数。

    对于第二类,把总步数拆成最后一轮走的步数(在 ([1, n]) 中)和之前走的步数(是 (n) 的整数倍)。

    先看前者。对于一个第二类起点,我们枚举一个 “假起点”,满足 “真起点” 先走整数轮到这点,然后走 (n + i) 步恰好走出场地((1 le i le n))。

    注意,一个 “假起点” 的贡献是 (i) 乘上可能的 “真起点” 个数。

    我们记走一整轮后,第 (i) 维坐标的增量为 (Delta x_i)。方便起见,(Delta x_i ge 0) 且不全为 (0)

    设 “真起点” 为 ((a_1, a_2, cdots, a_k)),“假起点” 为 ((b_1, b_2, cdots, b_k))。那么两点一定满足关系 (a_i = b_i - c Delta x_i)(c ge 0))。

    现在,先固定 “假起点”。可以发现 “真起点” 还需满足形如 (a_i ge l_i) 的限制,所以真起点个数就是 (lfloor min{frac{b_i - l_i}{Delta x_i}} floor + 1)

    然后考虑 “假起点”。它的范围也是两个矩形的差,所以可以差分算贡献。更特殊地,发现它的最小值恰为 (l_i),即它的范围形如 (l_i le b_i < r_i)。设 (m_i = r_i - l_i),方案数可以写作:

    [egin{aligned} & sum_{b_1 = l_1}^{r_1 - 1} sum_{b_2 = l_2}^{r_2 - 1} cdots sum_{b_k = l_k}^{r_k - 1} lfloor min{frac{b_i - l_i}{Delta x_i}} floor + 1 \ = & sum_{j_1 = 0}^{m_1 - 1} sum_{j_2 = 0}^{m_2 - 1} cdots sum_{j_k = 0}^{m_k - 1} lfloor min{frac{j_i}{Delta x_i}} floor + 1 \ = & sum_{t ge 0} sum_{j_1 = 0}^{m_1 - 1} sum_{j_2 = 0}^{m_2 - 1} cdots sum_{j_k = 0}^{m_k - 1} [lfloor min{frac{j_i}{Delta x_i}} floor ge t] \ = & sum_{t ge 0} prod_{i = 1}^{k} max(0, m_i - t Delta x_i) \ end{aligned} ]

    下面只需考虑这个东西如何算。首先可以发现 (t) 大于等于一个值 ( ext{lim}) 时乘积的结果就为 (0)。通过这个事实可以把 (max) 去掉。再把式子看做关于 (t) 的多项式,即可推得:

    [egin{aligned} & sum_{t ge 0} prod_{i = 1}^{k} max(0, m_i - t Delta x_i) \ = & sum_{t = 0}^{ ext{lim} - 1} prod_{i = 1}^{k} m_i - t Delta x_i \ = & sum_{t = 0}^{ ext{lim} - 1} sum_{i = 0}^{k} f_i t^i \ = & sum_{i = 0}^{k} f_i sum_{t = 0}^{ ext{lim} - 1} t^i \ end{aligned} ]

    所以问题转化成一个经典问题:求 (k) 次方前缀和。至此,我们解决了前者的贡献。

    现在考虑后者就简单了。我们枚举从起点开始走 (t) 整轮还没走出边界,然后给答案加上 (n) 乘上合法起点数。这里发现合法的起点还是矩形,且 (t) 每增加 (1) 矩形第 (i) 维的宽度就减少 (Delta x_i)。也就是说后者的式子和前者长得一样,也可以用上述方法计算。

    最终我们解决了问题,时间复杂度 (O(n k^2))

    代码:

    #include <bits/stdc++.h>
    #define rep(i, a, b) for (int i = (a); i <= int(b); i++)
    #define per(i, a, b) for (int i = (a); i >= int(b); i--)
    using namespace std;
    
    typedef long long ll;
    const int maxn = 5e5, mod = 1e9 + 7;
    
    int n, k, w[10], c[maxn + 5], d[maxn + 5], dt[10], res, S[11][11], inv[12];
    
    struct foo {
    	int z[10], l[10], r[10];
    	void reset() {
    		memset(z, 0, k << 2);
    		memset(l, 0, k << 2);
    		memset(r, 0, k << 2);
    	}
    	foo() {
    		reset();
    	}
    	int walk(int c, int d) {
    		z[c] += d;
    		if (z[c] < l[c] || z[c] > r[c]) {
    			l[c] = min(l[c], z[c]);
    			r[c] = max(r[c], z[c]);
    			return d;
    		}
    		return 0;
    	}
    } F, B;
    
    inline void red(int &x) {
    	x += x >> 31 & mod;
    }
    
    void prework(int n) {
    	S[0][0] = 1;
    	rep(i, 1, n) rep(j, 1, i) {
    		S[i][j] = (S[i - 1][j - 1] + ll(S[i - 1][j]) * j) % mod;
    	}
    	inv[1] = 1;
    	rep(i, 2, n + 1) {
    		inv[i] = ll(mod - mod / i) * inv[mod % i] % mod;
    	}
    }
    
    int calc(int k, int n) {
    	int s = 0, c = 1;
    	rep(i, 0, k) {
    		c = ll(c) * max(0, n - i) % mod;
    		s = (s + ll(c) * inv[i + 1] % mod * S[k][i]) % mod;
    	}
    	return s;
    }
    
    int work(int a[]) {
    	int lim = mod;
    	rep(i, 0, k - 1) if (dt[i]) {
    		lim = min(lim, (a[i] + dt[i] - 1) / dt[i]);
    	}
    	int dp[11] = { 1 };
    	rep(i, 0, k - 1) {
    		per(j, i, 0) {
    			dp[j + 1] = (dp[j + 1] + ll(mod - dt[i]) * dp[j]) % mod;
    			dp[j] = ll(a[i]) * dp[j] % mod;
    		}
    	}
    	int res = 0;
    	rep(i, 0, k) {
    		res = (res + ll(dp[i]) * calc(i, lim)) % mod;
    	}
    	return res;
    }
    
    int main() {
    	// freopen("walk.in", "r", stdin);
    	// freopen("walk.out", "w", stdout);
    	scanf("%d %d", &n, &k);
    	prework(k);
    	rep(i, 0, k - 1) {
    		scanf("%d", &w[i]);
    	}
    	rep(i, 1, n) {
    		scanf("%d %d", &c[i], &d[i]), c[i]--;
    		if (F.walk(c[i], d[i]) && F.r[c[i]] - F.l[c[i]] <= w[c[i]]) {
    			int x = 1;
    			rep(j, 0, k - 1) if (j != c[i]) {
    				x = ll(x) * max(0, w[j] - F.r[j] + F.l[j]) % mod;
    			}
    			res = (res + ll(i) * x) % mod;
    		}
    	}
    	rep(i, 1, n) if (F.z[c[i]] < 0) {
    		d[i] = -d[i];
    	}
    	rep(i, 0, k - 1) if (F.z[i] < 0) {
    		F.z[i] = -F.z[i];
    		swap(F.l[i], F.r[i]);
    		F.l[i] = -F.l[i];
    		F.r[i] = -F.r[i];
    	}
    	B = F;
    	bool chk = true;
    	rep(i, 0, k - 1) {
    		dt[i] = B.z[i];
    		chk &= dt[i] == 0;
    	}
    	if (chk) {
    		bool ok = false;
    		rep(i, 0, k - 1) {
    			ok |= B.r[i] - B.l[i] >= w[i];
    		}
    		printf("%d
    ", ok ? res : -1);
    		exit(0);
    	}
    	int a[10] = {};
    	rep(i, 1, n) {
    		if (F.walk(c[i], d[i]) && F.r[c[i]] - F.l[c[i]] <= w[c[i]]) {
    			bool ok = true;
    			rep(j, 0, k - 1) if (j != c[i]) {
    				ok &= w[j] - F.r[j] + F.l[j] > 0;
    			}
    			if (!ok) {
    				continue;
    			}
    			rep(j, 0, k - 1) if (j != c[i]) {
    				a[j] = w[j] - F.r[j] + F.l[j];
    			}
    			a[c[i]] = w[c[i]] - F.r[c[i]] + F.l[c[i]] + 1, res = (res + ll(i) * work(a)) % mod;
    			a[c[i]] = w[c[i]] - F.r[c[i]] + F.l[c[i]], res = (res + ll(mod - i) * work(a)) % mod;
    		}
    	}
    	rep(i, 0, k - 1) {
    		a[i] = max(0, w[i] - B.r[i] + B.l[i]);
    	}
    	res = (res + ll(n) * work(a)) % mod;
    	printf("%d
    ", res);
    	return 0;
    }
    
  • 相关阅读:
    论 设计模式及项目架构的作用
    Linux根据进程号查找其程序文件路径 及 lsof 命令使用
    Synchronized 原理
    Seata 中类SPI使用机制分析
    redisson spring boot starter 做分布式锁
    TTFB 时间过长
    ShardingSphere Hint模式 SpringBoot + Mybatis
    Core源码(十三)Stack和ConcurrentStack
    C#源码(十二) HashSet
    Core源码(十一)Queue
  • 原文地址:https://www.cnblogs.com/Alfalfa-w/p/14090825.html
Copyright © 2011-2022 走看看