Index
2019.10.12模拟赛
说在前面
四年前,小白甜;
四年后,腹黑透。
orz NODGD。
NP问题
问题描述
p6pou在平面上画了
n
n
n个点,并提出了一个问题,称为
N
−
P
o
i
n
t
s
N-Points
N−Points问题,简称NP问题。
p6pou首先在建立的平面直角坐标系,并标出了这
n
n
n个点的坐标。这 个点的坐标都是正整数,任意三个点都不共线。然后,p6pou选择其中一个点
A
A
A ,画一条
y
y
y轴的平行线,这条直线称为
l
l
l。直线
l
l
l以点
A
A
A为旋转中心逆时针旋转,当直线
l
l
l碰到另外一个点
B
B
B时,就立刻将
B
B
B点作为新的旋转中心继续逆时针旋转。此后,每当直线
l
l
l碰到除了旋转中心以外的另一个点,都会将这个点作为新的旋转中心继续逆时针旋转。这个过程可以一直进行。
p6pou不太关心旋转的完整过程,只想知道,当 旋转至平行于 轴时直线方程有哪些可能。
输入格式
输出文件
n
p
.
i
n
np.in
np.in。
第一行输入两个整数
n
,
A
n,A
n,A,表示平面上共有
n
n
n 个点,一开始
l
l
l与
y
y
y轴平行,直线方程是
x
=
A
x=A
x=A。
第
1
1
1到第
n
n
n行中,第
i
i
i行两个正整数
x
i
,
y
i
x_i,y_i
xi,yi,表示编号为
i
i
i的点的坐标,保证任意三点不共线。
输出格式
输出文件
n
p
.
o
u
t
np.out
np.out。
直线
l
l
l旋转到与
x
x
x轴平行时方程是
y
=
B
y=B
y=B,按从小到大的顺序输出
B
B
B所有可能的值,每行输出一个数。
输入输出样例1
样例输入
4 1
1 2
2 1
3 2
2 3
样例输出
1
3
样例说明
输入输出样例2
样例输入
6 2
2 2
2 4
4 1
4 2
3 4
1 3
样例输出
2
3
4
样例说明
数据规模与约定
对于100%的数据,n<=200 。
分析
这个包里三个700MB的视频,2个G的样例包,差点没吓死我。
于是我录了个gif挂在上面了。
我的做法啊……
以上是考场分析。
因为我相信数学是美丽而优雅的,所以我只dfs了两圈,就是从A点出发以后,第二次回来的话就结束程序。
于是就90分了。
如果我跑4圈就能A。
最好跑n圈,反正时间复杂度够,n才两百。
所以为什么n才两百呢?
于是我寻思着你造数据的时候就造一个大圆大椭圆大抛物线,在上面取点呗,你题面不写保证所有点在一个圆上,也没人会针对你的数据投机取巧,也不至于水到哪里去。
还是那句话,不喜欢冠冕堂皇以暴力为正解的题目。
所以简单说几句正解。为什么说
l
l
l两边的点的个数不变呢?
因为你旋转的时候,撞到了一个点。假设在撞之前,左边有L个,右边有R个,线上只有1个是我原来的旋转中心A,而我新撞到的点是点B,假设A在B左边,那么在撞到你B的前一瞬间,你B在我右边,你现在到线上了,R–,但我A出来了,而且我A不仅出来了还在你l的右边(画图自己看),所以R++。什么也没有发生。
所以我开始竖着的时候的L,R和我在任意时刻的L,R是不会变的。
而可能出现的横着的l就那么n个。
所以排排序就解决了。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
const int MAXN=444;
const double pi=acos(-1.0);
struct node
{
int id;
double alpha;
bool operator < (const node &n) const
{
return alpha<n.alpha;
}
}arr[MAXN][MAXN];
int n,A,c,B[MAXN],x[MAXN],y[MAXN],ok[MAXN],cnt[MAXN],ans[MAXN];
double angel(int i,int j)
{
double ans=(x[i]==x[j])?pi/2.0:atan((1.0*y[i]-y[j])/(x[i]-x[j]));
if(ans<0) ans+=pi;
return ans;
}
void dfs(int now,int las,int ed,int all)
{
if(now==ed) all++;
if(all==n) return ;
int pos=(upper_bound(arr[now]+1,arr[now]+1+cnt[now],(node){now,angel(now,las)})-arr[now]);
if(arr[now][pos].alpha>=pi) ok[now]=1;
dfs(arr[now][pos].id,now,ed,all);
}
int main()
{
Read(n),Read(A);
for(int i=1;i<=n;i++) Read(x[i]),Read(y[i]);
for(int i=1;i<=n;i++)
{
if(x[i]==A) B[++c]=i;
for(int j=1;j<=n;j++)
if(i!=j)
{
arr[i][++cnt[i]].id=j,arr[i][cnt[i]].alpha=angel(i,j);
if(arr[i][cnt[i]].alpha<pi) arr[i][++cnt[i]].id=j,arr[i][cnt[i]].alpha=arr[i][cnt[i]-1].alpha+pi;
}
sort(arr[i]+1,arr[i]+1+cnt[i]);
}
for(int i=1;i<=c;i++) dfs(B[i],B[i],B[i],0);
for(int i=1;i<=n;i++) if(ok[i]) ans[++ans[0]]=y[i];
sort(ans+1,ans+1+ans[0]);
int las=ans[1]; printf("%d\n",las);
for(int i=2;i<=ans[0];i++) if(ans[i]!=las) printf("%d\n",ans[i]),las=ans[i];
}
坏天平
分析
我们做的是题面5.0的,以下是题面4.0的内容。
题面5.0就直接告诉我们m没有任何用了。
我在考场上做了什么呢?
我做的事情就是算n=1的时候的答案。
题目残酷地告诉我,你辛苦求的
f
[
i
]
f[i]
f[i]居然是
C
i
⌊
i
2
⌋
\large C_{i}^{\lfloor \frac{i}{2}\rfloor}
Ci⌊2i⌋。
先说说我的f[i]是在搞什么。
首先跳过无数错误的尝试。
g ( x , y ) g(x,y) g(x,y)代表左边放x个,右边放y个的方案数,显然 g ( x , y ) = g ( x , y − 1 ) + [ x − 1 > = y ] ∗ g ( x − 1 , y ) g(x,y)=g(x,y-1)+[x-1>=y]*g(x-1,y) g(x,y)=g(x,y−1)+[x−1>=y]∗g(x−1,y)。然后 f [ n ] = ∑ i = 1 ⌊ n + 1 2 ⌋ g ( i , n − i + 1 ) f[n]=\sum _{i=1}^{\lfloor\frac{n+1}{2}\rfloor}g(i,n-i+1) f[n]=∑i=1⌊2n+1⌋g(i,n−i+1)
所以我说这是一个畸形的杨辉三角:
于是我就愉快地得出了 f [ n ] f[n] f[n]的前面几十项。
于是愉快的发现了规律 f [ 2 n ] = 2 × f [ 2 n − 1 ] , f [ 2 n + 1 ] = 2 × f [ 2 n ] − C a t a l a n ( n ) f[2n]=2\times f[2n-1],f[2n+1]=2\times f[2n]-Catalan(n) f[2n]=2×f[2n−1],f[2n+1]=2×f[2n]−Catalan(n)。
我的式子的正确性不浪费时间讨论了,问题是题解给的 C i ⌊ i 2 ⌋ \large C_{i}^{\lfloor \frac{i}{2}\rfloor} Ci⌊2i⌋是怎么来的。
回到—— g ( x , y ) 代 表 左 边 放 x 个 , 右 边 放 y 个 的 方 案 数 , g ( x , y ) = g ( x , y − 1 ) + [ x − 1 > = y ] ∗ g ( x − 1 , y ) g(x,y)代表左边放x个,右边放y个的方案数,g(x,y)=g(x,y-1)+[x-1>=y]*g(x-1,y) g(x,y)代表左边放x个,右边放y个的方案数,g(x,y)=g(x,y−1)+[x−1>=y]∗g(x−1,y)。然后 f [ n ] = ∑ i = n + 1 2 n g ( i , n − i + 1 ) f[n]=\sum _{i=\frac{n+1}{2}}^{n}g(i,n-i+1) f[n]=∑i=2n+1ng(i,n−i+1)——这一步。
假设没有这个左比右大的条件,我们的答案可以在图上表示为(请忽略橙色的线):
就是我们从
(
0
,
0
)
(0,0)
(0,0)出发,每次横坐标或纵坐标
+
1
+1
+1,最后到达
x
+
y
=
n
x+y=n
x+y=n这条线上某点的方案数,到达
(
x
0
,
y
0
)
(x_0,y_0)
(x0,y0)就是这个点的方案数(就是我
C
n
x
0
=
C
n
y
0
C_{n}^{x_0}=C_{n}^{y_0}
Cnx0=Cny0哒,至于为什么?快用杨辉三角。你也可以这么理解,总共2n步,你从中选出n步填上向右/上走),然后把这些点全加起来就能得到
2
n
2^n
2n,这就是每一步从
x
+
1
o
r
y
+
1
x+1 ~or~ y+1
x+1 or y+1中乱选一个的方案数。(这也许可以拿来证明二项式定理?)
那么本题的这个“左>=右”即“x>=y”的条件在图上怎么表现?它就是在说,你在走路的时候,不能越过
y
=
x
y=x
y=x这个蓝线,也就是说,图中的红色是不合法的了,而绿色是合法的。
这是什么,这有卡特兰数内味了。
我们卡特兰数是怎么做的?
我们要从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , n ) (n,n) (n,n),而且走的时候不能超过 y = x y=x y=x这条线,问方案数。
我们知道,不设要求乱走,我们的方案数是 C 2 n n C_{2n}^{n} C2nn这么多,但加上了要求,要减去一些不合法的方案。不合法是什么意思?就是它超出了 y = x y=x y=x,就是它走到了 y = x + 1 y=x+1 y=x+1上。
我们把不合法路径在越过 y = x y=x y=x,来到 y = x + 1 y=x+1 y=x+1之后的路径关于 y = x + 1 y=x+1 y=x+1对称,容易发现,求终点为 ( n , n ) \bold{(n,n)} (n,n),中途越过了 y = x \bold{y=x} y=x的方案数就等于终点为 ( n − 1 , n + 1 ) \bold{(n-1,n+1)} (n−1,n+1),没有限制条件的方案数,即 C 2 n n − 1 = C 2 n n + 1 C_{2n}^{n-1}=C_{2n}^{n+1} C2nn−1=C2nn+1。
所以就得到卡特兰数 C a t a l a n ( n ) = C 2 n n − C 2 n n − 1 Catalan(n)=C_{2n}^{n}-C_{2n}^{n-1} Catalan(n)=C2nn−C2nn−1,后续的化简就省略了。
那么对于本题的这个情况,它和卡特兰数的区别在哪里?
它同样有一条不可跨越的线
y
=
x
y=x
y=x,同样从
(
0
,
0
)
(0,0)
(0,0)出发——对了,终点不同,卡特兰数的终点是
(
n
,
n
)
(n,n)
(n,n),而它的终点则不止一个,
(
n
,
n
)
(n,n)
(n,n),
(
n
+
1
,
n
−
1
)
(n+1,n-1)
(n+1,n−1)……
(
2
n
,
0
)
(2n,0)
(2n,0)。
终点是
(
n
,
n
)
(n,n)
(n,n)的情况的方案数就是
C
a
t
a
l
a
n
(
n
)
=
C
2
n
n
−
C
2
n
n
+
1
Catalan(n)=C_{2n}^{n}-C_{2n}^{n+1}
Catalan(n)=C2nn−C2nn+1,那么
(
n
+
1
,
n
−
1
)
(n+1,n-1)
(n+1,n−1)呢?
如果不加限制,我们的方案数就是在一个长为
2
n
2n
2n的操作序列里面选
n
+
1
/
n
−
1
n+1/n-1
n+1/n−1个格子填上向右走/向左走的方案数,就是
C
2
n
n
+
1
=
C
2
n
n
−
1
C_{2n}^{n+1}=C_{2n}^{n-1}
C2nn+1=C2nn−1。
对于那些超过了线的,我们把它的从第一处超过线的位置开始的部分关于
y
=
x
+
1
y=x+1
y=x+1对称,就得到一条从
(
0
,
0
)
(0,0)
(0,0)自由自在无忧无虑走到
(
n
−
2
,
n
+
2
)
(n-2,n+2)
(n−2,n+2)的路线。总共多少条呢,与上文类似的,
C
2
n
n
−
2
=
C
2
n
n
+
2
C_{2n}^{n-2}=C_{2n}^{n+2}
C2nn−2=C2nn+2?
所以说终点为
(
n
+
1
,
n
−
1
)
(n+1,n-1)
(n+1,n−1)的答案是
C
2
n
n
−
1
−
C
2
n
n
−
2
C_{2n}^{n-1}-C_{2n}^{n-2}
C2nn−1−C2nn−2。
敏锐的读者、数学功底无限好的你们一定发现了顶好的规律,你把这些个终点的答案一加起来就得到了
f
[
2
n
]
=
C
2
n
n
−
C
2
n
0
=
C
2
n
n
f[2n]=C_{2n}^{n}-C_{2n}^0=C_{2n}^{n}
f[2n]=C2nn−C2n0=C2nn。
太棒了!现在我们知道n=1时的答案了,这样我们就得到了——零分!可喜可贺,可喜可贺。
(出题人,你好狠)
不过n>1的思路在我的那张便签里也有体现,本人,虽然智力障碍,但还是勇敢地试图递推!
以上是题解。
数学功底无限差的我还需要要绕一大圈才能明白,跟我一起绕圈圈吧。
它这个递推和常规的递推有些不同,我们(也许只有我)一般是考虑把第n种物品加入,然后由
d
p
n
−
1
dp_{n-1}
dpn−1推出
d
p
n
dp_n
dpn。这道题则是把最轻的砝码拿了出来。
现在我们求
a
n
s
n
ans_n
ansn,我们假设我们没有
m
0
m^0
m0这种砝码,只用
m
1
,
m
2
,
⋯
,
m
n
−
1
m^1,m^2,\cdots,m^{n-1}
m1,m2,⋯,mn−1这
n
−
1
n-1
n−1种砝码能摆出的方案数是多少?
a
n
s
n
−
1
。
ans_{n-1}。
ansn−1。
现在我们把
m
0
m^0
m0插入这些方案中。因为它是最轻的,如果我们把
m
0
m^0
m0放在
m
1
,
m
2
,
⋯
,
m
n
−
1
m^1,m^2,\cdots,m^{n-1}
m1,m2,⋯,mn−1之中,我们可以随便摆,爱咋摆咋摆。所以我们设有
i
i
i个
m
0
m^0
m0摆在了大部队之前,它能贡献的方案数就是
C
i
i
/
2
(
整
除
)
C_{i}^{i/2}(整除)
Cii/2(整除)。那么剩下
k
−
i
k-i
k−i个呢?现在就是一个插板的经典问题了。这篇blog的T1就详细地解释了这个问题。 自己看去。
除去开头的一个,总共 k ∗ ( n − 1 ) = k ∗ n − k k*(n-1)=k*n-k k∗(n−1)=k∗n−k个间隔。你要把 k − i k-i k−i个板子插进去,而且每个间隔可以塞多个板子。
我们可以把这个的方案数看作方程 x 1 + x 2 + ⋯ + x k − i = k ∗ n − k x_1+x_2+\cdots+x_{k-i}=k*n-k x1+x2+⋯+xk−i=k∗n−k的非负整数解的个数。
于是我们为了规避某个 x = 0 x=0 x=0,即某个间隔上出现了多个板子的情况出现,我们把所有 x + + x++ x++,变成 x 1 + x 2 + ⋯ + x k − i = k ∗ n − k + k − i = k ∗ n − i x_1+x_2+\cdots+x_{k-i}=k*n-k+k-i=k*n-i x1+x2+⋯+xk−i=k∗n−k+k−i=k∗n−i的正整数解的个数,在 k ∗ n − i k*n-i k∗n−i个间隔里面选 k − i k-i k−i个出来插板,这是什么啊,这是 C k ∗ n − i k − i C_{k*n-i}^{k-i} Ck∗n−ik−i。
每次插入时,你都可以任选放在左边还是右边,所以再乘上 2 k − i 2^{k-i} 2k−i。
然后枚举
i
i
i。
然后递推推起来。
时间复杂度
Θ
(
n
k
)
\Theta(nk)
Θ(nk)。
我的数学怎么这么差。
代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
const int MAXN=10002030,mod=998244353;
int n,m,k,inv[MAXN],arkcpy[MAXN];
int main()
{
inv[0]=inv[1]=arkcpy[0]=1;
Read(n),Read(m),Read(k);
for(int i=2;i<=k;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=k;i++) arkcpy[i]=1ll*arkcpy[i-1]*i%mod*inv[(i+1)>>1]%mod;
long long ans=arkcpy[k];
for(int i=2;i<=n;i++)
{
long long t=0,c=1;
for(int j=0;j<=k;j++) (t+=1ll*c*arkcpy[k-j])%=mod,(c*=2ll*((i-1)*k+j)%mod*inv[j+1]%mod)%=mod;
(ans*=t)%=mod;
}
cout<<ans<<endl;
}
垃圾分类
这道题真的是垃圾分类,我被分出来了。我是有害垃圾,应当深挖掩埋焚烧隔离。
没想,没写,没打暴力,没看题解。
(其实我看了)