1. 程式人生 > 其它 >[考試總結]ZROI-21-CSP7連-DAY3 總結

[考試總結]ZROI-21-CSP7連-DAY3 總結

ZROI-21-CSP7連-DAY2 總結

#T1 斯諾克

#題意簡述

\(7\) 種物品,第 \(i\) 種物品的價值為 \(i\),有 \(a_i\) 個,必須按照價值自小到大地選,特別的,必須將第 \(1\) 種選完才能向後選,選第 \(1\) 種時可以得到一個額外價值 \(x\),可以在當前 \(a_i>0(i\ne1)\) 的物品價值中任選。

#大體思路

顯然只要有就一定可以都選上,附加權值直接貪心的選最大的即可。

#Code

int rcnt, a[8], res, mx;

int main() {
    for (int i = 1; i <= 7; ++ i) {
        scanf("%d", &a[i]);
        res += a[i] * i;
        if (a[i]) mx = i;
    }
    res += a[1] * mx;
    printf("%d", res);
    return 0;
}

#T2 翻轉

#題意簡述

給定一個 \(n\times m(n,m\leq17)\)\(01\) 矩陣,每次操作可以將如下圖範圍的圖形做異或操作,圖形可以不全(在邊界),但中心點必須位於矩陣中。

問至少需要操作多少次才能將矩陣全部變為 \(0\)

#大體思路

不難發現

  • 對於一個位置最多被操作一次;
  • 對於兩個操作位置相同的操作聚合,結果相同;

於是可以想到自上而下的確定情況,注意到如果將第一行的狀態確定,每次只對已經確定的行的下一行進行操作,那麼下面怎麼變換都是確定的了,於是不難得到以下演算法:用二進位制列舉第一行的每個點的操作狀態,對原圖進行修改,並在改後的圖上進行模擬/統計即可,時間複雜度為 \(O(nm\cdot2^m)\)

.

#Code

const int N = 200010;
const int INF = 0x3fffffff;

template <typename T>
inline T Min(const T a, const T b) {return a < b ? a : b;}

int n, m, ans = INF, mp[20][20], tmp[20][20];

inline int bitcount(int x) {
    int cnt = 0;
    while (x) cnt += (x & 1), x >>= 1;
    return cnt;
}

int get_res(int x) {
    int res = 0;
    for (int i = 1; i <= n; ++ i)
      for (int j = 1; j <= m; ++ j)
        tmp[i][j] = mp[i][j];
    for (int i = 0; i < m; ++ i)
      if (x & (1 << i)) {
          tmp[1][i + 1] ^= 1, tmp[1][i] ^= 1;
          tmp[1][i + 2] ^= 1, tmp[2][i + 1] ^= 1;
      }
    for (int i = 1; i < n; ++ i)
      for (int j = 1; j <= m; ++ j)
        if (tmp[i][j]) {
            ++ res; tmp[i][j] ^= 1;
            tmp[i + 1][j] ^= 1;
            tmp[i + 1][j - 1] ^= 1;
            tmp[i + 1][j + 1] ^= 1;
            tmp[i + 2][j] ^= 1;
        }
    for (int i = 1; i<= m; ++ i)
      if (tmp[n][i]) return INF;
    return res + bitcount(x);
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
      for (int j = 1; j <= m; ++ j) {
          char c; cin >> c;
          if (c == 'X') mp[i][j] = 1;
          else mp[i][j] = 0;
      }
    for (int i = 0; i < 1 << m; ++ i)
      ans = Min(ans, get_res(i));
    cout << ans;
    return 0;
}

#T3 數對

#題意簡述

給定兩個數列 \(\{a_1,a_2,\dots,a_n\}\)\(\{b_1,b_2,\dots,b_m\}(n,m\leq10^5,1\leq a_i,b_i\leq2^30)\),問有多少數對 \((a_i,b_j)(1\leq i\leq n,1\leq j\leq m)\),滿足 \(\text{bitcount}(a_i\text{ xor }b_j)=2\).

#大體思路

\(\text{bitcount}(a_i\text{ xor }b_j)=2\) 意味著 \(a_i,b_j\) 在二進位制下只有兩位不同,維護一個 Hash,每次列舉不同的位置中的一位 \(k\),先查詢 Hash 中每個 \(b_j\text{ xor }2^k\) 出現的次數,再將每個 \(a_i\text{ xor }2^k\) 加入 Hash,不難發現這樣統計每對數字剛好被統計一次,即得答案。時間複雜度 \(O(30\cdot n)\)\(30\) 為列舉 \(k\) 的複雜度。

#Code

const int N = 20000010;
const int MOD = 19260817;
const int INF = 0x3fffffff;

struct Hash {int val, tot, nxt;} h[N];

int n, m, a[N], b[N], head[N], cnt;
ll ans;

inline void insert(int x, int c) {
    int hx = x % MOD, p = head[hx];
    while (p && h[p].val != x) p = h[p].nxt;
    if (h[p].val == x) h[p].tot += c;
    else {
        h[++ cnt].val = x, h[cnt].tot = c;
        h[cnt].nxt = head[hx], head[hx] = cnt;
    }
}

inline int Count(int x) {
    int hx = x % MOD, p = head[hx];
    while (p && h[p].val != x) p = h[p].nxt;
    if (h[p].val == x) return h[p].tot;
    else return 0;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i)
      scanf("%d", &a[i]);
    for (int i= 1; i <= m; ++ i)
      scanf("%d", &b[i]);
    for (int i = 1; i <= 30; ++ i) {
        int k = 1 << (i - 1);
        for (int j = 1; j <= m; ++ j)
          ans += Count(b[j] ^ k);
        for (int j = 1; j <= n; ++ j)
          insert(a[j] ^ k, 1);
    }
    printf("%lld", ans);
    return 0;
}

