1. 程式人生 > >PKUSC 2018 題解

PKUSC 2018 題解

PKUSC 2018 題解

Day 1

T1 真實排名

Solution

考慮對於每一個人單獨算

每一個人有兩種情況,翻倍和不翻倍,他的名次不變等價於大於等於他的人數不變

設當前考慮的人的成績為 \(v\)

翻倍的話,要求成績在 \([v, 2v-1]\) 的人全部翻倍,剩下的隨便

統計一下這段區間的人數,組合數算一下即可

不翻倍的話,要求成績在 \([\frac {v+1} 2,v-1]\) 的人不翻倍,因為他們如果翻倍就超過了當前這個人

所以同樣統計一下,加上組合數即可

注意成績為零得特殊處理,因為他翻不翻倍是一樣的

Code
// Copyright lzt
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef std::pair<int, int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef std::pair<long long, long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i, j, k)  for (register int i = (int)(j); i <= (int)(k); i++)
#define rrep(i, j, k) for (register int i = (int)(j); i >= (int)(k); i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

inline ll read() {
  ll x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') f = -1;
    ch = getchar();
  }
  while (ch <= '9' && ch >= '0') {
    x = 10 * x + ch - '0';
    ch = getchar();
  }
  return x * f;
}

const int mod = 998244353;
const int maxn = 100100;
int n, k;
pii p[maxn];
int ans[maxn], fac[maxn], inv[maxn];

inline int ksm(int x, int p) {
  int ret = 1;
  while (p) {
    if (p & 1) ret = ret * 1ll * x % mod;
    x = x * 1ll * x % mod; p >>= 1;
  }
  return ret;
}
inline int C(int x, int y) {
  return fac[x] * 1ll * inv[y] % mod * inv[x - y] % mod;
}
inline int calc(int v) {
  int l = 1, r = n;
  while (l + 1 < r) {
    int md = (l + r) >> 1;
    if (p[md].fi > v) r = md - 1;
    else l = md;
  }
  while (p[l].fi <= v && l <= n) l++;
  return l - 1;
}
inline int calc(int L, int R) {
  int ret = calc(R) - calc(L - 1);
  return ret;
}

void work() {
  n = read(), k = read();
  fac[0] = 1; rep(i, 1, n) fac[i] = fac[i - 1] * 1ll * i % mod;
  inv[n] = ksm(fac[n], mod - 2); rrep(i, n - 1, 0) inv[i] = inv[i + 1] * 1ll * (i + 1) % mod;
  rep(i, 1, n) p[i].fi = read(), p[i].se = i;
  sort(p + 1, p + n + 1);
  rep(i, 1, n) {
    if (p[i].fi == 0) {
      ans[p[i].se] = C(n, k);
      continue;
    }
    int sum = 0;
    int num = calc(p[i].fi, p[i].fi * 2 - 1);
    if (num <= k) sum = sum + C(n - num, k - num);
    num = calc((p[i].fi + 1) / 2, p[i].fi - 1) + 1;
    if (n - num >= k) sum = sum + C(n - num, k);
    ans[p[i].se] = sum % mod;
  }
  rep(i, 1, n) printf("%d\n", ans[i]); puts("");
}

int main() {
  #ifdef LZT
    freopen("in", "r", stdin);
    freopen("out", "w", stdout);
  #endif

  work();

  #ifdef LZT
    Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
  #endif
}

T2 最大字首和

Solution

考慮每一個子集的貢獻

事實上,一個序列能夠成為最大字首和當且僅當滿足兩個條件:

  1. 這個序列的任意一個字尾和大於等於零(所以不能截取出某個小字首的和比它大)
  2. 剩下的序列的任意一個字首和小於零(所以不能加上某一段之後和比他大)

這兩部分都可以 \(\text{dp}\) 出來

我們令 \(f[mask]\) 表示選了 \(mask\) 對應的數,任意一個字尾和大於等於零的排列數,\(g[mask]\) 表示選了 \(mask\) 對應的數,任意一個字首和小於零的排列數

