略
先用总的长度 ( n − 1 ) (n-1) (n−1)减去每次至少跳 k k k的限制,即令 N = ( n − 1 ) − ( m − 1 ) ∗ k N=(n-1)-(m-1)*k N=(n−1)−(m−1)∗k,然后用插板法,答案就是 C ( n + m − 2 , m − 2 ) C(n+m-2,m-2) C(n+m−2,m−2)
要求的是奇偶性。也就是模 2 2 2,考场上我先写了 L u c a s Lucas Lucas,时间复杂度 O ( T l o g N ) O(TlogN) O(TlogN),初步判定能过(毕竟良心出题人时限 2.5 s 2.5s 2.5s)。
然后发现可直接统计组合数分子分母分别有多少 2 2 2次幂作比较,只有当上下 2 2 2次幂相等才是奇数。
我认为这样常数更小,就成了这个,但还是 O ( T l o g N ) O(TlogN) O(TlogN)。不过要做 3 3 3次 l o g log log,分别是 n , m , n − m n,m,n-m n,m,n−m,就还有个 3 3 3的常数。实测比 L u c a s Lucas Lucas慢。
看了题解,发现可以根据卢卡斯定理得出, C ( n , k ) C(n,k) C(n,k)为奇数当且仅当 n , k n,k n,k在二进制下的每一位 n n n都要大于等于 k k k,否则就会在某一个过程得到 C ( 0 , 1 ) = 0 C(0,1)=0 C(0,1)=0,然后就为偶数了。所以当答案为奇数,当且仅当 n & k = k n\&k=k n&k=k。所以就 O ( n ) O(n) O(n)了。良心出题人。。
O ( n ) O(n) O(n)极简代码
#include
int main(){
int n,m,k,T;scanf("%d",&T);
while(T--)scanf("%d%d%d",&n,&m,&k), puts(((n-(m-1)*k+m-3)&(m-2))==m-2?"Yes":"No");
}
略
可以发现对于一个点 u u u,要求的就是 min v c [ u ] − c [ v ] + d i s [ u ] + d i s [ v ] − 2 ∗ d i s [ l c a ( u , v ) ] = c [ u ] + d i s [ u ] + min v d i s [ v ] − c [ v ] − 2 ∗ d i s [ l c a ( u , v ) ] \min_v\ c[u]-c[v]+dis[u]+dis[v]-2*dis[lca(u,v)]\\=c[u]+dis[u]+\min_v\ dis[v]-c[v]-2*dis[lca(u,v)] vmin c[u]−c[v]+dis[u]+dis[v]−2∗dis[lca(u,v)]=c[u]+dis[u]+vmin dis[v]−c[v]−2∗dis[lca(u,v)]
所以我们在 l c a lca lca处考虑贡献, v v v能对 u u u贡献,它们一定在 l c a lca lca的不同子树内。所以我们 d f s dfs dfs遍历 l c a lca lca的所有子树,存下来 ( d i s [ i ] − c [ i ] ) (dis[i]-c[i]) (dis[i]−c[i])的最小值和次小值,同时保证这两个值不在同一子树内。那么就可以对一棵子树下面所有的点来进行贡献。如果最小值出在这棵子树,就用次小值更新;否则就用最小值更新。
然后我想到子树求 min \min min就写了 d f s dfs dfs序+线段树,然后最后下放标记。 O ( n l o g n ) O(nlogn) O(nlogn)(思维僵化)。还是过了,良心出题人。
发现可以直接打到点上,最后下放标记。 O ( n ) O(n) O(n)
两次 d f s dfs dfs,类似树形 D P DP DP。
#include
using namespace std;
typedef long long LL;
inline void read(int &x) {
char ch; while(!isdigit(ch=getchar()));
for(x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
}
const int MAXN = 200005;
const LL INF = 1ll<<50;
int n, m, c[MAXN], fir[MAXN], to[MAXN<<1], nxt[MAXN<<1], w[MAXN<<1], cnt;
inline void link(int u, int v, int wt) {
to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt; w[cnt] = wt;
to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt; w[cnt] = wt;
}
LL dis[MAXN], mn[MAXN], lz[MAXN], ans[MAXN];
void dfs(int u, int ff) {
mn[u] = dis[u] - c[u];
LL val[2] = { mn[u], 1ll<<50 }, bel[2] = { u, -1 };
for(int i = fir[u], v; i; i = nxt[i])
if((v=to[i]) != ff) {
dis[v] = dis[u] + w[i], dfs(v, u);
mn[u] = min(mn[u], mn[v]);
if(mn[v] < val[0]) val[1] = val[0], bel[1] = bel[0], val[0] = mn[v], bel[0] = v;
else if(mn[v] < val[1]) val[1] = mn[v], bel[1] = v;
}
for(int i = fir[u], v; i; i = nxt[i])
if((v=to[i]) != ff) {
if(bel[0] != v) lz[v] = min(lz[v], val[0]-2*dis[u]);
else if(bel[1] != v && bel[1] != -1) lz[v] = min(lz[v], val[1]-2*dis[u]);
}
if(bel[0] != u) ans[u] = min(ans[u], val[0]-2*dis[u]);
else if(bel[1] != u && bel[1] != -1) ans[u] = min(ans[u], val[1]-2*dis[u]);
}
inline void getans(int u, int ff) {
lz[u] = min(lz[u], lz[ff]);
ans[u] = min(ans[u], lz[u]);
for(int i = fir[u], v; i; i = nxt[i])
if((v=to[i]) != ff)
getans(v, u);
}
int main () {
read(n);
for(int i = 1; i <= n; ++i) read(c[i]), lz[i] = ans[i] = INF;
for(int i = 1, x, y, z; i < n; ++i)
read(x), read(y), read(z), link(x, y, z);
dfs(1, 0), getans(1, 0);
int q, x; read(q);
while(q--) read(x), printf("%lld\n", ans[x] + dis[x] + c[x]);
}
同略
考场上傻逼了。感觉整道题很麻烦,多个存档很难搞。然后就不会了。
发现一定只有前 m m m个 a a a值有用。所以 n n n从 1 0 5 → 21 10^5\to21 105→21。
所以正解就是 O ( m 2 m ) O(m2^m) O(m2m)的算法。所以其实有 O ( m 2 ) O(m^2) O(m2)的算法。
概率 D P DP DP,设 f [ i ] [ j ] f[i][j] f[i][j]表示第 i i i个轮玩第 j j j章的概率。然后发现转移十分显然: f [ i ] [ j ] = ∑ k = 0 i − 1 f [ k ] [ j − 1 ] f[i][j]=\sum_{k=0}^{i-1}f[k][j-1] f[i][j]=k=0∑i−1f[k][j−1]
最后答案就是 ∑ f [ i ] [ j ] ∗ a [ j ] \sum f[i][j]*a[j] ∑f[i][j]∗a[j]。
这样是 O ( m 3 ) O(m^3) O(m3)(虽然对于这道题已经十分优秀了)
发现转移可以前缀和优化。时间达到了 O ( m 2 ) O(m^2) O(m2)。良心出题人(?)不卡O(m^3)
如果前缀和数组只开一维的话要注意转移时 j j j从大到小枚举。
所以说标算被碾了, m m m可以开到 5 k 5k 5k。 O ( m 2 m ) O(m2^m) O(m2m)标算见文末。
#include
const int MAXM = 22;
const int mod = 998244353;
int n, m, inv[MAXM], f[MAXM][MAXM], a[MAXM], sum[MAXM];
int main () {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i) scanf("%d", &a[i]);
inv[0] = inv[1] = 1;
for(int i = 2; i <= m; ++i) inv[i] = 1ll * (mod - mod/i) * inv[mod%i] % mod;
f[0][0] = sum[0] = 1;
int ans = 0;
for(int i = 1; i <= m; ++i)
for(int j = i; j >= 1; --j) {
f[i][j] = 1ll * sum[j-1] * inv[i] % mod;
sum[j] = (sum[j] + f[i][j]) % mod;
ans = (ans + 1ll * f[i][j] * a[j] % mod) % mod;
}
printf("%d\n", ans);
}
标算就是把当前所有存档按标号排序得到一个长度最大为 m m m的数组,差分后元素只会有 0 o r 1 0\ or\ 1 0 or 1,就可以状压了。 f ( i , S ) f(i, S) f(i,S)表示第 i i i轮,已有存档数列的差分为 S S S的方案数。则每种数列出现的概率 P S = f ( m , S ) m ! P_S=\frac{f(m,S)}{m!} PS=m!f(m,S)转移时枚举前一个数的大小,修改状态即可事先预处理出 t o t S tot_S totS表示状态 S S S所表示的数列的权值和,最后的答案即为 ∑ S P S t o t S \sum_SP_Stot_S ∑SPStotS复杂度: O ( m 2 m ) O(m2^m) O(m2m) 。
代码略。