20181030
orzslz rk1的神犇,被屠了150分
T1 4008: 纸牌游戏(cards)
题目描述
华华和秀秀在玩纸牌游戏,游戏的规则如下:
初始时,桌面上有n张纸牌,每张纸牌上写有一个正整数。游戏开始时华华先在黑板上写上数字0,之后秀秀和华华轮流选取纸牌(秀秀先手)。当一个人选定一张纸牌时,他需要将黑板上的数字改写成这个数和纸牌上的数的最大公约数,然后将这张纸牌丢弃。当一个人写下了1 或者无法选取纸牌时,他就输了。
现在秀秀想知道:
- 当华华和秀秀都按照随机策略选取卡片时,秀秀获胜的概率有多少;
- 当华华和秀秀都按照最优策略选取卡片时,秀秀获胜的概率有多少。
对于100%的数据:n≤300, 纸牌上的数≤1000。
题解:
对于第一问,我们设F[i][j]表示第i轮的gcd为j的概率
预处理sum[j]表示j的倍数有几个
则考虑主动转移:
- F[i+1][j]+=F[i][j]*(sum[j]-i)/(n-i);
- F[i+1][gcd(a[l],j)]+=F[i][j]/(n-i);
当j为1时或者i为n时,即可算出赢的概率
对于第二问,我们可以求出一些最小单位的出现次数,表示所有gcd中,一些不能够被别的gcd所整除的数
然后对于每个a[i],如果它的因为是最小单位,就要求它的次数为奇数,如果满足,先手获胜
具体见代码
#include <cstdio>
#include <algorithm>
#define db double
using namespace std;
int n,a[305],s[1005],g[1005],d[305][1005],ax;
db f[305][1005],p;bool v[1005],J;
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);ax=max(a[i],ax);
for (int j=1;j<=a[i];j++)
if (a[i]%j==0) s[j]++;
}
for (int i=1;i<=n;i++) for (int j=0;j<=ax;j++) d[i][j]=__gcd(a[i],j);
f[0][0]=1;
for (int i=0;i<n;i++){
for (int j=0;j<=ax;j++){
if (j^1) f[i+1][j]+=f[i][j]*(s[j]-i)/(n-i);
for (int l=1;l<=n;l++) if (d[l][j]^j)
f[i+1][d[l][j]]+=f[i][j]/(n-i);
}
if (i&1) p+=f[i+1][1];
}
if (n&1) for (int i=2;i<=ax;i++) p+=f[n][i];printf("%.9lf ",p);
for (int i=1;i<=n;i++) for (int j=1;j<i;j++) v[d[i][a[j]]]=1;
for (int i=2;i<=ax;i++) if (v[i])
for (int j=i+i;j<=ax;j+=i) v[j]=0;
for (int i=2;i<=ax;i++) if (v[i])
for (int j=1;j<=n;j++) g[i]+=(a[j]%i==0);
v[1]=0;for (int i=1;i<=n;i++){
J=1;if (a[i]==1) continue;
for (int j=1;j*j<=a[i];j++)
if (a[i]%j==0){
if (v[j]) J&=(g[j]&1);
if (v[a[i]/j]) J&=(g[i/j]&1);
}
if (J) return puts("1.000000000"),0;
}
return puts("0.000000000"),0;
}
T2 4009: 秀秀的森林(forest)
题目描述
秀秀有一棵带n个顶点的树T,每个节点有一个点权ai。
有一天,她想拥有两棵树,于是她从T中删去了一条边。
第二天,她认为三棵树或许会更好一些。因此,她又从她拥有的某一棵树中删去了一条边。
如此往复,每一天秀秀都会删去一条尚未被删去的边,直到她得到由n棵只有一个点的树构成的森林。
秀秀定义一条简单路径(节点不重复出现的路径)的权值为路径上所有点的权值之和,一棵树的直径为树上权值最大的简单路径。秀秀认为树最重要的特征就是它的直径。所以她想请你算出任一时刻她拥有的所有树的直径的乘积。因为这个数可能很大,你只需输出这个数对1e9+7取模之后的结果即可。
n≤100000,ai≤10000
题解
首先,删边看成加边
然后对于一棵树,这每个点引出去的最长半链的另一端一定在它的最长直径的端点上,这个可以利用反证法证明
然后我们在合并树的过程中,可以记录下每棵树的直径的两点以及其直径,新的树的直径要么是原来两棵树的直径,要么是连接起来的两个端点的最长半链相加,所以我们只要把两边直径的端点分别求出距离,取max即可
具体见代码
#include <cstdio>
#include <algorithm>
#define I inline
#define E register
using namespace std;
const int N=1e5+5,P=1e9+7;
int n,t,a[N],X[N],Y[N],head[N],V[N*2],
nex[N*2],b[N],f[N],s[N],ans[N],fa[N],
dep[N],top[N],sz[N],son[N],h[N];
struct O{int x,y;}fr[N];
I int K(E int x,E int y){
int A=1;while(y){
if (y&1) A=1ll*A*x%P;
x=1ll*x*x%P;y>>=1;
}
return A;
}
I void add(E int u,E int v){
V[++t]=v;nex[t]=head[u];head[u]=t;
}
I void ins(E int u,E int v){
add(u,v);add(v,u);
}
I int get(E int x){
return x==f[x]?x:f[x]=get(f[x]);
}
I void dfs1(E int x,E int fat,E int deep){
dep[x]=deep;fa[x]=fat;sz[x]=1;h[x]=h[fat]+a[x];
for (E int i=head[x];i;i=nex[i]){
if (V[i]==fat) continue;
dfs1(V[i],x,deep+1);sz[x]+=sz[V[i]];
if (sz[V[i]]>sz[son[x]]) son[x]=V[i];
}
}
I void dfs2(E int x,E int tp){
top[x]=tp;if (son[x]) dfs2(son[x],tp);
for (E int i=head[x];i;i=nex[i])
if (V[i]^fa[x] && V[i]^son[x])
dfs2(V[i],V[i]);
}
I int lca(E int x,E int y){
while(top[x]!=top[y]){
if (dep[top[x]]<dep[top[y]]) swap(x,y);x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);return x;
}
I int cal(E int x,E int y){
int L=lca(x,y);
return h[x]-h[fa[L]]+h[y]-h[fa[L]]-a[L];
}
int main(){
scanf("%d",&n);ans[0]=1;
for (E int i=1;i<=n;i++)
scanf("%d",&a[i]),s[i]=a[i],fr[i]=(O){i,i},
f[i]=i,ans[0]=1ll*a[i]*ans[0]%P;
for (E int i=1;i<n;i++)
scanf("%d%d",&X[i],&Y[i]),ins(X[i],Y[i]);
dfs1(1,0,1);dfs2(1,1);
for (E int i=n-1;i;i--) scanf("%d",&b[i]);
for (E int i=1;i<n;i++){
int k1=get(X[b[i]]),k2=get(Y[b[i]]),
s1=s[k1],s2=s[k2],a1=fr[k1].x,a2=fr[k1].y,
b1=fr[k2].x,b2=fr[k2].y,g1=cal(a1,b1),
g2=cal(a1,b2),g3=cal(a2,b1),g4=cal(a2,b2);
s[k1]=max(max(max(g1,g2),s1),max(max(g3,g4),s2));f[k2]=k1;
if (s[k1]==s1) fr[k1]=(O){a1,a2};
if (s[k1]==s2) fr[k1]=(O){b1,b2};
if (s[k1]==g1) fr[k1]=(O){a1,b1};
if (s[k1]==g2) fr[k1]=(O){a1,b2};
if (s[k1]==g3) fr[k1]=(O){a2,b1};
if (s[k1]==g4) fr[k1]=(O){a2,b2};
ans[i]=1ll*ans[i-1]*K(s1,P-2)%P*K(s2,P-2)%P*s[k1]%P;
}
for (E int i=n-1;i>=0;i--) printf("%d\n",ans[i]);
return 0;
}
T3 4010: 秀秀的照片(photo)
题目描述
华华在和秀秀视频时有截很多图。华华发现秀秀的每一张照片都很萌很可爱。为什么会这样呢?华华在仔细看过秀秀的所有照片后,发现秀秀的照片都具有一个相同的性质。
设秀秀的分辨率为m×n,即在水平方向上每一行有m个像素,垂直方向上每一列有n个像素,照片共有m×n个像素。每一个像素都有一个颜色,共有k种颜色。华华宝宝发现无论是沿着哪两列像素的分界线将秀秀的照片分成左右两半(共有m−1种分法),左右两半不同颜色的种数都是相同的。
华华宝宝把自己的发现告诉了秀秀宝宝。现在秀秀想知道当照片分辨率为m×n,像素颜色种数为k(不一定k种颜色都出现)的时候,共有多少张不同的照片满足上面的性质。
由于答案可能很大,你只需输出答案对1e9+7取模的结果即可。
题解
只要保证左右两列的颜色总数的是一样的,并且中间填充的是左右两列的公共颜色就好了。
然后了解一下第二类斯特林数,s[n][m]表示把n个不同的小球放在m个相同的盒子里方案数。
这题我们先枚举公共的颜色数i,然后再枚举左右两列的颜色数j,这其实就是把n个小球放在j个盒子里,然后这j个盒子可以随便排,所以就是s[n][j]*j!,因为是左右两列,所以是平方
所以式子是:
具体见代码
#include <cstdio>
#define I inline
const int N=1e6+5,P=1e9+7;
int n,m,k,s[2005][2005],jc[N],ny[N],a;
I int K(int x,int y){
int A=1;while(y){
if (y&1) A=1ll*A*x%P;
x=1ll*x*x%P;y>>=1;
}return A;
}I int M(int x){return 1ll*x*x%P;}
I int C(int x,int y){return (x<y)?0:1ll*jc[x]*ny[y]%P*ny[x-y]%P;}
int main(){
scanf("%d%d%d",&n,&m,&k);
jc[0]=1;for (int i=1;i<=k;i++) jc[i]=1ll*i*jc[i-1]%P;
ny[k]=K(jc[k],P-2);for (int i=k;i;i--) ny[i-1]=1ll*i*ny[i]%P;
s[0][0]=1;for (int i=1;i<=n;i++) for (int j=1;j<=n;j++)
s[i][j]=(s[i-1][j-1]+1ll*j*s[i-1][j]%P)%P;
for (int i=0;i<=n && i<=k;i++){
int p=1ll*K(i,n*(m-2))*C(k,i)%P;
for (int j=i;j<=n && j<=k;j++)
a=(a+1ll*p*M(1ll*s[n][j]*jc[j]%P)%P*C(k-i,j-i)%P*C(k-j,j-i)%P)%P;
}
return printf("%d\n",a),0;
}