那麼一個 \(mask\) 的貢獻就是 \(f[mask] \cdot g[mx - mask] \cdot sum[mask]\)

\(\text{dp}\) 的過程詳見程式碼,很簡單的一個 \(\text{dp}\),注意兩個 \(\text{dp}\) 轉移的方向不同

Code
// Copyright lzt
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef std::pair<int, int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef std::pair<long long, long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i, j, k)  for (register int i = (int)(j); i <= (int)(k); i++)
#define rrep(i, j, k) for (register int i = (int)(j); i >= (int)(k); i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

inline ll read() {
  ll x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') f = -1;
    ch = getchar();
  }
  while (ch <= '9' && ch >= '0') {
    x = 10 * x + ch - '0';
    ch = getchar();
  }
  return x * f;
}

const int mod = 998244353;
const int maxn = 22;
const int mxst = 1100000;
int n;
int a[maxn];
ll s[mxst];
int f1[mxst], f2[mxst];
// f1[s] 表示 s 中的數排列使得任意一個字首和<0 的方案數
// f2[s] 表示 s 中的數排列使得任意一個字尾和>=0 的方案數

inline void add(int &x, int y) {
  x += y;
  if (x >= mod) x -= mod;
  if (x < 0) x += mod;
}

void work() {
  n = read(); int mx = (1 << n) - 1;
  rep(i, 0, n - 1) a[i] = read();
  rep(i, 0, mx) rep(j, 0, n - 1) if ((i >> j) & 1) s[i] += a[j];
  f1[0] = 1;
  rep(i, 0, mx) {
    if (s[i] >= 0) continue;
    rep(j, 0, n - 1) {
      if ((i >> j) & 1)
        add(f1[i], f1[i - (1 << j)]);
    }
  }
  f2[0] = 1;
  rep(i, 0, mx) {
    if (s[i] < 0) continue;
    rep(j, 0, n - 1) {
      if ((i >> j) & 1) continue;
      add(f2[i | (1 << j)], f2[i]);
    }
  }
  int ans = 0;
  rep(i, 0, mx) {
    int j = mx - i;
    add(ans, f2[i] * 1ll * f1[j] % mod * s[i] % mod);
  }
  printf("%d\n", ans);
}

int main() {
  #ifdef LZT
    freopen("in", "r", stdin);
  #endif

  work();

  #ifdef LZT
    Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
  #endif
}

T3 主鬥地

Solution

大力搜尋

首先發現狀態數不多,大概在 \(3^{12}\times 2\times 2=2e6\) 和 $4^{12}\times 2\times 2=6e7 $之間,估計就是 \(1e7\) 這個級別的

那麼我們需要快速判斷一個狀態是否可行

還是大力搜尋

首先學會一個技能叫做拆單牌

我們發現除了飛機,四帶二,三帶一和三帶二以外的牌型,都可以直接拆成數張單牌一一打出,不影響結果

比如對子可以變成兩張單牌,順子可以變成一溜單牌,並且每張單牌和對家拆出來的單牌大小關係都一樣

所以關鍵在於暴力搜三和四

然後列舉三帶了幾個二,因為這個二的大小不影響結果,所以我們儘量取jry的大的二和網友的小的二配對

剩下的看看能帶走幾個不影響結果的單牌,也是jry的大單牌和網友的小單牌

最後單牌看看能不能壓住即可

