| by XianKa | No comments

【斜率优化】cf311b Cats Transport

题目地址

题意是说有n座山,每座山之间距离不同为di,p个饲养员,m只猫。每只猫会在Hi座山上,在Ti时间等待饲养。饲养员只能从1走到n不能停留,问怎么规划饲养员的出发时间才能使得等待时间最少。饲养员的出发时间可以为负。

规定 sum 代表前缀和。

对于某只猫 i 来说。如果饲养员在时间 t 出发,他要走 sumD[i] 的距离,那么这只猫的等待时间 W = t + sumD[i] – T[i] 。由此如果设 A[i] = T[i] – sumD[i] 。那么饲养员从时间 t 出发每只猫 i 的等待时间就是 t – A[i]

把 A 从小到大排序,由于 t – A[i] 不能是负数,所以每个饲养员一定从最开始往后带走连续的若干只猫而且排序的数组中最后一只一定等待时间为 0 。

设 f[ i , j ] 为前 i 个饲养员带走了前 j 只猫。那么第 i 个饲养员肯定从 A[j] 时间出发,带走的猫等待时间为\sum_{p=k+1}^{j}A_j-A{p}=A_j \times(j-k)-sumA[j]+sumA[k]设其为C

那么f[i][j] = min(f[i – 1][k] + C);

i j k 三层循环,把 i 看作不变量,对 j k 做斜率优化。同时由于每次只用到了i – 1的状态,所以可以把 … Read the rest

Read More
| by XianKa | No comments

【斜率优化】牛客练习赛40D 小A与最大子段和

题目传送门
官方题解
题意是说找到一个连续子段,使得\sum_{i}^{j}a_i \times i最大,也就是说每个数要乘上自己在子段里的位置。

官方题解就是说,令f[i]表示前i个数字的答案。f_{i}=\max_{j=0}^{i-1} (\sum_{k=j+1}^{i}a_k \times {(k-j)})这样每个a就可以和自己对应的坐标相乘,而j又是固定的,所以可以直接用前缀和把题目简化掉。

