评价
没想到北航程设题竟然这么有实力,难怪会作弊
一种可能是对的做法
不难想到一个贪心,每次都作弊,直到不能作弊时,就停止作弊
然后接下来想到直接二分作弊次数,然后剩下的时间都不作弊,判断就看最终警觉值会不会\(<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_1x)\ge p-t_1x\),最小的\(t\)的问题,递归求解即可,因为\(y-t_1x=y\%x\),所以时间复杂度是\(O(\log n)\)的,证明参考辗转相除法
代码实现
只实现了求\(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;
}