Code
// by sigongzi
#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int,int>
#define pdi pair<db,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define space putchar(' ')
#define eps 1e-8
#define mo 974711
#define MAXN 100005
//#define ivorysi
using namespace std;
typedef long long int64;
typedef double db;
template<class T>
void read(T &res) {
    res = 0;char c = getchar();T f = 1;
    while(c < '0' || c > '9') {
    if(c == '-') f = -1;
    c = getchar();
    }
    while(c >= '0' && c <= '9') {
    res = res * 10 + c - '0';
    c = getchar();
    }
    res *= f;
}
template<class T>
void out(T x) {
    if(x < 0) {x = -x;putchar('-');}
    if(x >= 10) {
    out(x / 10);
    }
    putchar('0' + x % 10);
}
char s[20];
int rem[16],A[16],W[16],K[16],ans,jry[16];
int C[16],D[16],H[16],J[16],P[16];
int code(char c) {
    if(c >= '4' && c <= '9') return c - '4' + 1;
    if(c == 'T') return 7;
    if(c == 'J') return 8;
    if(c == 'Q') return 9;
    if(c == 'K') return 10;
    if(c == 'A') return 11;
    if(c == '2') return 12;
    if(c == 'w') return 13;
    if(c == 'W') return 14;
}
bool check(int f,int t) {
    for(int i = 0 ; i <= t ; ++i) {
    memcpy(H,W,sizeof(H));
    memcpy(J,K,sizeof(K));
    if(2 * i + t - i + f * 2  + f * 4 + t * 3 > 17) break;
    int cnt = 0;
    for(int j = 1 ; j <= 14 ; ++j) {
        if(H[j] >= 2 && cnt < i) {H[j] -= 2;++cnt;}
        if(H[j] >= 2 && cnt < i) {H[j] -= 2;++cnt;}
        if(cnt == i) break;
    }
    if(cnt < i) break;
    cnt = 0;
    for(int j = 14 ; j >= 1 ; --j) {
        if(J[j] >= 2 && cnt < i) {J[j] -= 2;++cnt;}
        if(J[j] >= 2 && cnt < i) {J[j] -= 2;++cnt;}
        if(cnt == i) break;
    }
    if(cnt < i) break;
    memset(P,0,sizeof(P));
    cnt = 2 * f + t - i;
    for(int j = 14 ; j >= 1 ; --j) {
        int t = min(cnt,J[j]);
        J[j] -= t;cnt -= t;
        if(cnt == 0) break;
    }
    if(cnt) continue;
    cnt = 2 * f + t - i;
    for(int j = 1 ; j <= 14 ; ++j) {
        int t = min(cnt,H[j]);
        H[j] -= t;cnt -= t;
        if(cnt == 0) break;
    }
    if(J[14]) continue;
    for(int j = 1 ; j <= 14 ; ++j) {
        P[j] += H[j];
        P[j + 1] -= J[j];
    }
    cnt = 0;
    for(int j = 1 ; j <= 14 ; ++j) {
        cnt += P[j];
        if(cnt > 0) break;
    }
    if(cnt == 0) return true;
    }
    return false;
}
bool brute_jry(int dep,int four,int three,int f,int t,int q1,int q2) {
    if(four == f && t == three) return check(four,three);
    if(dep >= 12) return false;
    q1 += C[dep];q2 += D[dep];
    if(q1 > 0 || q2 > 0) return false;
    if(K[dep] >= 3) {
    K[dep] -= 3;
    if(brute_jry(dep + 1,four,three,f,t + 1,q1 - 1,q2)) return true;
    K[dep] += 3;
    }
    if(K[dep] >= 4) {
    K[dep] -= 4;
    if(brute_jry(dep + 1,four,three,f + 1,t,q1,q2 - 1)) return true;
    K[dep] += 4;
    }
    return brute_jry(dep + 1,four,three,f,t,q1,q2);
}
bool brute_wangyou(int dep,int four,int three) {
    if(four * 6 + three * 4 > 17) return false;
    if(dep > 12) {
    if(brute_jry(1,four,three,0,0,0,0)) return true;
    return false;
    }
    if(W[dep] >= 3) {
    W[dep] -= 3;++C[dep];
    if(brute_wangyou(dep + 1,four,three + 1)) return true;
    W[dep] += 3;--C[dep];
    }
    if(W[dep] >= 4) {
    W[dep] -= 4;++D[dep];
    if(brute_wangyou(dep + 1,four + 1,three)) return true;
    W[dep] += 4;--D[dep];
    }
    if(brute_wangyou(dep + 1,four,three)) return true;
    return false;
}
void dfs(int dep,int r) {
    if(!r) {
    memset(C,0,sizeof(C));
    memset(D,0,sizeof(D));
    memcpy(W,A,sizeof(A));
    memcpy(K,jry,sizeof(jry));
    if(brute_wangyou(2,0,0)) {++ans;}
    return;
    }
    if(dep > 14) return;
    for(int i = 0 ; i <= rem[dep] ; ++i) {
    if(i > r) break;
    jry[dep] = i;
    dfs(dep + 1,r - i);
    jry[dep] = 0;
    }
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    while(scanf("%s",s + 1) != EOF) {
    memset(A,0,sizeof(A));
    for(int i = 1 ; i <= 12 ; ++i) rem[i] = 4;
    rem[13] = rem[14] = 1;
    ans = 0;
    for(int i = 1 ; i <= 17 ; ++i) {
        A[code(s[i])]++;rem[code(s[i])]--;
    }
    dfs(1,17);
    out(ans);enter;
    }
}
// Copyright lzt
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef std::pair<int, int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef std::pair<long long, long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i, j, k)  for (register int i = (int)(j); i <= (int)(k); i++)
#define rrep(i, j, k) for (register int i = (int)(j); i >= (int)(k); i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

