Edu38

基本情况

C又不是正解,A甚至还加一,以后要考虑好再交。

C. Constructing Tests

Problem - C - Codeforces

为了更好的代码实现而非手算出解来推式子

式子都推出来了。

$n^2 - {\left \lfloor \frac{n}{m} \right \rfloor}^2 = x $

myCode

不知道怎么直接解,就写了一个 \(\operatorname{O}(n\log n)\) 的枚举 \(m\) 然后对 \(n\) 二分的写法。

constexpr int N(1E9 + 1);

void solve() {
#define tests
    int x;
    std::cin >> x;
    if (x == 0) {
        std::cout << "1 1\n";
        return ;
    }

    for (int m = 1; m * m <= x * 10; m++) {
        i64 lo(m), hi(N);

        auto check = [&](auto& o) -> bool {
            return o * o - (o / m) * (o / m) < x;
        };

        i64 n(0);

        while (lo <= hi) {
            i64 mid(lo + hi >> 1);
            if (check(mid)) {
                lo = mid + 1;
            }
            else {
                hi = mid - 1;
                n = mid;
            }
        }

        if (n * n - (n / m) * (n / m) == x) {
            std::cout << n << ' ' << m << '\n';
            return ;
        }
    }

    error;

}

STD

我疑惑的点在于如果按照求根公式那一套来解,代码很难实现,而且还要考虑下取整。

实际上可以暴力一点,先因式分解一下(为了方便代码实现服务)

\[(n + {\left \lfloor \frac{n}{m} \right \rfloor})(n - {\left \lfloor \frac{n}{m} \right \rfloor}) = x \]

\(x_1 = (n + {\left \lfloor \frac{n}{m} \right \rfloor})\)

\(x_2 = (n - {\left \lfloor \frac{n}{m} \right \rfloor})\)

然后直接 \(\operatorname{O}(\sqrt x)\) 枚举 \(x1, x2\)

然后通过 \(x_1, x_2\),解出 \(n, m\)(暂时不用管向下取整等等)

最后再回代验证,就确保了正确性。

void solve() {
#define tests
    int x;
    std::cin >> x;
    if (x == 0) {
        std::cout << "1 1\n";
        return ;
    }
    for (int i = 1; i * i < x; i++) if (x % i == 0) {
        int x_1(x / i), x_2(i);
        int n(x_1 + x_2 / 2), m((x_1 + x_2) / (x_1 - x_2));
        if (n * n - (n / m) * (n / m) == x) {//回代验证
            std::cout << n << ' ' << m << '\n';
            return ;
        }
    }
    error ;
    return ;
}

D. Buy a Ticket

Problem - D - Codeforces

建图技巧:虚点

化点权为边权

暴力的想法是对每一个点跑单源最短路,肯定超时。

考虑优化,因为每条路径最后一定会加上 \(a_j\),也就是终点的点权,我们直接建立一个虚点,对所有城市连边,其边权就是每个城市的点权。

然后直接从虚点出发跑一次单源最短路,自然就得到了所有点的最短路径(从终点到起点的)。

struct Node {
	i64 v;
	i64 w;
};

struct State{
	i64 u, d;
	bool operator < (const State &s) const {
		return d > s.d;
	}
};

int n, m;
std::vector<std::vector<Node>> adj;
std::vector<i64> dis;

void dijkstra(int S){
	std::priority_queue<State> heap;
	dis.assign(n + 1, LONG_LONG_MAX);
	heap.push({S, 0}); 
    dis[S] = 0;
	while(not heap.empty()){
		auto[u, d](heap.top());
		heap.pop();
		if (d > dis[u]) continue;
		for (auto&[v, w] : adj[u]) {
			if (dis[u] + w < dis[v]) {
				dis[v] = dis[u] + w;
				heap.push({v, dis[v]});
			}
		}
	}
}

void solve() {
    std::cin >> n >> m;
    adj.assign(n + 1, std::vector<Node>());
    for (i64 i = 0, u, v, w; i < m; i++) {
        std::cin >> u >> v >> w;
        adj[u].push_back({v, w * 2});
        adj[v].push_back({u, w * 2});
    }
    for (int i = 1; i <= n; i++) {
        i64 w;
        std::cin >> w;
        adj[0].push_back({i, w});
    }
    dijkstra(0);
    for (int i = 1; i <= n; i++) {
        std::cout << dis[i] << ' ';
    }
    std::cout << '\n';
}

E. Max History

Problem - E - Codeforces

