The 2022 ICPC Asia Nanjing Regional Contest

Luckyblock發表於2024-09-26

目錄
  • 寫在前面
  • I 簽到
  • G 貪心,模擬
  • D 二分答案,列舉
  • A 列舉,結論,二維字首和
  • B DP,列舉
  • M 計算幾何,列舉,大力討論
  • 寫在最後

寫在前面

補題地址:https://codeforces.com/gym/104128

以下按個人向難度排序。

SUA 什麼牛逼提媽的又被斬殺了,wenqizhi 大爹一個人爆切三道我和 dztlb 大神兩個人分別在 AM 上調不出來坐牢真是純純的戰犯

I 簽到

顯然當且僅當所有字元均相等才為完美迴文。

字符集只有 26,直接做就行了。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int T;
char s[N];
int a[300];
signed main(){
	cin>>T;
	while(T--){
		scanf("%s",s+1);
		int n=strlen(s+1);
		for(int i=0;i<=100;++i) a[i]=0;
		for(int i=1;i<=n;++i){
			a[s[i]-'a']++;
		}
		int ans=ans=n;
		for(int i=0;i<26;++i){
			ans=min(ans,n-a[i]);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

G 貪心,模擬

顯然應當在保證數量大於 0 情況下,儘可能進行操作 -1。

發現這個操作對答案的分數有加有減的很麻煩啊,一個套路是考慮統一操作的形式。考慮先將所有操作均看做一次操作 1(即令分母分子同時加 1),發現操作 -1 可看成再額外對分子減 1,對分母減 2。考慮最終進行了 \(c\) 次操作 -1,則進行 \(n\) 次操作後,最終的答案可以表示為:

\[\frac{n + 1 - c}{n + 1 - 2c} \]

保證上式分母時刻大於 0 即對應一種合法的操作方案。於是一個顯然的想法是考慮直接貪心,儘量進行操作 -1,若發現此時進行操作 -1 後會導致上式不合法,則嘗試將之前一次操作 -1 反悔即可。

賽時 wenqizhi 的思路是直接模擬上述轉化,先將所有操作 0 均看做操作 1,然後考慮將當前的分母的值看做一張折線圖,然後再倒序列舉折線圖上的點,若當前點對應操作為 0,且在此之後的折線圖上最小值至少為 2,則可以將當前的操作貪心地更改為操作 -1。

時間複雜度均為 \(O(n)\) 級別。

code by wenqizhi:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 1e6 + 5;
int n, a[N], pre[N], sufmn[N];

void solve()
{
    n = read();
    int b = 0;
    for(int i = 1; i <= n; ++i)
    {
        a[i] = read();
        pre[i] = pre[i - 1] + ((a[i] == 0) ? 1 : a[i]), b += (a[i] == -1);
    }
    sufmn[n] = pre[n];
    if(pre[n] < 0){ printf("-1\n"); return ; }
    for(int i = n - 1; i >= 1; --i)
    {
        sufmn[i] = min(pre[i], sufmn[i + 1]);
        if(pre[i] < 0)
        {
            printf("-1\n");
            return ;
        }
    }
    int cnt = 0;
    sufmn[n + 1] = 0x7fffffff;
    for(int i = n; i >= 1; --i)
    {
        sufmn[i] = min(sufmn[i], sufmn[i + 1]);
        if(a[i] == 0)
        {
            if(sufmn[i] >= 2) ++cnt, sufmn[i] -= 2;
        }
    }
    
    int X = n - b - cnt + 1, Y = n - 2 * b - 2 * cnt + 1, mx = max(X, Y);
    for(int i = 2; i <= mx; ++i)
        while(X % i == 0 && Y % i == 0) X /= i, Y /= i;
    printf("%d %d\n", X, Y);
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

反悔貪心:

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n;
//=============================================================
void getans(int ans_) {
  int x = n + 1 - ans_, y = n + 1 - 2 * ans_;
  int d = std::__gcd(x, y);
  std::cout << x / d << " " << y / d << "\n";
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    int cnt = 0, ans = 0, flag = 0; 
    for (int i = 1; i <= n; ++ i) {
      int a; std::cin >> a;
      if (a == 1) {
        continue;
      } else if (a == -1) {
        if (2 * (ans + 1) < i + 1) ++ ans;
        else if (cnt) -- cnt;
        else flag = 1;
      } else {
        if (2 * (ans + 1) < i + 1) ++ ans, ++ cnt;
      }
    }
    if (flag) std::cout << -1 << "\n";
    else getans(ans);
  }
  return 0;
}

D 二分答案,列舉

顯然答案有單調性,考慮二分答案。問題變為能否使序列中不小於 \(\operatorname{mid}\) 的數的數量不小於 \(k\)

然後考慮被列舉操作的區間,僅需考慮區間中的 0 中有多少可以變成 1 即可。對於操作區間為 \([1, m]\) 時可以大力處理,然後發現當操作區間右移至 \([i, i + m - 1]\) 時,新加入的數 \(a_{i+m-1}\) 會變為 \(a_{i+m-1} + kd\),原區間內的數均會減 \(d\)

發現每一個數在操作區間內時,隨區間右移其值是單調遞減的,即每個數均會在操作區間左端點取 \(i\) 時變為不小於 \(k\),又會在操作區間左端點取 \(i'(i'>i)\) 時變為小於 \(k\),於是考慮將每個數的貢獻 \(\plusmn 1\) 分別掛在這兩個位置上,則每種操作區間的影響即可透過字首和求得。

賽時 wenqizhi 大神原理大致如上但是用線段樹實現的,一發過了但是看不懂呃呃呃呃:

code by wenqizhi:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;
int n, m, K;
ll c, d, a[N], A[N], b[N];

#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)

struct Segment
{
    int sum[N << 2];
    
    void update(int k, int l, int r, int pos, int val)
    {
        sum[k] += val;
        if(l == r) return ;
        int mid = (l + r) >> 1;
        if(pos <= mid) update(ls(k), l, mid, pos, val);
        else update(rs(k), mid + 1, r, pos, val);
    }

    int query(int k, int l, int r, int L, int R)
    {
        if(L <= l && r <= R) return sum[k];
        int mid = (l + r) >> 1;
        if(R <= mid) return query(ls(k), l, mid, L, R);
        if(L > mid) return query(rs(k), mid + 1, r, L, R);
        return query(ls(k), l, mid, L, R) + query(rs(k), mid + 1, r, L, R);
    }
}T1, T2;

ll lazy;
int nn, mm;

bool check(ll mid)
{
    int pos1 = upper_bound(A + 1, A + mm + 1, mid) - A - 1;
    int pos2 = upper_bound(b + 1, b + nn + 1, mid - lazy) - b - 1;
    int sum1 = T1.query(1, 1, mm, 1, pos1), sum2 = T2.query(1, 1, nn, 1, pos2);
    if(sum1 + sum2 >= n - K + 1) return true;
    else return false;
}

int main()
{
    n = read(), K = read(), m = read(), c = read(), d = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), b[i] = a[i] + c + d * (i - 1), A[i] = a[i];
    b[n + 1] = -0x7fffffffffffffff, b[n + 2] = 0x7fffffffffffffff;
    sort(b + 1, b + n + 3);
    nn = unique(b + 1, b + n + 3) - (b + 1);
    A[n + 1] = -0x7fffffffffffffff, A[n + 2] = 0x7fffffffffffffff;
    sort(A + 1, A + n + 3);
    mm = unique(A + 1, A + n + 3) - (A + 1);
    for(int i = 1; i <= m; ++i)
    {
        int id = lower_bound(b + 1, b + nn + 1, a[i] + c + d * (i - 1)) - b;
        T2.update(1, 1, nn, id, 1);
    }
    for(int i = m + 1; i <= n; ++i)
    {
        int id = lower_bound(A + 1, A + mm + 1, a[i]) - A;
        T1.update(1, 1, mm, id, 1);
    }

    ll ans = -0x7fffffffffffffff;

    for(int i = m; i <= n; ++i)
    {
        ll l = -0x7ffffffffffff, r = 0x7ffffffffffff;
        while(l < r)
        {
            ll mid = (l + r) >> 1;
            if(check(mid)) r = mid;
            else l = mid + 1;
        }
        // printf("i = %d, l = %lld\n", i, l);
        ans = max(ans, l);
        if(i < n)
        {
            lazy -= d;
            int lastid = i - m + 1;
            int id = lower_bound(b + 1, b + nn + 1, a[lastid] + c + d * (lastid - 1)) - b;
            T2.update(1, 1, nn, id, -1);
            id = lower_bound(A + 1, A + mm + 1, a[lastid]) - A;
            T1.update(1, 1, mm, id, 1);
            lastid = i + 1;
            id = lower_bound(b + 1, b + nn + 1, a[lastid] + c + d * (lastid - 1)) - b;
            T2.update(1, 1, nn, id, 1);
            id = lower_bound(A + 1, A + mm + 1, a[lastid]) - A;
            T1.update(1, 1, mm, id, -1);
        }
    }
    printf("%lld\n", ans);
    return 0;
}

A 列舉,結論,二維字首和

和 wenqizhi 大神討論了下就直接會寫了然而直到結束吃了 11 發都沒過賽後一看是超級腦癱錯誤呃呃

B DP,列舉

我和 dztlb 大神雙人紅溫的時候,wenqizhi 大爹跑去開教練見面會了,到他回來我們也沒調出來然後他一個人隨便寫寫就又一發把這題過了唉唉真大神吧

M 計算幾何,列舉,大力討論

溝槽的題 corner case 這麼多賽時從 WA30 搞到 WA50 再搞到 WA83 然後再也過不去了

寫在最後

唉唉 wenqizhi 大爹太強了感覺我是純飛物。

學到了什麼:

  • A:使用非常人類智慧的表示式,嘗試簡化特判——並非好事!

相關文章