inline ll read() {
  ll x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') f = -1;
    ch = getchar();
  }
  while (ch <= '9' && ch >= '0') {
    x = 10 * x + ch - '0';
    ch = getchar();
  }
  return x * f;
}

const int maxn = 30;
int a[maxn], b[maxn], c[maxn], d[maxn], e[maxn];
int dy[400], ans;
char s[maxn];

int B(int o,int p) {
  for(int i=0;i<=o;i++) {
    int x=o+p-i,y=i;
    for(int j=0;j<14;j++) d[j]=c[j];
    for(int j=13;~j;j--) {
      while(y&&d[j]>=2) d[j]-=2,--y;
      while(x&&d[j]) --d[j],--x;
    }
    if(x|y) continue;
    for(int j=0;j<14;j++) e[j]=a[j];
    x=o+p-i,y=i;
    for(int j=0;j<14;j++) {
      while(y&&e[j]>=2) e[j]-=2,--y;
      while(x&&e[j]) --e[j],--x;  
    }
    if(x|y) continue;
    x=0,y=1;
    for(int j=0;j<14;j++) {
      if(e[j]>x) y=0;
      x+=d[j]-e[j];
    }
    if(y) return 1;
  }
  return 0;
}
int C(int x,int o,int p,int q,int r) {
  if(x==14) return !q&&!r&&B(o,p);
  int f;
  if(c[x]>=4) {
    c[x]-=4,f=C(x+1,o,p+2,q,r+1),c[x]+=4;
    if(f) return 1;
  }
  if(c[x]>=3) {
    c[x]-=3,f=C(x+1,o+1,p,q+1,r),c[x]+=3;
    if(f) return 1;
  }
  if(a[x]>=4&&r) {
    a[x]-=4,f=C(x+1,o,p,q,r-1),a[x]+=4;
    if(f) return 1; 
  }
  if(a[x]>=3&&q) {
    a[x]-=3,f=C(x+1,o,p,q-1,r),a[x]+=3;
    if(f) return 1;
  }
  return C(x+1,o,p,q,r);
}
inline void dfs(int x, int s) {
  if (x == 14) {
    if (!s && C(0, 0, 0, 0, 0)) ++ans;
    return;
  }
  for (int i = 0; i <= s && i <= b[x]; i++) c[x] = i, dfs(x + 1, s - i);
}

void work() {
  dy['T'] = 6; dy['J'] = 7; dy['Q'] = 8; dy['K'] = 9;
  dy['A'] = 10; dy['2'] = 11; dy['w'] = 12; dy['W'] = 13;
  scanf("%s", s);
  rep(i, 0, 11) b[i] = 4; b[12] = b[13] = 1;
  rep(i, 0, 16) {
    int x;
    if (s[i] >= '4' && s[i] <= '9') x = s[i] - '4';
    else x = dy[s[i]];
    ++a[x]; --b[x];
  }
  dfs(0, 17);
  printf("%d\n", ans);
}

