D. 给一个数组,求最少把这个数组分成多少部分,要求每一部分都大于等于l,且最大值和最小值的差不大于s。
显然是dp的思想,大概就是dp[i]=min{dp[k]}+1(j<=k<=i-l),j表示最远从哪个数开始到第i个数满足j到i中最大值和最小值的差不大于s,这个j可以用单调队列解决,开两个单调队列,一个维护区间最大一个维护区间最小。知道j之后,就要从j到i-l之间找到一个dp最小的值,用线段树可以搞。
代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define N 100005
#define INF 99999999
struct Tree
{
int l,r,val;
};
Tree tree[N*5];
int incQueue[N],incHead,incTail;
int decQueue[N],decHead,decTail;
int dpQueue[N],dpHead,dpTail;
int a[100005];
int dp[100005];
void build(int t,int l,int r)
{
tree[t].l=l;
tree[t].r=r;
tree[t].val=INF;
if (l==r) return;
int mid=(l+r)/2;
build(2*t+1,l,mid);
build(2*t+2,mid+1,r);
}
void add(int t,int idx,int val)
{
if (tree[t].l==tree[t].r)
{
tree[t].val=val;
return;
}
int mid=(tree[t].l+tree[t].r)/2;
if (idx<=mid) add(2*t+1,idx,val);
else add(2*t+2,idx,val);
tree[t].val=min(tree[2*t+1].val,tree[2*t+2].val);
}
int query(int t,int l,int r)
{
if (tree[t].l==l && tree[t].r==r)
{
return tree[t].val;
}
int mid=(tree[t].l+tree[t].r)/2;
int ret=INF;
if (l<=mid) ret=min(ret,query(2*t+1,l,min(mid,r)));
if (r>mid) ret=min(ret,query(2*t+2,max(l,mid+1),r));
return ret;
}
void pushInc(int idx)
{
while(1)
{
if (incTail==incHead) break;
if (a[incQueue[incTail-1]]<=a[idx]) break;
incTail--;
}
incQueue[incTail++]=idx;
}
void pushDec(int idx)
{
while(1)
{
if (decTail==decHead) break;
if (a[decQueue[decTail-1]]>=a[idx]) break;
decTail--;
}
decQueue[decTail++]=idx;
}
int main()
{
int i,j,n,s,l;
scanf("%d%d%d",&n,&s,&l);
for (i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
incHead=incTail=decHead=decTail=dpHead=dpTail=0;
build(0,0,n-1);
int far=0;
for (i=0;i<n;i++)
{
pushInc(i);
pushDec(i);
while(1)
{
if (a[i]-a[incQueue[incHead]]>s)
{
far=max(far,incQueue[incHead]+1);
incHead++;
}
else break;
}
while(1)
{
if (a[decQueue[decHead]]-a[i]>s)
{
far=max(far,decQueue[decHead]+1);
decHead++;
}
else break;
}
if (i-far+1<l) dp[i]=-1;
else
{
// printf("%d:%d!\n",i,far);
if (far==0)
{
dp[i]=1;
add(0,i,dp[i]);
}
else
{
int tmp=query(0,far-1,i-l);
// printf("%d: %d~%d %d\n",i,far-1,i-l,tmp);
if (tmp==INF) dp[i]=-1;
else
{
dp[i]=tmp+1;
add(0,i,dp[i]);
}
}
}
}
// for (i=0;i<n;i++) printf("%d ",query(0,i,i));
// printf("\n");
if (dp[n-1]==INF) printf("-1\n");
else printf("%d\n",dp[n-1]);
return 0;
}
只有质数和1和4有解,因为对于合数N可以有N=p1*p2,而p1和p2可以分别从小于N的数当中凑得,因此无论怎么排都会有大于1个取模的结果为0。
4有解是因为4=2*2,而1~3当中只有一个2因子,所以可能会存在解,事实证明也是有解的(1, 3, 2, 4)
因为想要让这个全排列的前i个数相乘模n的结果都不一样,因此n要放在最后一位(不然乘以n之后无论怎么乘 模都是0),1要放在第一位(乘以1之后模不变)。
之后当然看看可不可以构造一个序列使得:
A1%n=1
A1*A2%n=2
A1*A2*A3%n=3
...
A1*A2*...*A_(n-1)%n=n-1
A1*A2*...*An%n=0
且A1=1, An=n。
如果这种构造方法成立,就要证明不存在某个数p(1<p<N),使得ip%n=i+1且jp%n=j+1,根据这两个式子可得:
(i+1+xn)/i=(j+1+yn)/j =>
j+xnj=i+yni =>
i-j=n(xj-yi)
因为i和j都在0和n之间,因此i-j会小于n,但是右边xj-yi是整数,所以唯一满足上述式子的情况就是左边等于右边等于0,即i==j。
所以就证明了不存在某个数p,使得ip%n=i+1且jp%n=j+1。
也就是说这种构造方法是满足A1到An互不相等的。
接下来需要考虑怎么快速的得到p,使得(i-1)*p mod n = i。
考虑费马小定理:a^(n-1) mod n = 1
因此,我们可以让p等于(i-1)^(n-2) * i
这样(i-1)*p= (i-1) * (i-1)^(n-2) * i = (i-1)^(n-1)*i
这样(i-1)^(n-1) mod n=1,所以(i-1)^(n-1)*i mod n = i,即(i-1) * (i-1)^(n-2)*i mod n = i
所以每次只需要快速幂求出这个p,打印输出即可。
代码很简单
#include <stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
bool is_prime(int t)
{
for (int i=2;i<=sqrt(t*1.0);i++)
{
if (t%i==0) return false;
}
return true;
}
long long f_pow(long long x,int n,int mod)
{
long long ret=1;
while(n)
{
if (n&1) ret=ret*x%mod;
n>>=1;
x=x*x%mod;
}
return ret;
}
int main()
{
int i,j,n;
scanf("%d",&n);
if (!is_prime(n) && n!=1 && n!=4)
{
printf("NO\n");
return 0;
}
printf("YES\n");
if (n==1)
{
printf("1\n");
return 0;
}
if (n==4)
{
printf("1\n3\n2\n4\n");
return 0;
}
printf("1\n");
for (i=2;i<n;i++)
{
printf("%d\n",f_pow(i-1,n-2,n)*i%n);
}
printf("%d\n",n);
return 0;
}