1. 程式人生 > 實用技巧 >2020.07.27 牛客多校第六場

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");
    }
}