NOI online 提高组题解

我菜死了qaq
T1 不会正解
T2 根本没看
T3 没想到枚举因数容斥,\(O(nm)\)的写法调半天没调出来

T1 序列

题意:Luogu
题解:
这是xht37的题解
把每个位置看成一个点。
首先对于 2 操作连边。
如果两个位置连通则意味着可以使一个位置 +1 另一个位置 −1。
即对于一个连通块,我们可以在保证总和不变的情况下任意的加减,因此并查集将一个连通块缩成一个点。
再对于 1 操作连边。
如果形成的图是二分图,则可以在保证左部点总和与右部点总和的差不变的情况下任意的加减。
如果形成的图不是二分图,则可以在保证总和奇偶性不变的情况下任意的加减。

#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
    x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 100005
int n, m, a[maxn], b[maxn], fa[maxn];
int cnt, x[maxn], y[maxn], col[maxn];
long long v[maxn], val[3];
struct Edge
{
    int fr, to;
}eg[maxn << 1];
int head[maxn], edgenum;
inline void add(int fr, int to)
{
    eg[++edgenum] = { head[fr],to };
    head[fr] = edgenum;
}
int getfa(int x)
{
    return fa[x] == x ? fa[x] : fa[x] = getfa(fa[x]);
}
bool dfs(int rt, int k)
{
    val[k] += v[rt], col[rt] = k;
    bool ok = true;
    for (int i = head[rt]; i; i = eg[i].fr)
    {
        if (col[eg[i].to] == col[rt]) ok = false;
        if (!col[eg[i].to] && !dfs(eg[i].to, 3 - k)) ok = false;
    }
    return ok;
}
bool work()
{
    read(n), read(m), cnt = 0, edgenum = 0;
    for (int i = 1; i <= n; ++i) read(a[i]), fa[i] = i, head[i] = col[i] = v[i] = 0;
    for (int i = 1; i <= n; ++i) read(b[i]), b[i] -= a[i];
    for (int i = 1, t, u, v; i <= m; ++i)
    {
        read(t), read(u), read(v);
        if (t == 2) fa[getfa(u)] = getfa(v);
        else x[++cnt] = u, y[cnt] = v;
    }
    for (int i = 1; i <= n; ++i) v[getfa(i)] += b[i];
    for (int i = 1, u, v; i <= cnt; ++i)
    {
        u = getfa(x[i]), v = getfa(y[i]);
        add(u, v), add(v, u);
    }
    for (int i = 1; i <= n; ++i)
        if (getfa(i) == i && !col[i])
        {
            val[1] = val[2] = 0;
            bool tp = dfs(i, 1);//是否为二分图
            if (tp && val[1] != val[2]) return false;
            if (!tp && (val[1] & 1) != (val[2] & 1)) return false;
        }
    return true;
}
int main()
{
    int T; read(T);
    while (T--) puts(work() ? "YES" : "NO");
    return 0;
}

T2 冒泡排序

题意:Luogu
题解:树状数组
蒯的这位的题解
有一个性质,每一轮冒泡排序之后,\(before[i]=\max(before[i]-1,0)\)
(before 就是位置在前面但是值比它大的数量)
具体可以手玩一下小数据
实现看代码吧
如果你跟我一样没看懂,跟着程序走一遍小数据大概就明白了

#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
    x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 200005
#define ll long long
int n, m, a[maxn], buc[maxn], before[maxn];
ll Ans;
namespace BitArray
{
    ll c[maxn];
    inline int lowbit(int& x) { return x & (-x); }
    inline void update(int pos, ll v) { for (int i = pos; i <= n; i += lowbit(i)) c[i] += v; }
    inline ll query(int pos) { ll ans = 0; for (int i = pos; i; i -= lowbit(i)) ans += c[i]; return ans; }
}
using namespace BitArray;

int main()
{
    read(n), read(m);
    for (int i = 0; i < n; ++i)
    {
        read(a[i]);
        before[i] = i - query(a[i]);
        Ans += before[i];
        ++buc[before[i]];//开桶记录一下
        update(a[i], 1);
    }
    memset(c, 0, sizeof(c));
    update(1, Ans); Ans = 0;
    for (int i = 0; i < n; ++i) Ans += buc[i], update(i + 2, -(n - Ans));
    //差分,先把总逆序对数量放到1
    //每次Ans记录的是前面有小于等于i个数字比它大的数字的数量
    //实现差分,在这一个位置会有n-tot个数字逆序对数-
    for (int i = 1, op, x; i <= m; ++i)
    {
        read(op), read(x);
        x = min(x, n - 1);
        if (op == 2) printf("%lld\n", query(x + 1));
        else
        {
            --x;
            if (a[x] < a[x + 1])
            {
                swap(a[x], a[x + 1]);
                swap(before[x], before[x + 1]);
                update(1, 1);//逆序对总量+1
                update(before[x + 1] + 2, -1);//由于before+1,所以原before[x+1]轮时也可以使逆序对数-1
                ++before[x + 1];//前面比它大的数量+1
            }
            else
            {
                swap(a[x], a[x + 1]);
                swap(before[x], before[x + 1]);
                update(1, -1);
                --before[x];//前面比它大的数量-1
                update(before[x] + 2, 1);//由于before-1,所以原before[x]轮时无法使逆序对数减少
            }
        }
    }
    return 0;
}

C 最小环

题意:Luogu
题解:
显然最优解必然是\(\gcd(n,k)\)个环
比如\(n=6,k=2\)
最优解就是\(6,5,4---3,2,1\)两个环
于是我们预处理出\(\frac{n}{2}\)内的每个\(n\)的因数对应的环长度对应的答案,对于每组询问直接回答
比如\(n=26\),就预处理\(1,2,13\)的答案
怎么处理看代码(我考场上自己写没跳出来qaq)

#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
    x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 200005
long long ans[maxn];
int n, m, k, a[maxn], tp[maxn];
int gcd(int x, int y)
{
    if (!y) return x;
    return gcd(y, x % y);
}
int main()
{
    read(n), read(m);
    for (int i = 1; i <= n; ++i) read(a[i]), ans[0] += 1ll * a[i] * a[i];
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n / 2; ++i)//枚举数量
    {
        if (n % i) continue;
        int blo = n / i, pos = 0;//blo长度
        for (int j = 2; j < blo; j += 2) tp[++pos] = j;//先把第一个环处理出来,第一个环必然是[1,blo]内的数
        tp[++pos] = blo;
        for (int j = blo - 1 - (blo & 1); j >= 1; j -= 2) tp[++pos] = j;
        for (int j = blo + 1; j <= n; ++j) tp[j] = tp[j - blo] + blo;//剩余的环
        for (int j = 1; j <= n; ++j)//统计答案
        {
            if (j % blo) ans[i] += 1ll * a[tp[j]] * a[tp[j + 1]];
            else ans[i] += 1ll * a[tp[j]] * a[tp[j - blo + 1]];
        }
    }
    while (m--)
    {
        read(k);
        if (k) k = gcd(n, k);
        printf("%lld\n", ans[k]);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/123789456ye/p/12483443.html
今日推荐