前言
隔了3天做的day2……对我而言难度爆炸,最后只有20分也是给跪了……很多人在T3上刷了不少分,我咋就没想到呢?
T1_WYF的盒子
题目大意:给出 k, n, m, p ,其中 90% 的数据满足 k≤2000, n,m,p≤1012 ,另外 10% 的数据满足 k,n,m,p≤1012, n−m≤5000 。求 (∑ni=mik)modp 。
题目描述相当简单,但是如果不懂一些数学知识,要拿分那简直是难上加难。比赛时我只会后 10% 数据的做法,至于剩余的数据我想了一个手推公式的做法,结果前面的数据却只有10分,加起来20分,成了我唯一的分数……
其实我的想法是没错的,只不过因为对基础数学知识的运用不熟练,导致我没想到更好的优化方案。
设
Sumxk=∑xi=1ik
。我们现在要求公式。考虑使用累加法求解:
我们求 ∑xi=1[(i+1)k+1−ik+1] ,可得:
从上式中我们发现:如果 x=n ,通过对上式变形我们就可以求得 Sumnk 。而题目所要求的正是 Sumnk−Summ−1k !
分析求上式的时间复杂度。我们发现其中包含了 Sumxj ,因为 0≤j<k ,这启示我们可以先求 Sumx0 ,再求 Sumx1 ……一直递进求到 Sumxk 。加上求组合数和快速幂的复杂度,总的时间复杂度为 O(k2+klogk) ,可以通过前 90% 的数据。而剩下的 10% 我们直接暴力求快速幂即可。至此本题
真的完美解决了吗?当然不,还有个小问题:给出的 p≤1012 ,如果直接开 long long 存下答案的话,乘法操作会溢出。解决的方法有两种:使用 __int128 类型,它可以存下最大 2127−1 的数;另一种是把其中一个12位乘数拆成两个6位的数,再分别进行乘法操作。见下面程序中的 mul 过程。
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;
typedef long long LL;
const int maxm=2000+10, w=1e6;
LL K,n,m,p,C[2][maxm],f[maxm];
void mul(LL &x,LL y){
LL rx=y%w*x, ry=y/w*x;
x=(ry%p*w+rx)%p;
}
LL Pow(LL a,LL b){
LL ret=1;
while (b){
if (b&1) mul(ret,a);
mul(a,a);
b>>=1;
}
return ret;
}
LL calc(LL n){
if (!n) return 0;
memset(C,0,sizeof(C));
C[0][0]=1, C[0][1]=1;
int z=0;
fo(k,1,K){
z=1-z; C[z][0]=1;
f[k]=n;
fo(i,1,k+1){
C[z][i]=(C[1-z][i-1]+C[1-z][i])%p;
if (i<k){
LL t=C[z][i];
mul(t,f[i]);
f[k]=(f[k]+t)%p;
}
}
f[k]=(Pow(n+1,k+1)-1+p-f[k])%p;
mul(f[k],Pow(k+1,p-2));
}
return f[K];
}
int main(){
freopen("box.in","r",stdin);
freopen("box.out","w",stdout);
scanf("%I64d%I64d%I64d%I64d",&K,&n,&m,&p);
if (n-m<=5050){
LL ans=0;
while (m<=n) ans=(ans+Pow(m,K))%p, ++m;
printf("%I64d\n",ans);
}
else printf("%I64d\n",(calc(n)+p-calc(m-1))%p);
return 0;
}
T2_互相追逐的点
题目大意:有 n 个互相追逐的点 (
1≤n≤20000
),每个点
i
都有它自己的目标点
当点
i
距离它的目标点只有 1 单位距离、或者它与目标点的距离在 1 单位时间内保持不变时,它会跟随它的目标点向同一方向移动(即它的移动方向由它的目标点决定)。我们称一个点“不知道自己的移动方向”当且仅当它的移动方向由它自己决定(注意如果一个点的移动方向决定于一个“不知道自己的移动方向”的点的移动方向,那么它不能算是“不知道自己的移动方向”)。一个“不知道自己的移动方向”的点会停下不动。
现在询问的是:在过了无限长的时间后,任意一组“不知道各自的移动方向”的点。输出任意一组且不要求输出顺序。
看到这样的题目时我是震惊的——题目好长!(上文是题目大意,原题有1000+字数)稍微一想,如果要模拟每个点的移动,几乎是不可能的,因为一个点会向它的目标点移动,目标点又会向它的目标点移动……这种嵌套的移动模拟将会非常困难。因此我放弃了此题。
殊不知这题其实很简单,因为你可以忽略题目中间“描述每个点的移动方式”这一段。这是因为题目要求的东西的前提是过了无限长的时间后,如果所有点在很长一段时间内仍然随着各自的目标点移动,那么它们将慢慢聚拢在一起,最终一定会全部停下。所以如果从每个点向它的目标点连一条有向边,那么就构成了一个图,而且一定有至少一个环。从任一点出发寻找环即可。时间复杂度
Code
#include<cstdio>
#include<iostream>
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;
const int maxn=200000+10;
int n,a[maxn],b[maxn],q[maxn];
int main(){
freopen("head.in","r",stdin);
freopen("head.out","w",stdout);
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]);
int x=1, cnt=0;
while (!b[x]){
b[x]=++cnt;
q[cnt]=x;
x=a[x];
}
printf("%d\n",cnt-b[x]+1);
fo(i,b[x],cnt) printf("%d ",q[i]);
printf("\n");
return 0;
}
T3_恐怖的奴隶主
题目大意:在一个只能容纳 k 个人的场上最开始有 1 个血量为 m 的人。接下来将进行 n 个回合的操作:每个回合中设场上有 c 个人,那么场上新增最多 c 个血量为 m 的人,并保证总人数在 k 人的上限内。然后先前的 c 个人每人血量减 1,如果血量为 0 则被清除出场。问 n 个回合后场上的人数。 1≤n,m,k≤1015 。
这道题的数据范围如此之大,很容易让人想到——场上人数以及血量状态有循环节!于是我们尝试模拟操作,用 A[i] 表示第 i <script type="math/tex" id="MathJax-Element-76">i</script> 回合新增的人数,观察之后能发现规律。由于规律难以解释,先贴程序,日后再作解释。
Code
#include<cstdio>
#include<iostream>
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;
typedef long long LL;
LL n,m,K,a[100];
int main(){
freopen("horrible.in","r",stdin);
freopen("horrible.out","w",stdout);
scanf("%I64d%I64d%I64d",&n,&m,&K);
if (m==1){ printf("0\n"); return 0; }
++n;
LL cnt=1, S=1, nxt; a[1]=1;
while (cnt<=n){
nxt=S;
if (S+nxt>=K) break;
if (cnt>=m) S-=a[cnt-m+1];
a[++cnt]=nxt; S+=nxt;
}
//if (cnt==n){ printf("%I64d\n",S); return 0; }
n=(n-cnt)%(m+1);
if (cnt>=m){
fo(i,1,n){
nxt=S; S+=S;
if (S>K) S=K;
S-=a[(cnt++)-m+1];
}
printf("%I64d\n",S);
}
else if (!n) printf("%I64d\n",S);
else if (n<=m-cnt) printf("%I64d\n",K);
else printf("%I64d\n",K-a[n-m+cnt]);
return 0;
}
总结
这道题对我而言是极好的(因为比赛得分低的离谱),尤其是T3我到现在也不能作出详细的解释,只有个大概的了解。看来数学的基础我仍然没有掌握透彻啊……正在苦读《训练指南》补习中……