2020.07.27 牛客多校第六场

C. Combination of Physics and Maths

题意:

一个矩阵的底面积 \(S\) 定义为最后一行的数的和,重量 \(F\) 定义为所有数的和,给一个正整数矩阵,找一个“压强“ \(p=\frac{F}{S}\) 最大的可非连续子矩阵,输出 \(p\)

思路:

当底面积确定时,重量越大,压强越大,所以子矩阵的顶部一定是原矩阵的顶部。不确定的是子矩阵的底在哪一行,并且子矩阵选择哪几列。那么枚举每一行作为底,依次选择贡献最大的列直到答案无法变大。如何计算贡献?用前缀和求出 \(sum[i][j] = \sum_{k=1}^isum[k][j]\) ,那么在枚举 到某一行 \(i\) 时,第 \(j\) 列的贡献为 \(sum[i][j]/a[i][j]\) ,按贡献大小 sort 一下,依次选择即可。

复杂度为 \(O(Tnmlogm)\)

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
int a[205][205];
int sum[205][205];
bool cmp(pii x, pii y) {
    return 1ll * x.fi * y.se > 1ll * x.se * y.fi;
}
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
                scanf("%d", &a[i][j]);
        for(int j = 1; j <= m; j++)
            for(int i = 1; i <= n; i++)
                sum[i][j] = sum[i - 1][j] + a[i][j];
        ll afz = 0, afm = 1;
        for(int i = 1; i <= n; i++) {
            vector<pii> tmp;
            for(int j = 1; j <= m; j++)
                tmp.pb(mp(sum[i][j], a[i][j]));
            sort(tmp.begin(), tmp.end(), cmp);
            ll fz = tmp[0].fi, fm = tmp[0].se;
            for(int j = 1;j<SZ(tmp);j++) {
                ll tmpz = fz + tmp[j].fi;
                ll tmpm = fm + tmp[j].se;
                if(fz * tmpm < fm * tmpz) {
                    fz = tmpz;
                    fm = tmpm;
                } else
                    break;
            }
            if(afz * fm < afm * fz) {
                afz = fz;
                afm = fm;
            }
        }
        printf("%.8f\n", 1.0 * afz / afm);
    }
}

J. Josephus Transform

题意:

给一个长度为 n 的排列和 m 次操作,每个操作可以表示为 (k, x),即进行 x 次以 k-约瑟夫变
换。问最后排列长啥样。

思路:

对于一次k-约瑟夫变换,我们需要知道它的变化序列,如【1,2,3,4,5】的3-约瑟夫变换序列是【3,1,5,2,4】。

如何求变化序列?

设上一个被取出来的数字是当时的第 pos 个(初始设为 1 ),当前还剩下 cnt 个数字,那么下一个被选出来的数应该是当前剩下的所有数字中的第 【\((pos+k-2)\% cnt + 1\) 】个。可以利用树状数组或线段树处理出这个变化序列,复杂度为 \(O(nlogn)\)

接着对于x次的k-约瑟夫变换,容易发现变换是满足结合律的,所以可以自己写一个快速幂,复杂度为 \(O(nlogx)\)

所以对于m次查询,总的复杂度为\(O(m(nlogn+nlogx))=O(nm(logn+logx))\)

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 1e5 + 5;
int n, m;
int lowbit(int x) {
    return x & (-x);
}
vi multi(vi x, vi y) {
    vi tmp(n + 1);
    for(int i = 1; i <= n; i++)
        tmp[i] = x[y[i]];
    return tmp;
}
vi qpow(vi a, int x) {
    vi ans(n + 1);
    for(int i = 1; i <= n; i++)
        ans[i] = i;
    while(x) {
        if(x & 1)
            ans = multi(ans, a);
        a  = multi(a, a);
        x >>= 1;
    }
    return ans;
}
int main() {
    scanf("%d%d", &n, &m);
    vi ans(n + 1);
    for(int i = 1; i <= n; i++)
        ans[i] = i;
    while(m--) {
        vi  a(n + 1), c(n + 1);
        for(int i = 1; i <= n; i++)
            for(int j = i; j <= n; j += lowbit(j))
                a[j]++;
        int k, x;
        scanf("%d%d", &k, &x);
        int pos = 1;
        for(int i = n; i >= 1; i--) {
            pos = (pos + k - 2) % i + 1;
            int L = 1, R = n;
            while(L < R) {
                int mid = (L + R) / 2;
                int sum = 0;
                for(int j = mid; j >= 1; j -= lowbit(j))
                    sum += a[j];
                if(sum < pos)
                    L = mid + 1;
                else
                    R = mid;
            }
            c[n - i + 1] = L;
            for(int j = L; j <= n; j += lowbit(j))
                a[j]--;
        }
        ans = multi(ans, qpow(c, x));
    }
    for(int i = 1; i <= n; i++)
        printf("%d ", ans[i]);
}

