关于北航程设考试题目的一点研究
评价
没想到北航程设题竟然这么有实力,难怪有人会作弊
一种可能是对的做法
不难想到一个贪心,每次都作弊,直到不能作弊时,就停止作弊
然后接下来想到直接二分作弊次数,然后剩下的时间都不作弊,判断就看最终警觉值会不会\(<L\)
仔细一想,因为警觉值应该不能为负数,所以有可能不作弊时减去\(y\)时,\(y\)不能完全减去
这样前面的做法就有问题
再思考,每次减到\(0\)后,接下来就会不断地循环这个过程,我们可以找到循环的长度,多余的部分就可以直接套用前面的做法
剩下的问题就转化成了\(tx\%y\ge L-x\),求最小的\(t\),若\(tx\%y+x\ge L\),这时候必须额外减去一个y来清零,如果不存在这样的\(t\),就可以直接套用前面的做法
令\(p=L-x,x=x\%y,t'=\lfloor\frac{p}{x}\rfloor\)
- 若\(t'x\ge p\),则\(t'\)为最小的\(t\)
- 若\((t'+1)x\%y\ge p\),则\(t'+1\)为最小的\(t\)
- 否则\((t'+1)x\%y=(t'+1)x-y<p\)
设\(dx=(t'+1)x-y\),显然有\(dx<x,y-p<x\)
- 若\(dx=0\),则\(t\)不存在
- 若\(dx\not=0\),那么可以考虑每次在\(t'x\)的基础上每次\(t'x+(t'+1)x\%y\),相当于每次\(+dx\),如果\(+dx\)超过了\(y\),就再加一次\(t'x\),这样问题就转化成了求\(t\cdot dx\%(y-t'x)\ge p-t'x\),最小的\(t\)的问题,递归求解即可,因为\(y-t'x=y\%x\),所以时间复杂度是\(O(\log n)\)的,证明参考辗转相除法
补充
对于为什么最后的转化是对的,我又稍微写了点证明
取\(k\in[t'x,y)\),表示当前的值
- 如果\(k+dx<y\),这时\((t'+1)x\),是一定要加上的,这种情况不会有问题
- 如果\(k+dx\ge y\),\((k+dx)\%y=k+dx-y<dx<x\),所以有\(dx+(t'-1)x<t'x\)
这说明我们在通过加上\((t'+1)x\),并且取模之后,不可能通过加上\(t''x(t''<t')\),使得\(k\)再次\(\in[t'x,y)\),所以取摸后\(t'x\)是一定要加上的
这样就证明了转化是正确的
代码实现
只实现了求\(tx\%y\ge p\),最小值\(t\)的函数,随便拍了一些小数据,应该问题不大
点击查看代码
#include<cstdio>
using namespace std;
int get(int x,int y,int p)
{
for(int t=1;t<=y;t++)
{
int res=t*x%y;
if(res>=p)
return t;
}
return 0;
}
int dfs(int x,int y,int p)
{
if(p>=y) return 0;
x%=y;
if(x==0) return 0;
int t=p/x;
if(t*x>=p) return t;
if(t*x+x<y) return t+1;
int dx=t*x+x-y;
if(dx==0) return 0;
// printf("xyp %d %d %d %d\n",dx,y-t*x,p-t*x,get(dx,y-t*x,p-t*x));
int rest=dfs(dx,y-t*x,p-t*x);
// int rest=get(dx,y-t*x,p-t*x);
if(rest==0) return 0;
return t+rest*(t+1)+(dx*rest/(y-t*x))*t;
}
int main()
{
// printf("%d\n",17*30%99);
for(int i=13;i<=13;i++)
{
for(int j=i+1;j<=500;j++)
for(int k=1;k<=500;k++)
{
// if(get(i,j,k)!=0)
// {
if(get(i,j,k)!=dfs(i,j,k))
printf("ijk %d %d %d get %d dfs %d\n",i,j,k,get(i,j,k),dfs(i,j,k));
// }
}
}
return 0;
//30 99 96 get 23 dfs 17
// printf("%d %d\n",30*23%99,30*29%99);
// return 0;
int x=30,y=99,p=96;
printf("%d %d\n",get(x,y,p),dfs(x,y,p));
return 0;
}