要截距最大化,斜率不单调,维护整个上突壳,在上突壳上二分找左边斜率小于当前,右边斜率大于当前的点。注意答案的初始化要为负数,因为答案可能为负。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+233;
ll a[maxn],b[maxn],f[maxn];
int q[maxn];
int l=1,r=1;
int n;
int brf(int k)
{
    if(l == r) return q[l];
    int L=l,R=r;
    while(L<R)
    {
        int mid=(L+R)>>1;
        if(b[q[mid + 1]] * q[mid + 1] - a[q[mid + 1]] - b[q[mid]] * q[mid] + a[q[mid]] 
            >= b[k] * (q[mid + 1] - q[mid])) L = mid + 1;
        else R 
Read the rest Read More
| by XianKa | No comments

蓝桥杯 算法提高 周期字串

题目地址

题意就是让求整个字符串最小循环节的长度。字符串长度小于100。
可以直接上kmp。就算长度一千万也可以搞。
当i-nxt[i]能整除i的时候,i-nxt[i]就是最小循环节的长度

#include<bits/stdc++.h>
using namespace std;
char a[111];
int nxt[111];
int n;
void kmp()
{
    nxt[1]=0;
    for(int i=2,j=0;i<=n;i++)
    {
        while(j>0 && a[i]!=a[j+1]) j=nxt[j];
        if(a[i]==a[j+1]) j++;
        nxt[i]=j;
    }
}
int main()
{
    scanf("%s",a+1);
    n=strlen(a+1);
    kmp();
    if(n%(n-nxt[n])==0) printf("%d",n-nxt[n]);
    else printf("%d",n);
}
Read the rest Read More
| by XianKa | No comments

蓝桥杯题库解题报告

蓝桥杯历届试题做题记录

解题报告 与 题解

标签(空格分隔): 做题记录


鉴于网上蓝桥杯的大多数题目几乎找不到很好的题解,甚至出现了很多错误的题解。所以抽时间做了一份结题报告。
按逆序编号,和蓝桥杯练习网站上一样。有一些题在本弱博客上写过就直接贴地址了。
本报告所有题目已经由本弱亲自提交并且AC,但不排除网站的测试数据会被增强,以前的AC思路不一定会再次适用,建议对解题思路有疑问的先将AC代码粘上去看结果。


[TOC]


PREV-55 小计算器

题解

模拟

具体题解见博客

可以先把所有进制转为十进制,方便计算,要输出的时候再转成要求的进制

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll,char> eco;
map<char,ll> deco;
int k=10;
ll ans=0;
char num[99];
void init()
{
    for(ll i=0;i<=9;i++)
    {
        eco[i]=i+'0';
        deco[i+'0']=i;
    }
    for(ll i='A';i<='Z';i++)
    {
        eco[i-'A'+10]=i;
        deco[i]=i-'A'+10;
    }
}
ll getnum()
{
    ll tmp=1;
    ll ret=0;
    for(int i=strlen(num)-1;i>=0;i--)
    {
        ret+=tmp*deco[num[i]];
        tmp*=k;
    }
    return ret;
}
void pout(ll tnum)
{
    //cout<<tnum<<endl;
    if(tnum==0)
    {
        printf("0\n");
        return;
    
Read the rest Read More
| by XianKa | No comments

蓝桥杯历届试题 区间移位

http://lx.lanqiao.cn/problem.page?gpid=T451

题意是给定n<=10000个区间,保证总长度大于等于10000,有重叠,让求最大移动的最小值是多少。

首先这个问法就很二分,但是check的方法不好想。

wjjj随便猜了个结论就猜对了(%%%)。二分后按照b排序,优先选b小的。

因为二分出mid后,对于任意一个区间,只能在[a-mid,b+mid]里移动。所以最开始假设没有覆盖,当前点k=0,从左到右选出可以覆盖的区间,尽量往右放进行覆盖,覆盖完以后k移动到本次覆盖区间的最右边。在多个区间都可以覆盖当前点k的时候,b小的肯定在向右移动的时候不是最优解,因为它无论如何都比比它b大的移动得多。最后如果找不到方案可以覆盖完整个区间就说明答案太小,否则答案可能太大。至于小数,可以证明只会有0.5出现,因为最开始的区间都是整数,如果有一个区间移动了1/3那么必然有一个区间需要移动2/3。肯定不是最优解。既然只有0.5那直接把区间长度扩大一倍,规定只能移动整数距离,最后答案half就可以了。

在查找b的时候不能暴力找,否则会炸掉,可以用二分来找,但是注意标记已经用过的区间,我直接用了multiset方便删除用过的区间,复杂度都是logN。总复杂度Nlog²N

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const double eps=1e-12;
const int N=20000;
vector<pii> ab;
int vis[10001];
multiset<pii> c2;
int line[10001]; 
int main()
{
    int n,a,b;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a,&b);
        a*=2;b*=2;
        c2.insert({b,a});
    }
    int l=0,r=N;
    while(r>l)
    {
        multiset<pii> c1(c2);
        int mid=(l+r)>>1;
        int now=0;
        while(1)
        {
            int flag=0;
            multiset<pii>::iterator it;
            for(it=c1.begin();it!=c1.end();it++)
            {
                pii tmp=*it;
                int tta=tmp.second,ttb=tmp.first;
                if(tta-mid <= now && ttb+mid >= now)
                {
                    flag=1;
                    int len=ttb-tta;
                    if(now<=tta+mid) 
Read the rest Read More
| by XianKa | No comments

【数论】CF1110C

http://codeforces.com/problemset/problem/1110/C

题意是给一个a让求一个0<b<a使得gcd(a ^ b , a & b) 要最大

  • 首先注意到a^b和a&b的每一位都是相反的,所以说很容易想到构造一个b使得b&a=0,这样答案就是a^b。构造方法就是把a的每一位1变成0,0变成1。
  • 对于a的每一位全是1的情况需要单独讨论。
  • 如果a的每一位都是1,任取一个b,可以得到a^b=a-b , a&b=b 这个结论。那么问题就变成了求gcd(a-b,b),也就是gcd(a,b)_max。
  • 对于求最大的gcd,把a分解质因数,除掉其中一个最小的质因子,就是答案。
  • 由于本题时间不紧,而且cf测评机都是香港记者,所以可以直接根号复杂度暴力分解。当然也可以预处理质数表
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int q,a;
int main()
{
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d",&a);
        //a=q;
        int res=0;
        for(int i=0;(1<<i)+res < a;i++)
        {
            if(!((1<<i)&a) ) 
                res+=(1<<i); 
            //cout<<res<<'-';
        }
        if(res) printf("%d\n",res^a);
        else
        {
            bool flag=true;
            for(int i=2;i*i<=a;i++) 
            {
                if(a%i==0)
                {
                    flag=false;
                    printf("%d\n",a/i);
                    break;
                }
            }
            if(flag) printf("1\n");
        }
    }
}
Read the rest Read More
| by XianKa | No comments

牛客19寒假R2I

https://ac.nowcoder.com/acm/contest/327/I

这题有点坑的就是,这题其实和本场比赛上一题一点关系都没有……

直接分解质因子以后暴力n²枚举就可以,下面来分析一下这么做的时间复杂度:

  • 对于每个数分解质因子,sqrt(n)的复杂度,打素数表可以更快。2000*20=40000
  • 对于每个数枚举其他数,由于分解的时候质因子是有序的,所以对两个数可以归并排序的方法线性合并质因子。
  • 小知识点:对于1e9以内的数,一般不同的质因子个数不超过10个
  • 所以对于合并的复杂度,不会超过o(20),2000*2000*20=80000000
  • 总复杂度小于1e8,且常数极小,可以接受
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
int n,ans=0;
vector<pii> a[2002];
int main()
{
    scanf("%d",&n);
    for(int j=1;j<=n;j++)
    {
        int k;
        scanf("%d",&k);
        for(int i=2;i*i<=k;i++)
        {
            int res=0;
            if(k%i==0)
            {
                while(k%i==0)
                {
                    k/=i;
                    res++;
                }
                a[j].push_back(make_pair(i,res));
            }  
        }
        if(k>1) a[j].push_back(make_pair(k,1));
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            int l=0,r=0,tmp=1;
            auto &aa=a[i];
            auto &bb=a[j];
            while(l<aa.size() && r<bb.size())
            {
                if(aa[l].first<bb[r].first) tmp*=aa[l++].second+1;
                else if(aa[l].first>bb[r].first) tmp*=bb[r++].second+1;
                else tmp*=aa[l++].second+bb[r++].second+1;
            }
            while(l<aa.size()) tmp*=aa[l++].second+1;
            while(r<bb.size()) tmp*=bb[r++].second+1;
            if(tmp<=10) ans++;
        }
    }
    printf("%d\n",ans);
Read the rest Read More
| by XianKa | No comments

【数论】欧拉降幂,费马小定理求逆元,阶乘逆元

今天在群里看见一道题目t1

  • 首先这题模数是一个大质数,所以考虑求分母的逆元
  • 由于只需要算一次,没有变量也没有多次询问,所以直接暴力求20192019!%p的结果,然后用费马小定理求出其逆元即可
  • 对于分母,也可以用欧拉定理来降幂,由于p是个质数,所以p的欧拉函数值就是p-1,可以直接放上分子,然后对分子进行快速幂

SharedImage

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll mod=1e9+7;
ll qsm(ll n,ll k,ll md)
{
    ll res=1;
    while(k)
    {
        if(k&1) res=(res*n)%md;
        n=(n*n)%md;
        k>>=1;
    }
    return res%md;
}
int main()
{
    ll fac=1;
    for(int i=1;i<=20192019;i++) fac=(fac*i)%mod;
    ll inv=qsm(fac,mod-2,mod);
    ll son=qsm(2019,qsm(2019,2019,mod-1),mod);
    cout<<inv*son%mod<<endl;
}

 

  • 补充:
  • 对于阶乘的逆元,可以[latex]O(N)[/latex]预处理出其逆元。
  • 假设用费马小定理求出了[latex]q![/latex]的逆元[latex](q!)^{-1}[/latex]。那么就有
  • [latex](q!) * (q!)^{-1} \equiv 1[/latex]
  • [latex](q-1)! * q * (q!)^{-1} \equiv 1[/latex]
  • 从上式可以看出[latex](q-1)![/latex]的逆元就是[latex]q * q!^{-1}[/latex]
  • fact[0]=1;
    for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
    invfact[n]=qsm(n,mod-2);
    for(int i=n;i>=1;i--) invfact[i-1]=invfact[i]*i%mod;

     

 … Read the rest

Read More
| by XianKa | No comments

【组合|卡特兰数】牛客19寒假R1H

链接:https://ac.nowcoder.com/acm/contest/317/H
来源:牛客网


本题考查对卡特兰数的理解。突破点就是2n天恰好写n天作业。

先把问题抽象成:任意位置,不写为-1,写了为+1,要求在任意位置,前缀和大于-k。

当K=1的时候。结果就是卡特兰数。katelan1

如图,卡特兰数的几何理解。假设写了为往x轴+1,没写为y+1,任何时刻写了要大于等于没写,所以任意合法路径不能够超过y=x+1这条线。

对于任意不合法路径,将其从第一个不合法的地方开始,把剩余图像关于y=x+1对称。那么要达到的(n,n)就变成了(n-1,n+1)。所以对于任意不合法路径,可以看作一条到(n-1,n+1)的路径。

合法路径的条数就是到(n,n)的条数减去到(n-1,n+1)的条数。也就是C(2n,n)-C(2n,n-1)  (因为有一次横着走变成了竖着走,所以是n-1)

本题对于卡特兰数的几何意义作了推广,把y=x+1变成了y=x+k,所以答案就是C(2n,n)-C(2n,n-k)

这里还有一个考点就是组合数取模,有很多做法,由于P不一定是质数,不一定有逆元,可以套线性筛模板,把组合的质因子和幂处理出来,再快速幂。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e6+233;
int primes[N], cnt;
ll st[N];
ll sum[N];
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; j < cnt && i * primes[j] <= n; j ++ )
        {
            st[primes[j] * i] = primes[j];
            if (i % primes[j] 
Read the rest Read More
| by XianKa | No comments

【数论】牛客19寒假R1D

链接:https://ac.nowcoder.com/acm/contest/317/D
来源:牛客网


数论题

  • 1.1.首先[latex]x+y=n[/latex]在任何时刻都恒成立。所以如果[latex](x,n)=1[/latex]那么[latex](y,n)=1[/latex]也必定成立。
  • 1.2.关于gcd(),记作(),支持几种操作:[latex](n-x,n)=(-x,n)=(x,n)[/latex]也就是说,一边加到另一边,一边取反,gcd的还是不变的。可以由同余的性质证明。
  • 2.1.每次符合条件k的指数就会增加,所以问题就变成了1-n里面和n互质的数,的和。
  • 2.2.欧拉定理,即费马小定理的推广,介绍了欧拉函数phi(x)表示小于x的数里面,与x互质的数,的个数。有1.1得到的结论,如果x和n互质,那么n-x也和n互质。两数之和等于n。所以两两配个对。所有数之和 [latex]sum(x)=n*phi(n) \div 2[/latex]
  • 2.3.关于n或phi(n)的奇偶,如果phi(n)是奇数,那么n/2就会和n互质。但由于n/2还是n的一个约数。所以n只能为2。所以phi(n)为奇数当且仅当n==2,此时也可以整除。同时证明了当n不等于2的时候phi(n)一定为偶数。
  • 3.1.最后快速幂处理答案。注意模
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000000+7;
ll n,k,a,b;
ll ksm(ll n,ll m)
{
    ll res=1;
    while(m)
    {
        if(m&1) res=res*n %mod;
        n=n*n %mod;
        m>>=1;
    }
    return res %mod;
}
int main()
{
    cin>>n>>k>>a>>b;
    ll euler=n,bn=n;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0) euler=euler/i*(i-1);
        for(;n%i==0;n/=i);
    }
    if(n!=1) euler=euler/n*(n-1);
    ll p=euler/2*bn;
    ll num=a%mod +b%mod;
    cout<<num*ksm(k,p)%mod<<endl;
}

 

Read the rest
Read More