K. K-Bag

题意:

k-bag 序列定义为由多个 1~ k 的排列顺序连接起来的序列。

题目询问给定序列是不是 k-bag 的连续子序列。

思路:

定义一个长度为 k 的 k-bag 为最小 k-bag,即1~k的某种排列。则合法的给定序列应该是由

【最小k-bag的一部分(头部)+ 0至多个最小k-bag(中部) + 最小k-bag的一部分(尾部)】组成的。

首先预处理一些变量:

用 ex[i] 表示到 i 为止的 k 个数中(i<k 则为 i 个)是否有重复的数,有则 ex[i] = 0,否则 ex[i] = 1。

用 sum[i] 表示前缀和,用 maxx[i] 表示前缀最大值。

预处理的复杂度为 \(O(n)\)

用 dp[i] 表示 i 是否可以作为合法的结束位置。

若 i 是”头部“的合法结束位置,应满足 ex[i] = 1 && maxx[i] <= k,合法则 dp[i] = 1。

若 i 是”中部“的合法结束位置,应满足 ex[i] = 1 && dp[i-k] = 1 && sum[i] - sum[i-k] == k*(k+1)/2,合法则 dp[i] = 1。

若 i 是”尾部“的合法结束位置,与头部类似,反向处理一遍即可,合法则 dp2[i] = 1。

因此总的复杂度还是 \(O(n)\)

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 5e5 + 5;
int n, k;
int a[MAXN];
int ex[MAXN];
unordered_map<int, int>num;
int dp[MAXN];
int maxx[MAXN];
ll sum[MAXN];
void solve1() {
    int cnt = 0;
    for(int i = 1; i <= min(n, k); i++) {
        num[a[i]]++;
        if(num[a[i]] == 2)
            cnt++;
        if(cnt > 0)
            ex[i] = 0;
    }
    for(int i = min(n, k) + 1; i <= n; i++) {
        if(i - k >= 1) {
            num[a[i - k]]--;
            if(num[a[i - k]] == 1)
                cnt--;
        }
        num[a[i]]++;
        if(num[a[i]] == 2)
            cnt++;
        if(cnt > 0)
            ex[i] = 0;
    }
    for(int i = 1; i <= n; i++) {
        maxx[i] = max(maxx[i - 1], a[i]);
        sum[i] = sum[i - 1] + 1ll * a[i];
    }
}
int ex2[MAXN];
unordered_map<int, int>num2;
int maxx2[MAXN];
int dp2[MAXN];
void solve2() {
    int cnt = 0;
    for(int i = n; i >= 1 && n - i + 1 <= k; i--) {
        num2[a[i]]++;
        if(num2[a[i]] == 2)
            cnt++;
        if(cnt > 0)
            ex2[i] = 0;
    }
    for(int i = n; i >= 1; i--)
        maxx2[i] = max(maxx2[i + 1], a[i]);
}
void init(int n) {
    for(int i = 0; i < n + 5; i++) {
        ex[i] = ex2[i] = 1;
        dp[i] = maxx[i] = sum[i] = 0;
        dp2[i] = maxx2[i] = 0;
    }
    num.clear();
    num2.clear();
}
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &n, &k);
        init(n);
        for(int i = 1; i <= n; i++)
            scanf("%d", a + i);
        solve1();
        for(int i = 1; i <= min(n, k); i++)
            if(ex[i] == 1 && maxx[i] <= k)
                dp[i] = 1;
        dp[0] = 1;
        for(int i = min(n, k) + 1; i <= n; i++) {
            int f = ex[i];
            if(i - k >= 0) {
                if(dp[i - k] == 0)
                    f = 0;
                if((sum[i] - sum[i - k]) != 1ll * k * (k + 1) / 2)
                    f = 0;
            }
            if(f)
                dp[i] = 1;
        }
        solve2();
        dp2[n + 1] = 1;
        for(int i = n; i >= 1 && n - i + 1 <= k; i--)
            if(ex2[i] == 1 && maxx2[i] <= k)
                dp2[i] = 1;
        int ans = 0;
        for(int i = 1; i <= n; i++)
            if(dp[i] && dp2[i + 1])
                ans = 1;
        if(ans)
            printf("YES\n");
        else
            printf("NO\n");
    }
}

猜你喜欢

转载自www.cnblogs.com/Hartley/p/13389700.html
今日推荐