#T4 迴文串

#題意簡述

定義一個字串的價值是它的迴文子串的個數。現給定一字串 \(s(|s|\leq10^5)\),可以任一改變一個字元,問可得到的最大價值。

#大體思路

考慮改變一個位置會造成的影響:失去一些迴文子串、得到新的迴文子串。

在考慮改變造成的影響前,我們需要先得到在每個位置的迴文半徑,可以用 Manacher 求,當然也可以用 Hash+二分完成,這裡直接用的 Hash。然後,我們再來考慮造成的影響。

對於每個迴文中心 \(i,j(i\leq j)\) 和其對應的迴文半徑 \(r\),考慮得到新的迴文子串時對應的改變位置要麼是 \(x=i-r\) 要麼是 \(y=j+r\),且一定是改成對應的另一個的字元,否則對於這個迴文中心,不會造成價值增加。

對於奇迴文,\(i=j\),對於偶迴文,\(j=i+1\)

之後同樣可以用 Hash+二分求得不包括 \(x\)\(y\) 的新增半徑(即為新增的迴文子串的數量),用陣列 \(f_{i,c}\) 進行儲存,其中 \(f\) 的意義是 “將第 \(i\) 個位置變為 \(c\) 字元增加的迴文子串的數量”。

下面來考慮修改 \(x\) 失去的迴文串數量,顯然對於當前迴文中心 \(i,j\),若 \(i-r+1\leq x\leq i\),那麼減少的個數為 \(r-(i-x)=r-i+x\),若 \(j\leq x\leq j+r-1\),那麼減少的個數為 \(j+r-1-(x-1)=j+r-x\),發現這兩個貢獻對於修改位置 \(x\) 都可以拆分成兩部分:\(r-i\)\(x\)\(r+j\)\(-x\),其中僅關於 \(i,j,r\) 的部分與修改哪裡無關,於是可以維護一個數組 \(fl_x\),記錄這一部分的貢獻,這裡的區間加法可以用差分處理;再來看 \(x\)\(-x\),同樣可以維護差分陣列 \(fk_x\),對 \(fk\) 只做區間加 \(1\) 與加 \(-1\),最後統計貢獻可以統一乘上 \(x\)

綜上我們對於修改的影響都已經解決了,最後列舉修改位置及修改後的字元,將貢獻取最大值與初始價值相加即得答案。時間複雜度為 \(O(n\log n+26\cdot n)\).

#Code

#define ll long long
#define ull unsigned long long

const int N = 200010;
const ull BASE = 9973;
const ull MOD = 1e9 + 7;
const int INF = 0x3fffffff;

template <typename T>
inline T Min(const T a, const T b) {return a < b ? a : b;}

template <typename T>
inline T Max(const T a, const T b) {return a > b ? a : b;}

int n, p[N], h[N], hr[N]; char s[N], sr[N];
ll f[N][26], fk[N], fl[N], init_val;

inline void init_hash(char *S, int *H) {
    int len = strlen(S); H[0] = 1;
    for (int i = 0; i < len; ++ i)
      H[i + 1] = (1ull * H[i] * BASE % MOD + (ll)(S[i] - 'a')) % MOD;
}

inline ull getHash(int* H, int a, int b) {
    return ((1ull * H[b + 1] - 1ull * H[a] * p[b - a + 1] % MOD) + MOD) % MOD;
}

inline bool check(int a, int b, int c, int d) {
    return getHash(h, a, b) == getHash(hr, n - d - 1, n - c - 1);
}

int getRadius(int l, int r, int x, int y){
    int L = x, R = y, res = 0;
    while (L <= R) {
        int mid = (L + R + 1) >> 1;
        if (check(l - mid + 1, l, r, r + mid - 1))
          res = mid, L = mid + 1;
        else R = mid - 1;
    }
    return res;
}

signed main() {
    scanf("%s", s); n = strlen(s); p[0] = 1;
    for (int i = 0; i <= n; ++ i)
      p[i + 1] = (1ull * p[i] * BASE) % MOD;
    for (int i = 0; i < n; ++ i)
      sr[i] = s[n - i - 1];
    init_hash(s, h); init_hash(sr, hr);
    for (int i = 0; i < n; ++ i)
      for (int j = i; j <= i + 1; ++ j) {
          int r = 0; r = getRadius(i, j, 0, Min(i + 1, n - j));
          if (i == j) {
              fk[j + 1] += -1, fk[j + r] -= -1;
              fl[j + 1] += j + r, fl[j + r] -= j + r;
              fk[i - r + 1] += 1, fk[i] -= 1;
              fl[i - r + 1] += -(i - r), fl[i] -= -(i - r);
          } else {
              fk[j] += -1, fk[j + r] -= -1;
              fl[j] += j + r, fl[j + r] -= j + r;
              fk[i - r + 1] += 1, fk[i + 1] -= 1;
              fl[i - r + 1] += -(i - r), fl[i + 1] -= -(i - r);
          }
          int x = i - r, y = j + r; init_val += r;
          if (x < 0 || y >= n) continue;
          ++ r; r = getRadius(x - 1, y + 1, 0, Min(i + 1, n - j) - r);
          f[x][s[y] - 'a'] += r + 1;
          f[y][s[x] - 'a'] += r + 1;
      }
    ll best_change = 0, k = 0, l = 0;
    for (int i = 0; i < n; ++ i) {
        k += fk[i], l += fl[i];
        for (int j = 0; j < 26; ++ j)
          if (j + 'a' != s[i])
            best_change = Max(best_change, f[i][j] - (1ll * i * k + l));
    }
    printf("%lld\n", init_val + best_change);
    return 0;
}