A层邀请赛3
做A层邀请赛的题之前:我不是A层的怎么了,我偏要做A层的题,有一天我会进A层的!
做A层邀请赛的题之后:%%%,Cat对A层大佬佩服的五体投地请受我一拜……
A. 玩个球
部分分:n=2的情况,可以发现0的位置可以唯一限制变成0的两个颜色的先后顺序,除非前两个球分别是两种不同的颜色,这样就少了一组情况,2*k个球里选k个染同种颜色,固定前两个球的减一组,答案就是C(2*k, k)-C(2*k-2, k-1).
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5000; const ll mod = 1e9 + 7; int n, k; ll ans, mul[maxn], w1, w2; ll qpow(ll a, ll b) { ll ans = 1; while(b) { if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } int main() { scanf("%d%d", &n, &k); mul[1] = 1; for(int i=2; i<=k*2; i++) { mul[i] = mul[i-1] * i % mod; } w1 = qpow(mul[k], mod-2); w2 = w1*k%mod; ans = mul[k*2]*w1%mod*w1-mul[k*2-2]*w2%mod*w2%mod; ans = (ans + mod) % mod; printf("%lld\n", ans); return 0; }
正解:f[i][j]表示放了i个白球,有j个颜色放完的方案数(这里的放完指的不是都在当前位置的左边,指的是不算白球出现的颜色数,而一个颜色一旦出现,涂白的不算,它的所有情况就会被计入答案)。
循环的边界:容易得出从左往右数的话,白球的个数一定大于等于其它颜色的种类数。
循环的含义:枚举剩余位置第一个放啥 1.放白球,其它颜色种类数不变,直接转移 2.放颜色,不能选放过的颜色,所以这是新的颜色,从j-1转移,已经放完j-1种颜色,当前位置还有n-(j-1)种颜色可选,选到的颜色已经有一个被涂白,有一个放在当前位置,还有k-2个,总共剩余的位置数就是n*k-(j-1)*(k-1)-i-1。
细节——初始化fac[0]和inv[0]为1,因为有C(0, 0)。其实C函数里应该判断一个m>n return 0,不过在这道题里好像不判也能过。还有一个开数组的蠢问题:maxn*maxn不要开成5000啊,Cat又不会算数了……
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2006; const ll mod = 1e9 + 7; int n, k; ll ans, fac[maxn*maxn], inv[maxn*maxn], f[maxn][maxn]; ll qpow(ll a, ll b) { ll ans = 1; while(b) { if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } void pre(int mx) { fac[1] = 1; fac[0] = 1; for(int i=2; i<=mx; i++) fac[i] = fac[i-1]*i%mod; inv[mx] = qpow(fac[mx], mod-2); for(int i=mx; i>=1; i--) inv[i-1] = inv[i]*i%mod; } ll C(int n, int m) { return fac[n]*inv[m]%mod*inv[n-m]%mod; } int main() { scanf("%d%d", &n, &k); pre(n*k); for(int i=1; i<=n; i++) { f[i][0] = 1; for(int j=1; j<=i; j++) { f[i][j] = (f[i-1][j]+f[i][j-1]*(n-j+1)%mod*C(n*k-(j-1)*(k-1)-i-1, k-2)%mod)%mod; } } printf("%lld\n", f[n][n]); return 0; }
B. 序列
每次找到当前区间独一无二的数,然后拆分问题为左右两边的区间,暴力地二分判断一下,TLE 60。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 4; int T, a[maxn], b[maxn], cnt; int v[maxn], n; bool solve(int l, int r) { if(r <= l) return true; for(int i=l; i<=r; i++) v[a[i]] = 0; for(int i=l; i<=r; i++) { v[a[i]]++; } int pos = 0; bool fl = 0; for(int i=l; i<=r; i++) { if(v[a[i]] == 1) { fl = 1; pos = i; break; } } if(!fl) return 0; else if(solve(l, pos-1) && solve(pos+1, r)) return 1; return 0; } int main() { scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i=1; i<=n; i++) { scanf("%d", &a[i]); b[i] = a[i]; } sort(b+1, b+1+n); cnt = unique(b+1, b+1+n)-b-1; for(int i=1; i<=n; i++) { a[i] = lower_bound(b+1, b+1+cnt, a[i])-b; } bool flag = solve(1, n); if(flag) printf("non-boring\n"); else printf("boring\n"); } return 0; }
优化的话就是找到区间中独一无二的数的过程,不再每次数一遍个数,一个数在区间内只出现了一次还可以用它的上一次出现的位置和它的下一次出现的位置都不在当前区间里来判定,就可以预处理出pre[]和nxt[]。
进一步优化:循环可以不是从l到r,而是从两边向中间找,所以每个区间只循环了一半的长度,据说这叫启发式?我发现我不知道启发式是什么意思了……
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 4; int T, a[maxn], b[maxn], cnt; int pre[maxn], n, nxt[maxn], rm[maxn]; bool solve(int l, int r) { if(l >= r) return 1; int len = r - l + 1; int mx = (len + 1) >> 1; int pos = -1; for(int i=1; i<=mx; i++) { int nl = l+i-1, nr = r-i+1; if(pre[nl] < l && nxt[nl] > r) {pos = nl; break; } if(pre[nr] < l && nxt[nr] > r) {pos = nr; break; } } if(pos == -1) return 0; return solve(l, pos-1) && solve(pos+1, r); } int main() { scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i=1; i<=n; i++) { scanf("%d", &a[i]); b[i] = a[i]; } sort(b+1, b+1+n); cnt = unique(b+1, b+1+n)-b-1; for(int i=1; i<=n; i++) { a[i] = lower_bound(b+1, b+1+cnt, a[i])-b; } for(int i=1; i<=n; i++) pre[i] = 0; for(int i=1; i<=n; i++) nxt[i] = 0; for(int i=1; i<=n; i++) rm[i] = 0; for(int i=1; i<=n; i++) { pre[i] = rm[a[i]];//上一次a[i]出现的位置 nxt[rm[a[i]]] = i;//下一次出现的位置 rm[a[i]] = i; } for(int i=1; i<=n; i++) nxt[rm[a[i]]] = n+1;//上一个rm是最后一次出现 if(solve(1, n)) printf("non-boring\n"); else printf("boring\n"); } return 0; }