思路:对于第一问:f[i][j]表示前i个数,当前黑板上的数为j的概率
当前有三种情况
1. 当前数不是j的倍数—>黑板上的数字改变。
2. 当前数是j的倍数且当前数在前i个数中(已经选过)
3. 当前数是j的倍数且没有选过
转移:f[i+1][j]=((j的倍数个数-i)*f[i][j]+f[i][gcd(j,k)])的平均值 j的倍数个数-i是没选过的j的倍数。
对于第二问,考虑博弈论中sg函数。可知sg[i][1]二维含义同f数组)必定为0(最后黑板上剩下1必败) sg[n][i]=0(选完了必败) 同样枚举上述三种情况,取后续状态mex值即可。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define rep(i, l, r) for (int i = l; i <= r; i++) #define drep(i, r, l) for (int i = r; i >= l; i--) typedef long long ll; const int N = 1008; int n, T, a[N], g[N][N], sg[N][N], m, cnt[N], mex[N]; double f[N][N]; int gcd(int x, int y) {return !y ? x : gcd(y, x % y);} void init(int n) { rep(i, 0, n) rep(j, 0, n) g[i][j] = gcd(i, j); } bool check() { int gg=0; for (int i=1; i<=n; i++) gg=g[gg][a[i]]; if (gg==1) return false; if (n&1) printf("%.9lf 1.000000000 ",1); else printf("%.9lf 0.000000000 ",0); return true; } void solve() { if (check()) return ; memset(sg, 0, sizeof(sg)); memset(f, 0, sizeof(f)); memset(mex, 0, sizeof(mex)); rep(j, 0, m) sg[n][j] = 0, f[n][j] = 0; sg[n][1] = 1,f[n][1] = 1; int p = 0; drep(i, n - 1, 0) rep(x, 0, m) { if (x == 1) { sg[i][x] = 1; f[i][x] = 1; continue; } memset(cnt, 0, sizeof(cnt)); rep(j, 1, n) cnt[g[x][a[j]]]++; if (x && i && cnt[x] < i) continue; if (x) cnt[x] -= i; ++p; rep(y, 0, m) {if (cnt[y]) mex[sg[i + 1][y]] = p, f[i][x] += (double)cnt[y] / (n - i) * (1 - f[i + 1][y]);} for (sg[i][x] = 0; mex[sg[i][x]] == p; sg[i][x]++); } if (sg[0][0]) printf("%.9lf 1.000000000 ", f[0][0]); else printf("%.9lf 0.000000000 ", f[0][0]); } int main() { freopen("cards.in", "r", stdin); freopen("cards.out", "w", stdout); init(1000); scanf("%d", &n); m = 0; rep(i, 1, n) scanf("%d", &a[i]), m = max(m, a[i]); solve(); fclose(stdin); fclose(stdout); return 0; }
思路:T2树形dp+LCA
对于60~80分,可以n^2暴力,断掉每条边时,O(N)求每个部分的直径,然后相乘。
正解:倒序加边,考虑两棵树合并的时候新直径一定是原来两个直径四个断点任意两个的路径。所以可以首先求LCA倍增处理两点间路径,然后求最大。
#include <cstdio> #include <iostream> #define N 111111 #define MOD 1000000007 #define LL long long using namespace std; int a[N], dep[N], sum[N], par[N][18], h[N], del[N]; int endpoint[N][2], ans[N], f[N], length[N]; int tote, n, product; struct edge { int s, t, n; } e[N * 2]; void adde(int u, int v) { e[++tote].t = v; e[tote].s = u; e[tote].n = h[u]; h[u] = tote; return ; } void dfs(int u, int fa) { par[u][0] = fa; dep[u] = dep[fa] + 1; sum[u] = sum[fa] + a[u]; for (int i = 1; i < 18; i++) par[u][i] = par[par[u][i - 1]][i - 1]; for (int i = h[u]; i; i = e[i].n) { int v = e[i].t; if (v != fa) dfs(v, u); } return ; } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int t = dep[u] - dep[v], i = 0; t > 0; t >>= 1, i++) if (t & 1) u = par[u][i]; int t = 17; while (u != v) { while (t && par[u][t] == par[v][t]) t--; u = par[u][t]; v = par[v][t]; } return u; } int getf(int u) { if (u == f[u]) return u; f[u] = getf(f[u]); return f[u]; } int pw(int a, int b) { int ans = 1, t = a; for (int i = b; i; i >>= 1) { if (i & 1) ans = (LL) ans * t % MOD; t = (LL) t * t % MOD; } return ans; } int getlength(int u, int v) { int w = lca(u, v); return sum[u] + sum[v] - 2 * sum[w] + a[w]; } int getint() { char ch; do {ch=getchar();} while (ch!='-'&&(ch<'0'||ch>'9')); int ans=0,f=0; if (ch=='-') f=1; else ans=ch-'0'; while (isdigit(ch=getchar())) ans=ans*10+ch-'0'; if (f) ans*=-1; return ans; } int main() { freopen("forest.in", "r", stdin); freopen("forest.out", "w", stdout); n = getint(); product = 1; for (int i = 1; i <= n; i++) { a[i] = getint(); f[i] = i; product = (LL) product * a[i] % MOD; endpoint[i][0] = endpoint[i][1] = i; length[i] = a[i]; } for (int i = 1; i < n; i++) { int u = getint(), v = getint(); adde(u, v); adde(v, u); } dfs(1, 0); int t = n; ans[t] = product; for (int i = 1; i < n; i++) del[i] = getint(); for (int i = n - 1; i; i --) { int id = del[i], u = e[id * 2 - 1].s, v = e[id * 2 - 1].t; u = getf(u); v = getf(v); if (length[u] < length[v]) swap(u, v); int tmax = length[u], end[2]; for (int j = 0; j < 2; j++) end[j] = endpoint[u][j]; for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { int l = getlength(endpoint[u][j], endpoint[v][k]); if (l > tmax) { tmax = l; end[0] = endpoint[u][j]; end[1] = endpoint[v][k]; } } product = (LL) product * pw(length[u], MOD - 2) % MOD; product = (LL) product * pw(length[v], MOD - 2) % MOD; f[v] = u; length[u] = tmax; for (int j = 0; j < 2; j++) endpoint[u][j] = end[j]; product = (LL) product * length[u] % MOD; ans[--t] = product; } for (int i = 1; i <= n; i++) printf("%d ", ans[i]); return 0; }
思路:
组合数学
考虑两列的情况。若两列颜色分别为A,B,则A独有的颜色就是A—A∩B ,B同理。
若是多列那还是设两边两列为A,B,中间多列为C,那根据题目结论可以知道C一定是A∩B的子集。枚举AB中共有颜色个数i,B中独有颜色个数与A中相同为j。有C(K,i)*C(k-i+,j)*C(k-i-j,j)//共有+A,B,独有的情况。然后再求C中的情况,因为每个块都有染每种颜色的概率,所以总的就是k^n(m-2)种。但要保证这k种颜色必须都用,所以减掉那些用了<k种的就是最后答案。
#include<cstdio> #include<iostream> using namespace std; const int pp=1000000007; int c[2008][2008],f[2008],p[2008],ni[2008]; int n,m,k,nn; inline int power(int x,int n) { int ans=1,tmp=x; while (n) { if (n&1) ans=(long long)ans*tmp%pp; tmp=(long long)tmp*tmp%pp; n>>=1; } return ans; } void Count_c() { for (int i=0; i<=nn; i++) c[i][0]=1; for (int i=1; i<=nn; i++) for (int j=1; j<=i; j++) { c[i][j]=c[i-1][j-1]+c[i-1][j]; if (c[i][j]>=pp) c[i][j]-=pp; } } void Count_p() { int mm=(m-2)*n; for (int i=0; i<=nn; i++) p[i]=power(i,mm); } void Count_f() { f[0]=0; f[1]=1; for (int i=2; i<=nn; i++) { f[i]=power(i,n); for (int j=1; j<i; j++) { f[i]-=(long long)f[j]*c[i][j]%pp; if (f[i]<=-pp) f[i]+=pp; } if (f[i]<0) f[i]+=pp; } } void Count_ni() { ni[1]=1; for (int i=2; i<=nn; i++) ni[i]=power(i,pp-2); } int main() { freopen("photo.in","r",stdin); freopen("photo.out","w",stdout); scanf("%d%d%d",&n,&m,&k); nn=min(n,k); if (m==1) printf("%d ",power(k,n)); else { Count_c(); Count_p(); Count_f(); Count_ni(); long long tmp=1,tmp1=1,sum=0,sum1; for (int s=1; s<=nn; s++) { tmp=tmp*ni[s]%pp; tmp=tmp*(k-s+1)%pp; tmp1=1; sum1=0; for (int j=0; j<=s; j++) { sum1+=tmp1*c[s][s-j]%pp*p[s-j]%pp; if (sum1>=pp) sum1-=pp; tmp1=tmp1*ni[j+1]%pp; if (k-s<j+1) break; tmp1=tmp1*(k-s-j)%pp; } sum+=tmp*f[s]%pp*f[s]%pp*sum1%pp; if (sum>=pp) sum-=pp; } printf("%d ",sum); } fclose(stdin); fclose(stdout); return 0; }