int main() {
  #ifdef LZT
    freopen("in", "r", stdin);
  #endif

  work();

  #ifdef LZT
    Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
  #endif
}

Day 2

T1 星際穿越

Solution

首先想暴力

發現一個點到左側一個點的最短路,一定是最多向右跑一次之後一直向左跑

每次取能達到的最小的 \(L\) 繼續跑

下面想正解

不難看出所有邊能夠形成一棵樹,而且在點樹上只向上跑,那麼考慮倍增

首先令 \(f[i][j]\) 表示 \([i, n]\) 中的所有點向左跑 \(2^j\) 步最遠能夠到達的位置,\(s[i][j]\) 表示從 \(i\) 這個點向左跑,跑到所有 \([i - 2^j, i]\) 之間的點的距離和

都可以倍增

\(f[i][j] = f[f[i][j-1]][j-1]\)

\(s[i][j]=s[i][j-1]+s[f[i][j-1]][j-1]+(f[i][j-1]-f[i][j])\cdot 2^{j-1}\)

這個自己想一下就好了

剩下的就是第一步向左走還是向右走的問題了

一個小技巧:我們先向左走一步

這樣的話,第一步向左走已經統計在裡面了,這一步經過的點距離都為 \(1\),加進答案裡即可

第一步向右走的話,肯定是因為右邊有一個點的 \(L\) 值比較小,比當前這個位置直接向左走兩步還要小

那麼我們從 \(i\) 的右邊那個點開始和從 \(L[i]\) 開始沒有區別

這樣就規避了先向右走的影響

Code
// Copyright lzt
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef std::pair<int, int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef std::pair<long long, long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i, j, k)  for (register int i = (int)(j); i <= (int)(k); i++)
#define rrep(i, j, k) for (register int i = (int)(j); i >= (int)(k); i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

inline ll read() {
  ll x = 0, f = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') f = -1;
    ch = getchar();
  }
  while (ch <= '9' && ch >= '0') {
    x = 10 * x + ch - '0';
    ch = getchar();
  }
  return x * f;
}

const int maxn = 300300;
int n;
ll sum[maxn][22], pw[22];
int to[maxn][22], L[maxn];

ll calc(int l, int r) {
  if (L[r] <= l) return r - l;
  ll ans = r - L[r]; r = L[r]; int tot = 1;
  rrep(i, 20, 0) if (to[r][i] > l) {
    ans += sum[r][i] + tot * (r - to[r][i]);
    r = to[r][i]; tot += pw[i];
  }
  return ans + (r - l) * 1ll * (tot + 1);
}

void work() {
  L[1] = 1; pw[0] = 1; n = read();
  rep(i, 2, n) L[i] = read();
  rep(i, 1, n) pw[i] = pw[i - 1] * 2ll;
  to[n][0] = L[n]; sum[n][0] = n - L[n];
  rrep(i, n - 1, 1) {
    to[i][0] = min(to[i + 1][0], L[i]);
    sum[i][0] = i - to[i][0];
  }
  rep(j, 1, 20) rep(i, 1, n) {
    if (to[i][j - 1]) {
      to[i][j] = to[to[i][j - 1]][j - 1];
      sum[i][j] = sum[i][j - 1] + sum[to[i][j - 1]][j - 1];
      sum[i][j] += (to[i][j - 1] - to[i][j]) * pw[j - 1];
    }
  }
  int q = read();
  while (q--) {
    int l = read(), r = read(), x = read();
    ll a = calc(l, x) - calc(r + 1, x), b = r - l + 1;
    printf("%lld/%lld\n", a / __gcd(a, b), b / __gcd(a, b));
  }
}

int main() {
  #ifdef LZT
    freopen("in", "r", stdin);
  #endif

  work();

  #ifdef LZT
    Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
  #endif
}