对于一个 \(n\) 的排列 \(a_1, a_2, \ldots, a_n\)。对于 \(i\) (从小到大枚举),如果 \(a_i > a_j\),则:\(\sum_{k=1}^{i-1} a_k\)。对 \(n\) 种可能的排列的 \(\sum_{k=1}^{n} a_k \bmod 10^9+7\)

推式子

首先,由于总共 \(n!\) 种排列的 \(f(a)\) 难以分每次逐个统计,于是想到对每一个以 \(a_i\) 为终值的排列对答案的贡献分开计算并且求和。

而注意到,当 \(a_m >a_i\) 时,\(m=i\),由此可以得知:

如果将历史上出现的每一个 \(a_m\) 的值组成一个序列,则这个序列是单调递增的。

\(a_m\)\(0\) 变为终值的过程中,只与那些比 \(a_m\) 小的数有关

考虑先进行排序(时限3s),通过一次扫描对每个 \(a_i\) 得到比其值小的数字个数 \(pre_i\)

下面就到式子了:

假设对于 \(a_i\) ,共有 \(x\)​ 个数比其小。

因为只有\(a_i\) 前的比起小的数会对结果有影响,故考虑枚举 \(a_i\) 前比其小的数字个数

假设当前 \(a_i\) 前面有 \(i\) 个比它小的数,共 \(\binom{x}{i}\) 种方案。

\(i\) 个数随便排列,共 \(i!\) 种方案。

剩下除这 \(i\) 个数与当前枚举的 \(a_i\) 以外的数共 \(n-i-1\) 个随意排列方案数:\((n-i-1)!\)

所以数 \(a_i\) 对答案的贡献次数为:

\[\sum_{i=0}^{x} \binom{x}{i} ·i!·(n-i-1)! \]

下面的过程是简化这个式子:

拆组合数:

\[=\sum_{i=0}^{x} \frac{x!}{i!·(x-i)!}·i!·(n-i-1)! \]

抵消:

\[=\sum_{i=0}^{x} \frac{x!}{(x-i)!}·(n-i-1)! \]

\(x!\) 提到前面:

\[=x!·\sum_{i=0}^{x} \frac{(n-i-1)!}{(x-i)!} \]

由组合数:\(\binom{n}{m}= \frac{n!}{m!(n-m)!}\) 添项:

\[=x!·\sum_{i=0}^{x} \frac{(n-i-1)!}{(x-i)!(n-x-1)!}·(n-x-1)! \]

\((n-x-1)!\) 提到前面:

\[=x!·(n-x-1)!·\sum_{i=0}^{x} \frac{(n-i-1)!}{(x-i)!(n-x-1)!} \]

合成组合数:

\[=x!·(n-x-1)!·\sum_{i=0}^{x} \binom{n-1-i}{x-i} \]

由于 \(i\)\(x-i\) 值域均为 \(0 \sim x\),且\((i)+(x-i)=x\)

所以 \(i\)\(x-i\) 可相互替换(相当于求和的循环倒着枚举):

\[=x!·(n-x-1)!·\sum_{i=0}^{x} \binom{n-1-x+i}{i} \]

\[=x!·(n-x-1)!·\sum_{i=0}^{x} \binom{n-x+i-1}{i} \]

又因为:\(\sum_{i=0}^{k}\binom{n+i-1}{i}=\binom{n+k}{k}\)这一步利用了上项求和,关键在于多加了一项\(\binom{n}{-1} = 0\)

\[=x!·(n-x-1)!·\binom{n}{x} \]

拆组合数:

\[=x!·(n-x-1)!·\frac{n!}{x!(n-x)!} \]

抵消:

\[=n!·\frac{(n-x-1)!}{(n-x)!} \]

\[=\frac{n!}{n-x} \]

因为最终值 \(a_m\) 不会被计算在答案当中,显然排序后所有值为 \(a_n\) 的数不应该被统计在答案当中。

则答案就为:

\[\sum_{i=1}^{n}\frac{n!}{n-pre_i}·a_i·[a_i \neq a_n] \]

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> pre(n), a(n);
    for (auto& x : a) std::cin >> x;
    std::sort(all(a));
    for (int i = 0; i + 1 < n; i++) {
        if (a[i + 1] == a[i]) {
            pre[i + 1] = pre[i];
        }
        else {
            pre[i + 1] = i + 1;
        }
    }
    Z ans(0);
    Z fac(comb.fac(n));
    for (int i = 0; i < n; i++) {
        if (a[i] == a[n - 1]) {
            break;
        }
        ans += fac / (Z(n) - Z(pre[i])) * Z(a[i]);
    }
    std::cout << ans << '\n';
}

另外一种推式子,更简洁

posted @ 2024-03-20 21:29  加固文明幻景  阅读(8)  评论(0编辑  收藏  举报