Codeforces #278

本文深入探讨了算法设计与数据结构应用的核心概念,包括动态规划、哈希算法、贪心算法等,并通过实例展示了如何高效解决问题。同时,文章还涵盖了数据安全、版本控制、项目管理等现代软件开发领域的关键实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}


E. 给n,求一个n的全排列A使得A1*A2*...*A_i%n(1<=i<=n)这n个数为0到n-1的一个全排列。

只有质数和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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值