四年前,小白甜;
四年后,腹黑透。
orz NODGD。
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所有可能的值,每行输出一个数。
4 1
1 2
2 1
3 2
2 3
1
3
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
#include
#include
#include
#include
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
#include
#include
#include
#include
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;
}
这道题真的是垃圾分类,我被分出来了。我是有害垃圾,应当深挖掩埋焚烧隔离。
没想,没写,没打暴力,没看题解。
(其实我看了)