2017 Chinese Multi-University Training, BeihangU Contest
阿新 • • 發佈:2021-07-16
多校
A:Add More Zero
\(10^n = 2^m\)
$n = lg2^m = mlg2 = m * \frac{log(n)}{log(m)} $ //以m為底n即:$\frac{log(n)}{log(m)} $
B: Balala Power!
26進位制大數模擬比較
比較只有26個串,所以可以轉換成字串然後sort
前導0加點判斷
struct node{ string s; bool f2; int id; bool operator < (const node &a) const { return f2 == a.f2 ? s < a.s : f2 > a.f2; } }ss[26]; int num[26][maxn]; const int mod = 1e9 + 7; int n, cas = 0; bool vis[26]; void run() { int mxl = 0; for(int i = 0; i < 26; ++ i) vis[i] = 0, ss[i].s = "", ss[i].f2 = 0, ss[i].id = i; for(int i = 0; i < n; ++ i) { scanf("%s", s); int len = strlen(s); mxl = max(mxl, len); if(len > 1) vis[s[0] - 'a'] = 1; for(int j = len - 1, sz = 0; j >= 0; -- j) num[s[j] - 'a'][sz++] ++; } for(int i = 0; i < 26; ++ i) { for(int j = 0; j < mxl; ++ j) { num[i][j + 1] += num[i][j] / 26; num[i][j] %= 26; if(num[i][mxl]) mxl ++; } } for(int i = 0; i < 26; ++ i) for(int j = mxl - 1; j >= 0; -- j) ss[i].s += 'a' + num[i][j]; sort(ss, ss + 26); for(int i = 0; i < 26; ++ i) if(!vis[ss[i].id]) { ss[i].f2 = 1; break; } sort(ss, ss + 26); int ans = 0; for(int i = 0; i < 26; ++ i) { //cout << ss[i].s << " " << ss[i].f1 << " " << ss[i].f2 <<endl; int len = ss[i].s.length(), add = 0; for(int j = 0; j < len; ++ j) add = (add * 26 + (ss[i].s[j] - 'a') * i) % mod; ans = (ans + add) % mod; } printf("Case #%lld: %lld\n", ++cas, ans % mod); for(int i = 0; i < 26; ++ i) for(int j = 0; j <= mxl; ++ j) num[i][j] = 0; return ; }
C: Colorful Tree
\[\frac {n * n * (n - 1)} 2 - ans_{dfs} - \sum_{i = 1}^n C_n^{sum_i} \]//思路:容斥,考慮對於每種顏色刪去後,分成的塊中的點兩兩之間對於這種顏色沒有貢獻 //可以用桶做到O(n),核心是利用了子數具有可減性。 const int maxn = 2e5 + 10; int head[maxn], to[maxn << 1], nxt[maxn << 1], ecnt = 0, ans = 0; inline void add(int u, int v) { to[++ecnt] = v; nxt[ecnt] = head[u]; head[u] = ecnt; to[++ecnt] = u; nxt[ecnt] = head[v]; head[v] = ecnt; } int sum[maxn], sz[maxn], c[maxn]; inline int cn2(int n) { return n * (n - 1) >> 1; } int dfs(int now, int fa) { sz[now] = 1; for(int i = head[now]; i; i = nxt[i]) { int v = to[i]; if(v == fa) continue; int r = sum[c[now]];//當前子節點數 sz[now] += dfs(v, now); int o = sum[c[now]] - r;//下一個點的子節點數 ans += cn2(sz[v] - o);//與下一個點之間連通塊大小 sum[c[now]] += sz[v] - o;//加上連通塊中的點 } sum[c[now]] ++;//自己不要忘記 return sz[now]; } /* 8 1 2 5 3 7 2 1 3 1 2 2 3 3 4 3 5 3 6 6 7 7 8 */ int n, m, cas = 0; void run() { for(int i = 1; i <= n; ++ i) { c[i] = rd(); sum[i] = 0; head[i] = 0; } ecnt = 0; for(int i = 1; i < n; ++ i) { int u = rd(), v = rd(); add(u, v); } ans = 0; dfs(1, 1); //printf("%lld\n", ans); for(int i = 1; i <= n; ++ i) ans += cn2(n - sum[i]);//sum[i]是剩下的不包含在dfs中兩個顏色中的連通塊,或者說是剩下的主連通塊 ans = cn2(n) * n - ans; //單個顏色的貢獻是$\frac {n*(n-1)} 2$ 減去不含這個顏色的路徑數 //通過每個相同顏色的點將樹分割為一個個連通塊,那麼顯然連通塊內路徑不會經過這個顏色 //n個顏色總貢獻為$\frac {n * n * (n - 1)} 2 - ans_dfs - \sum_{i = 1}^n C_n^{sum_i} $ printf("Case #%lld: %lld\n", ++cas, ans); return ; }
F: Function
void dfs(int now, int *a) { if(vis[now]) return ; vis[now] = 1; cnt ++; dfs(a[now], a); } void run() { cout << log(2) << " " << log(10) << endl; printf("Case #%lld: ", ++cas); for(int i = 0; i < m; ++ i) vis[i] = 0; for(int i = 0; i <= n; ++ i) c[i] = 0; for(int i = 0; i < n; ++ i) a[i] = rd(); for(int i = 0; i < m; ++ i) b[i] = rd();//b[rd()] = i; 序列等價 for(int i = 0; i < m; ++ i) if(!vis[i]) {//求出b的所有自環大小,統計 cnt = 0; dfs(i, b); c[cnt] += cnt; c[cnt] %= mod; } for(int i = n; i; --i) for(int j = i + i; j <= n; j += i) c[j] += c[i];//所有含這個因子的數都可以得到因子的貢獻 int ans = 1; for(int i = 0; i < n; ++ i) vis[i] = 0; for(int i = 0; i < n; ++ i) if(!vis[i]) {//求出a的所有自環,與b對映要求a環需要選b環大小是a的因子,計算貢獻 cnt = 0; dfs(i, a); ans = ans * c[cnt] % mod; } printf("%lld\n", ans); return ; }
H: Hints of sd0061
\(nth\_element(T^*~ a,int~k,T^ *~end) O(n)\)查詢序列第\(k\)小的數
查詢後將第\(k\)的數放在\(k\)的位置,比\(k\)小的數到了前部分,\(k\)大的數到了後部分
內部實現應該是類快排的分治排序演算法求區間第\(k\)小
通過這個性質在大區間無法排序的情況下查詢少部分位置的數可通過對位置離線排序後挨個查詢,這樣可以慢慢減小區間
題目中滿足\(b_i + b_j <= b_k\),即增長類似斐波那契速度,那麼顯然從後往前減小區間複雜度更優秀
void run() {
x = A; y = B; z = C;
for(int i = 1; i <= n; ++ i) a[i] = rng61();
for(int i = 1; i <= m; ++ i) b[i] = rd(), id[i] = i;
sort(id + 1, id + 1 + m, [](int x, int y) {return b[x] < b[y];});
for(int i = m, r = n; i >= 1; r = b[id[i]], -- i) {
nth_element(a + 1, a + b[id[i]] + 1, a + r + 1);
ans[id[i]] = a[b[id[i]] + 1];
}
printf("Case #%d: ", ++cas);
for(int i = 1; i <= m; ++ i) printf("%u%c", ans[i], " \n"[i == m]);
return ;
}
K:
思維題,畫幾個樣例就知道了
L: Limited Permutation
int fact[maxn], finv[maxn], inv[maxn];
inline int C(int n, int m) {
return fact[n] * finv[m] % mod * finv[n - m] % mod;
}
void init() {
fact[0] = fact[1] = 1;
finv[0] = finv[1] = 1; inv[1] = 1;
for(int i = 2; i < maxn; ++ i) {
fact[i] = fact[i -1] * i % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
finv[i] = inv[i] * finv[i - 1] % mod;
}
}
struct seg{
int l, r, p;
friend bool operator < (seg a,seg b){
if(a.l!=b.l) return a.l < b.l;
return a.r > b.r;
}
}s[maxn];
int n, cas = 0, nowpos;
/*
首先要理解題意:當前僅當li<=L<=i<=R<=ri時P[i]=min(P[L],P[L+1],...,P[R])
因此對於P[i]一定有P[i]>P[li-1]且P[i]>P[ri+1],進一步說區間[li,ri](除了[1,n])一定被某個區間[lj,rj]包含,且j=li-1或j=ri+1
即區間j可分成[lj,j-1]和[j+1,rj]
我們把n個區間按L升序R降序進行排序(這樣得到的區間LR正是前序遍歷的區間,區間由大到小)。得到的第1個區間一定要是[1,n](1比任何數都小),否則不合法,輸出0;
設這個區間對應的是第i個數,因此區間可再分為[1,i-1]和[i+1,n],看是否有這2個區間,如果沒有則不合法,輸出0...直到區間不可再分。
現在再來考慮方法數:設f(i)為區間i內的方法數,u,v分別為左右子區間,i內一共有ri-li+1個數,除去中間一個,
要從中選i-li個數放入左區間,剩下的放入右區間,因此答案為:f(i)=f(u)*f(v)*C(ri-li,i-li)
*/
void run() {
for(int i = 1; i <= n; ++ i) s[i].p = i, s[i].l = rd();
for(int i = 1; i <= n; ++ i) s[i].r = rd();
sort(s + 1, s + 1 + n);
//for(int i = 1; i <= n; ++ i) dbg(i, s[i].p, s[i].l, s[i].r);
nowpos = 1;
function <int(int, int)> dfs;
dfs = [&](int l, int r) {
if(l != s[nowpos].l || r != s[nowpos].r) return 0ll;
int mid = s[nowpos].p;
//dbg(l, r, nowpos, mid, s[nowpos].l, s[nowpos].r);
nowpos ++;
int ansl = 1, ansr = 1;
if(l < mid) ansl = dfs(l, mid - 1);
if(r > mid) ansr = dfs(mid + 1, r);
return ansl * ansr % mod * C(r - l, mid - l) % mod;
};
int ans = dfs(1, n);
printf("Case #%lld: %lld\n", ++cas, ans);
return ;
}
signed main() {
init();
while(~scanf("%lld", &n)) run();
return 0;
}