【题解】NOIP 13连测 #2

NOIP 13连测 #2

A

手动模拟可以发现:

\(a_{i-2},a_i,a_{i+2}\) 总是相邻的,且 \(a_1,a_2\) 是相邻的,\(a_n\) 在最左端

  • \(n\) 为奇数,答案为:\(a_na_{n-2}\cdots a_1 +a_2a_4\cdots a_{n-1}\)
  • \(n\) 为偶数,答案为:\(a_na_{n-2}\cdots a_2+a_1a_3\cdots a_{n-1}\)

时间复杂度 \(\mathcal{O}(n)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, a[N];
int main() {
    freopen("reverse.in", "r", stdin);
    freopen("reverse.ans", "w", stdout);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    if (n & 1){
        for (int i = n; i > 0; i -= 2)
            cout << a[i] << ' ';
        for (int i = 2; i <= n; i += 2)
            cout << a[i] << ' ';
    }
    else{
        for (int i = n; i > 0; i -= 2)
            cout << a[i] << ' ';
        for (int i = 1; i <= n; i += 2)
            cout << a[i] << ' ';
    }
    return 0;
}

B

如果一条边不在图中一棵最小生成树中,那么加入一条新边之后的新图中也可以不用考虑这条边了。(考虑kruskal将边排序的过程)

对原图使用 prim 求出最小生成树的 \(n-1\) 条边,与新加入的 \(q\) 条边排序后,做次 \(q\) 次 kruskal 即可。

时间复杂度 \(\mathcal{O}(n^2+nq\log n)\)。(\(\log n\)为并查集)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 10;
int n, q, px[N], py[N], cnt, id[N];
bool vis[N];
ll d[N];
struct Edge{    
    int u, v, t;
    ll w;
    bool operator<(const Edge &tmp) const { return w < tmp.w; }
} e[N];
ll cal(int x) { return abs(1ll * x * x * x); }
ll dis(int u, int v) { return cal(px[u] - px[v]) + cal(py[u] - py[v]); }
void add(int u, int v, ll w, int t) { e[++cnt] = (Edge){u, v, t, w}; }
void prim(){
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;
    for (int i = 1; i <= n; i++){
        int u = 0;
        for (int j = 1; j <= n; j++)
            if (!vis[j] && (!u || d[u] > d[j]))
                u = j;
        vis[u] = 1;
        if (id[u])
            add(u, id[u], d[u], 0);
        for (int j = 1; j <= n; j++)
            if (!vis[j] && d[j] > dis(u, j)){
                d[j] = dis(u, j);
                id[j] = u;
            }
    }
}
int fa[N];
ll ans;
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void solve(int t){
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    ans = 0;
    for (int i = 1; i <= cnt; i++){
        if (e[i].t > t)
            continue;
        int u = find(e[i].u), v = find(e[i].v);
        if (u != v){
            ans += e[i].w;
            fa[u] = v;
        }
    }
    cout << ans << '\n';
}
int main(){
    freopen("logistics.in", "r", stdin);
    freopen("logistics.ans", "w", stdout);
    cin >> n >> q;
    for (int i = 1; i <= n; i++)
        cin >> px[i] >> py[i];
    prim();
    for (int i = 1; i <= q; i++){
        ll w;
        int u, v;
        cin >> u >> v >> w;
        add(u, v, w, i);
    }
    sort(e + 1, e + cnt + 1);
    for (int i = 0; i <= q; i++)
        solve(i);
    return 0;
}

C

很水的题目,显然是排好序后答案最小。

当然也可以用 \(n-环数\)

#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 300010, P = 998244353;
int n, T;
int rk[N];
PII w[N], d[N];
signed main() {
    freopen("sequence.in", "r", stdin);
    freopen("sequence.ans", "w", stdout);
    cin >> n >> T;
    for (int i = 1; i <= n; i++) scanf("%d", &w[i].x), w[i].y = i;
    for (int i = 1; i <= n; i++) scanf("%d", &d[i].x), d[i].y = i;
    sort(w + 1, w + n + 1), sort(d + 1, d + n + 1);
    for (int i = 1; i <= n; i++) rk[w[i].y] = i;
    // 位置在 w[i].y 上的排名第 i
    LL res = 0, cnt = 0;
    for (int i = 1; i <= n; i++) {
        int t = d[i].y;  // 第 i 小的数要到位置 t
        if (w[i].y != t) {
            int r = rk[t];  // 位置 t 上的是哪个
            swap(rk[t], rk[w[i].y]);
            swap(w[i].y, w[r].y);
            cnt++;
        }
        (res += 1ll*(d[i].x - w[i].x)*(d[i].x - w[i].x)) %= P;
    }
    cout << res;
    if (T)
        cout << " " << cnt << endl;
    return 0;
}

D

枚举方阵的大小为 \(k\) ,每行给定一个权值 \(x_{i}\) ,每列给定一个权值 \(y_{i}\) ,那么 \(a_{i, j}=x_{i}+y_{j}\) 是一个合法的方阵。

一个合法的方阵也一定可以由一组 \(x_{i}, y_{i}\) 表示。 \(\left(x_{i}=a_{i, 1}, y_{j}=a_{1, j}-a_{1,1}\right)\)
但是 \(x_{i}^{\prime}=x_{i}+1, y_{i}^{\prime}=y_{i}-1\)\(x_{i}, y_{i}\) 对应的方阵相同。
\(\min \left(x_{i}\right)=0\) 则方阵与数列之间——对应。
此时 \(a_{i, j} \geq m\) 等价于 \(\min \left(y_{i}\right) \geq m\)
\(y_{i}\) 减去 \(m\) ,问题变为将至多 \(n-k * m\) 个石头放入 \(2 k\) 个格子里,且前 \(k\) 个格子至少有一个没有石子的方案数。

容斥求得答案为 \(\binom{n-k * m+2 * k}{2 * k}-\binom{n-k * m+k}{2 * k}\)
最后对所有可能的 \(k\) 求和。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 998244353;
int n, m, fac[N], inv[N];
int fpow_(int a, int b, int res = 1){
    for (; b; b >>= 1, a = 1ll * a * a % mod)
        if (b & 1)
            res = 1ll * res * a % mod;
    return res;
}
void init_(int n){
    fac[0] = 1;
    for (int i = 1; i <= n; i++)
        fac[i] = 1ll * fac[i - 1] * i % mod;
    inv[n] = fpow_(fac[n], mod - 2);
    for (int i = n; i; i--)
        inv[i - 1] = 1ll * inv[i] * i % mod;
}
int C_(int n, int k) { return n < k ? 0 : 1ll * fac[n] * inv[k] % mod * inv[n - k] % mod; }
void work_(){
    int ans = 0;
    for (int i = 1; 1ll * i * m <= n; i++){    
        int tot = n - i * m;
        ans = (ans + C_(tot + 2 * i, 2 * i)) % mod;
        ans = (ans - C_(tot + i, 2 * i)) % mod;
    }
    printf("%d\n", (ans % mod + mod) % mod);
}
int main(){
    freopen("magic.in","r",stdin);
    freopen("magic.ans","w",stdout);
    init_(2e5);
    int T;
    scanf("%d", &T);
    while (T--){
        scanf("%d%d", &n, &m);
        work_();
    }
    return 0;
}
posted @ 2025-02-06 16:18  Star_F  阅读(21)  评论(0)    收藏  举报