背景:
终于,还是逃不过的模拟赛,(尽管自从从海亮的15天自闭模拟赛回来就没有再打过比赛,,,整个八月就是和线段树过了一个月,,,,),毕竟都快比赛了,,老师就说是一个星期至少两场的模拟赛,听说到后面还会加多,,但是就是不停课,,,(无语ing ┐( ̄ヮ ̄)┌)说是今年的形式比较严峻了,毕竟Noip都没了,尽管改成了CSP,但是很明显,教育局还是不好惹的,,,但是这还是没有妨碍着我好好“上课”的步伐(好吧,一个星期不上了不到三十节课就很,,了),尽管下星期的开学考,我,,表示丝毫不慌,,,毕竟就没怎么上过课,,,,
不瞎扯了,,,还是好好过来看看昨天的模拟赛吧,,,
题目:
(尽管在考试的时候老师发了简化题目,但是我还是读了一个半小时的题目,,结果就敲了个第二题的暴力60分,最后一个小时就认认真真推了个等差数列的T1假算法,结果就过了第一个样例,,,╥﹏╥)(这题目,,怎么就noip级别了,,,╥﹏╥)
T1:树上路径(phantasm)
简化题目:
求1,2,…,n有多少个长为m的子序列a,满足:
•
a
1
=
1
,
a
m
=
n
a_1=1,a_m=n
a1=1,am=n
•
∀
i
,
a
i
+
1
−
a
i
>
=
k
∀i,a_{i+1}-a_i>=k
∀i,ai+1−ai>=k
保证这样的子序列存在。只需要判断方案数的奇偶性即可。
n , m , k < = 1 0 9 , T < = 2 × 1 0 6 n,m,k<=10^9,T<=2×10^6 n,m,k<=109,T<=2×106
题解:
这个题目啊,我就看了好长时间才看懂的,表示原题真的好容易糊弄人的!!还不如推一遍样例,,(本人认为推一变样例就是解释题面的最佳方案!)(仅仅是个人观点,说错了别打脸,,,)
先说一下所谓的暴力写法吧,这个题的暴力分给了45分的,30分的dfs,还有15分的特殊情况,(这样的话,加上我的60,Day1就过百了,,,菜死了),但是我还是花了一早上的时间研究了一下正解,,结果是组合数,,╥﹏╥(去***的等差数列)
-
对于 m < = 3 m<=3 m<=3 的算法
m = 2 m=2 m=2 时 , a n s = 1 ans=1 ans=1
m = 3 m=3 m=3 时, a n s = n − 2 ∗ k ans=n-2*k ans=n−2∗k -
对于 k = 1 k=1 k=1 的算法
a n s = ( n − 2 m − 2 ) ans=\dbinom{n-2}{m-2} ans=(m−2n−2)
-
对于 k < = 2 k<=2 k<=2 的算法
a n s = ( n − m − 1 m − 2 ) ans=\dbinom{n-m-1}{m-2} ans=(m−2n−m−1) -
引入dp思想的算法
-
f [ i ] [ j ] f[i][j] f[i][j]表示 a i = j a_i=j ai=j时,子序列a前i个位置可能的情况数(就是经过i位置到达j时的方案数)
f [ i ] [ j ] = ∑ p = 1 i − k f [ i − 1 ] [ p ] f[i][j]=\sum_{p=1}^{i-k} f[i-1][p] f[i][j]=p=1∑i−kf[i−1][p]
这里DP的复杂度就是 O ( T n m ) O(Tnm) O(Tnm)。在 k < = 200 k<=200 k<=200时,只需做200遍DP就好这样就可以得65分了,复杂度就是 O ( T + n m k ) O(T+nmk) O(T+nmk)的了。
-
推式子喽,,,(这里是需要两次差分来进行对于式子的简化)
设 b i = a i + 1 − a i b_i=a_{i+1}-a_i bi=ai+1−ai
则设一个 b b b序列满足:
- ∀ i , b i ∀i,b_i ∀i,bi是 [ k , n ] [k,n] [k,n]上的整数。
- f [ i ] [ j ] = ∑ i = 1 m − 1 b i = n − 1 f[i][j]=\sum_{i=1}^{m-1} b_i=n-1 f[i][j]=∑i=1m−1bi=n−1
再设一个c序列来除掉第一个条件,这样就满足:
- ∀ i , c i ∀i,c_i ∀i,ci是正整数
- ∑ i = 1 m − 1 c i = n − 1 − ( m − 1 ) ∗ ( k − 1 ) \sum_{i=1}^{m-1} c_i=n-1-(m-1)*(k-1) ∑i=1m−1ci=n−1−(m−1)∗(k−1)
由隔板法得:
a
n
s
=
(
n
−
2
−
(
m
−
1
)
∗
(
k
−
1
)
m
−
2
)
ans=\dbinom{n-2-(m-1)*(k-1)}{m-2}
ans=(m−2n−2−(m−1)∗(k−1))
这样的话就是90分的做法了,那100分的怎么做呢,,
——再加个Lucas定理不就好了嘛,,,,
由Lucas 定理,
(
n
k
)
≡
1
(
m
o
d
2
)
\dbinom{n}{k}≡1 (mod 2)
(kn)≡1(mod2) 当且仅当二进制下k 的各
位都不大于n 的对应位,即
n
n
n &
k
=
k
k=k
k=k。
这样的话就可以
O
(
1
)
O(1)
O(1)求解了。
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
int T,n,m,k;
int main()
{
freopen("phantasm.in","r",stdin);
freopen("phantasm.out","w",stdout);
T=read();
for(int i=1;i<=T;i++)
{
n=read(); m=read(); k=read();
if((((n-2)-(m-1)*(k-1))&(m-2))==(m-2)) puts("Yes"); else puts("No");
}
return 0;
}
T2:泳池(skylines)
简化题目:
给一个n个点的树,点有权c_i,边也有权。有q次询问,每次给定u,询问:
f
(
u
)
=
m
i
n
(
d
i
s
(
u
,
v
)
+
c
u
−
c
v
)
(
v
≠
u
)
f(u)=min(dis(u,v)+c_u-c_v)(v \ne u)
f(u)=min(dis(u,v)+cu−cv)(v=u)
n
,
q
<
=
2
×
1
0
5
n,q<=2×10^5
n,q<=2×105
题解:
这个题,我考场上就写了个60分的做法,就写了个 d i j k s t r a dijkstra dijkstra,没有想到正解,表示现在也没有看懂题解,即其代码,(这ACM选手的指针加大量头文件,,我服了)
代码::
(就直接放一下60分的暴力吧,尽管很简单,,)
#include<bits/stdc++.h>
#define int long long
#define mkp make_pair
#define fir firsr
#define sec second
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int ocean=2147483647;
const int sea=2e5+7;
struct see{int ver,next,edge;}e[sea<<1];
int n,tot,ans,res[sea],dis[sea],vis[sea],w[sea],last[sea];
priority_queue<pair<int ,int > >Q;
void add(int x,int y,int z){e[++tot].edge=z;e[tot].next=last[x],e[tot].ver=y;last[x]=tot;}
void dijkstal(int s)
{
memset(dis,0x3f,sizeof(dis));
dis[s]=0;Q.push(mkp(0,s));
while(Q.size())
{
int x=Q.top().sec;Q.pop();
if(vis[x]) continue; vis[x]=1;
for(int i=last[x];i;i=e[i].next)
{
int y=e[i].ver,z=e[i].edge;
if(dis[y]>dis[x]+z)
{
dis[y]=dis[x]+z;
Q.push(mkp(dis[y],y));
}
}
if(x==s) res[x]=ocean;
else res[x]=dis[x]+w[s]-w[x];
ans=min(ans,res[x]);
}
}
signed main()
{
freopen("skylines.in","r",stdin);
freopen("skylines.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) w[i]=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read(),z=read();
add(x,y,z); add(y,x,z);
}
int T=read();
while(T--)
{
ans=ocean;
memset(dis,0,sizeof(dis));
memset(vis,0,sizeof(vis));
int x=read();dijkstal(x);
printf("%lld\n",ans);
}
return 0;
}
T3:空之轨迹(kiseki)
在这里说明一下下面的题解是翻老师所发过来的文件的,应该是出题人所写,在这里就当做我博客的内容,并没有请问老师,如若有不满或者警告,将立即改掉!
简化题目:
随机生成一个 m + 1 个数的数列,第一个数为 0, 生成第 i个数时,在前 i − 1 个数中等概率选择一个数 k, 则第 i 个数为k + 1。每个数均有一个对应的权值,求数列权值和的期望。
S S S:数列的权值和
X
i
X_i
Xi:第i个数的权值(
X
1
X_1
X1为定值0,所以忽略不计)
S
=
∑
i
=
2
m
+
1
X
i
S=\sum_{i=2}^{m+1}X_i
S=i=2∑m+1Xi
根据期望的线性性有(和的期望等于期望的和)
E
(
S
)
=
∑
i
=
2
m
+
1
E
(
X
i
)
E(S)=\sum_{i=2}^{m+1}E(X_i)
E(S)=i=2∑m+1E(Xi)
只要求出
E
(
X
i
)
E(X_i)
E(Xi)就可以了,再设
P
[
i
]
[
j
]
P[i][j]
P[i][j]:序列的第i个数为j的概率
E
(
X
i
)
=
∑
j
=
1
i
−
1
P
[
i
]
[
j
]
∗
a
j
E(X_i)=\sum_{j=1}^{i-1}P[i][j]\ast a_j
E(Xi)=j=1∑i−1P[i][j]∗aj
现在只要求出
P
[
i
]
[
j
]
P[i][j]
P[i][j]就可以了
j
j
j的出现要求在前
i
−
1
i-1
i−1个数中随机到
j
−
1
j-1
j−1
P
[
i
]
[
j
]
=
∑
k
=
1
i
−
1
1
i
−
1
P
[
k
]
[
j
−
1
]
P[i][j]=\sum_{k=1}^{i-1}\frac {1}{i-1}P[k][j-1]
P[i][j]=k=1∑i−1i−11P[k][j−1]
综上所述
E
(
S
)
=
∑
i
=
2
m
+
1
∑
j
=
1
i
−
1
a
j
∑
k
=
1
i
−
1
1
i
−
1
P
[
k
]
[
j
−
1
]
E(S)=\sum_{i=2}^{m+1}\sum_{j=1}^{i-1}a_j \sum_{k=1}^{i-1}\frac {1}{i-1}P[k][j-1]
E(S)=i=2∑m+1j=1∑i−1ajk=1∑i−1i−11P[k][j−1]
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int mod=998244353;
const int sea=1e5+7;
const int pool=30;
int n,m;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
LL p[pool][pool],a[sea],ny[pool],x[pool];
LL ksm(LL a,LL b)
{
LL s=1;
while(b)
{
if(b&1) s=s*a%mod;
a=a*a%mod; b>>=1;
}
return s;
}
int main()
{
freopen("kiseki.in","r",stdin);
freopen("kiseki.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m+1;i++) ny[i]=ksm(i,mod-2);
x[1]=0; p[1][0]=1;
for(int i=2;i<=m+1;i++)
for(int j=1;j<=i-1;j++)
{
for(int k=1;k<=i-1;k++)
p[i][j]=(p[i][j]+p[k][j-1]*ny[i-1]%mod)%mod;
x[i]=(x[i]+p[i][j]*a[j]%mod)%mod;
}
LL ans=0;
for(int i=2;i<=m+1;i++) ans=(ans+x[i])%mod;
printf("%lld\n",ans);
return 0;
}
总结:
这次的模拟赛就是猝不及防啊,刷了一个月的线段树,数据结构,这就DP,和图论都快忘完了,还是要在敲敲板子复习复习啊,,,要不然就死人了,,,这次考了个组合数,树形DP,还有个不知道是什么知识点的算是期望的数学题,考的数学内容还是偏多的,这时候就想到了我的组合数(T▽T),早知道开数论的时候就顺便把组合数也开了,导致现在就是见到组合数比较慌,,,要好好补补了,,