思维数论&贪心专题

序言(~v~)

本蒟蒻由于太弱只能开数论坑了qwq。

本专题可能不会涉及那么多比较难的数论问题qwq

一般都是简单数学(主要是思维)还有简单贪心(毕竟我太菜了)qwq

【14/20】

(小声咕咕:都是luogu的题)

[1/20](数论)luogu【p2671】求和(NOIP2015pjT3)

    挺好的数论推导题。首先看着数据范围就是O(N)解(qwq)但是我蒟蒻啊推了一天

要注意的是,对于”xyz 是整数, x<y<z,y-x=z-y,x<y<z;yx=zy“这个条件,我们会发现x+z=2y

x+z一定为偶数---------x于z要么同奇要么同偶

对于每一种颜色,我们可以得到O(N^2)解法。(当然有很多n^2的解法qwq)

对于第i个元素,我们假设【c为其颜色,p为其位置,n为其数字】,每遇到一次就一次累加:

color[c]=(pi+pj)*(ni+nj)            (1<=j<=k,其中k表示迄今为止,该颜色奇/偶的元素的个数(不包含当前i元素))

展开来,得到color[c]=pi*ni+pi*nj+pj*ni+pj*nj     一共k次累加

我们可以发现答案可以表示成为      pi*ni*k+pi*(n1+n2+...+nk)+(p1+p2+...+pk)*ni+(p1*n1+p2*n2+...+pk*pk)

很高兴!我们可以用一个数组记录每种颜色的(n1+n2+...+nk),(p1+p2+...+pk),以及(p1*n1+p2*n2+...+pk*pk)

出现了!O(N)的效率!

【特别声明:一定要分奇偶!!!】

2018-08-01

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=100010;
ll a,b,c,d,ans=0,m=10007;
ll number[orzhjw]={0},color[orzhjw]={0};
ll sumn1[orzhjw]={0},sump1[orzhjw]={0},howmany1[orzhjw]={0},sumq1[orzhjw]={0};
ll sumn2[orzhjw]={0},sump2[orzhjw]={0},howmany2[orzhjw]={0},sumq2[orzhjw]={0};
inline ll get(ll &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%d ",x);
}
int main(){
    get(a);get(b);
    for(ll i=1;i<=a;i++)get(number[i]);
    for(ll i=1;i<=a;i++){
        ll x;get(x);
        if(i&1){
            color[x]+=howmany1[x]*i*number[i]%m+i*sumn1[x]%m+number[i]*sump1[x]%m+sumq1[x]%m;
            sumn1[x]+=number[i];sump1[x]+=i;
            howmany1[x]++;sumq1[x]+=number[i]*i;
        }
        else{
            color[x]+=howmany2[x]*i*number[i]%m+i*sumn2[x]%m+number[i]*sump2[x]%m+sumq2[x]%m;
            sumn2[x]+=number[i];sump2[x]+=i;
            howmany2[x]++;sumq2[x]+=number[i]*i;
        }
        color[x]%=m;
    }
    for(ll i=1;i<=b;i++){
        //put(color[i]);
        ans=(ans+color[i])%m;
    }
    put(ans);turn;
}
//872
View Code

 

[2/20](数论)luogu【P2119】魔法阵(NOIP2016pjT4)

蒟蒻我又来搬题解了。。。。

80%

首先我们看一下数据:

你会发现n*n*n可以过80分。。。。

(有好吃的为什么不吃呢?)

显然,对于一个xa,那这个xa可以和其他x组成的魔法阵个数为nb*nc*nd(乘法原理)(xb,xc,xd同理)。

于是又根据小学生思维,对于一个四元等式,确定3个可以得到第4个。所以我们可以把所有n弄出来,然后排序,去重。

由于已经排序而且去重,所以可以直接从前面枚举xb,xc,xd然后判断xa存不存在以及是否满足要求。

100%

100分怎么办?

我们设xd-xc=k,xc-xb=p,xb-xa=m;

由于我们可以从条件里得到

xb-xa=2(xd-xc),所以m=2*k,那就意味着有k就有m。

以及3*2(xd-xc)<xc-xb,所以可以把m表示成6*k<p.

整理一下,我们有:

xb=xa+2*k;

xc=xd-k;

xb+6*k<xc;

在k确定的情况下:

1.对于一个确定的xd,可以得到xc,以及xb的最大值xb=xc-6*k-1(即xd-7*k-1),有了xb的最大值也有了xa的最大值xa=xd-9*k-1;

2.对于一个确定的xa,可以得到xb,以及xc的最小值xc=xb+6*k+1(即xa+8*k+1),有了xc的最大值也有了xd的最大值xd=xc+k;

好了记住它们。(qwq)

 现在我们回到那个80分的暴力,我们可以发现当我们在枚举xb的时候,存在的xb的数值的范围应该是(2,xd-7*k-1);

然后第一次是f(2,3),第二次是f(2,4),第三次是f(2,5)........最后一次是f(2,xd-7*k-1);

可以观察到每一次都重复查找了上一次的个数!!!

由于k>=1,所以c的最小值为xa+8*k+1=1+8+1=10;

那当我们枚举xc的时候,你会发现xc最小时所求出来的xb也是最小值(xb=xc-6*k-1=10-6-1=3),当xc最大时xb也是最大。

又因为xc是等差递增,所以xb也是等差递增,而且效果和暴力差不多。

所以我们可以用前缀和优化一下,用一个sum记录前面一共有多少不同的合法的xa和xb的情况,根据乘法原理,可以得到C[xc]=D[xd]*sum,D[xd]=C[xc]*sum;

而对于A,B的答案更新,同样可以求出范围,然后根据乘法原理累加一波。

(注意sum清0)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=40010;
ll a,b,c,d,num=0;
ll whh[orzhjw]={0},old[orzhjw]={0};
ll A[orzhjw]={0},B[orzhjw]={0},C[orzhjw]={0},D[orzhjw]={0};
int what[orzhjw]={0};
ll lyf[orzhjw]={0};
inline ll get(ll &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);
}
int main(){
    get(b);get(a);
    for(int i=1;i<=a;i++){
        get(old[i]);what[i]=old[i];lyf[old[i]]++;
    }
    for(int k=1;k*9<=b;k++){
        ll summ=0;
        for(int xd=9*k+2;xd<=b;xd++){
            ll xc=xd-k,xa,xb;
            xa=xd-9*k-1;
            xb=xa+k+k;
            summ+=lyf[xa]*lyf[xb];
            C[xc]+=lyf[xd]*summ;
            D[xd]+=lyf[xc]*summ;
        }
        summ=0;
        for(int xa=b-9*k-1;xa>=1;xa--){
            ll xb=xa+k+k,xc,xd;
            xc=xb+6*k+1;
            xd=xb+7*k+1;
            summ+=lyf[xc]*lyf[xd];
            A[xa]+=lyf[xb]*summ;
            B[xb]+=lyf[xa]*summ;
        }
    }
    
    for(int i=1;i<=a;i++){
        int x=what[i];
        put(A[x]);put(B[x]);put(C[x]);put(D[x]);turn;
    }
    return 0;
}
View Code

 【3/20】(贪心)luogu【P2672】推销员

60%

可以DP一波:

DP[i][j]表示前i个以i结尾选择j个的最大值,它的答案等于i前面的j个点权的最大和,我们用f[i][j]表示;

那么

DP[i][j]=f[i-1][j-1]+a[i]+2*s[i];

f[i][j]=max(f[i-1][j],f[i-1][j-1]+a[i]);

那么——很愉快地TLE了qwq

100%

 我们会发现无论怎样,当前答案都是之前的答案加一个点。根据DP的思想,当前的答案可以是上一状态加上一个最大的值,使得答案最大。

然后我们需要分类讨论:

  • 我们假设新加的点为第x个点
  • 当x在当前答案的末尾(即被选答案的距离最大)左边,那么答案就加上a[i]
  • 当x在当前答案的末尾右边,那么答案就 ans=ans-s[tail]+a[x]+s[i]+s[i](减去之前的最大距离  再加上  新加的权值)
  • 因为tail和新加的x无关,所以我们可以找出最大的a[i](s[i]<s[tail])以及最大的
  • 然后分情况比较,标记,更新。
  • 那么——可以用一个堆维护左边的答案,用一个有序序列来维护右边的答案。
  • 一旦更新了tail,就把原tail到新tail之间的所有点从右边的序列里丢掉,然后丢到左边的堆里去。
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=100010;
int a,b,c,qwq=0,Tail=0,last=0,tot=1;
int lyf[orzhjw]={0},hy[orzhjw]={0};
bool u[orzhjw]={0},v[orzhjw]={0};
priority_queue<pair<int,int>,vector<pair<int,int> >,less<pair<int,int> > >mid;
struct qaq{
    int x;
    int id;
}f[orzhjw]={0};
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);return 0;
}
bool cmp(qaq x,qaq y){
    return x.x>y.x;
}
void deal(int l,int r){
    for(int i=l;i<=r;i++){
        mid.push(make_pair<int,int>(lyf[i],i));
        v[i]=1;
    }
}
int main(){
    get(a);
    mid.push(make_pair<int,int>(0,0));
    for(int i=1;i<=a;i++){
        get(hy[i]);
    }
    for(int i=1;i<=a;i++){
        get(lyf[i]);
        f[i].id=i;f[i].x=lyf[i]+hy[i]+hy[i];
    }
    sort(f+1,f+a+1,cmp);
    u[0]=1;
    for(int k=1;k<=a;k++){
        int X,Y,x=mid.top().second,y,ans=0;
        
        while(u[x]&&x){mid.pop();x=mid.top().second;}
        
        while(v[f[tot].id]&&tot<=a)tot++;
        y=f[tot].id;
        
        X=lyf[x];Y=lyf[y]+hy[y]+hy[y]-last;
        
        if(X>Y){
            qwq+=X;
            u[x]=1;
            mid.pop();
        }
        
        else{
            qwq+=Y;
            u[y]=1;v[y]=1;
            tot++;
            deal(Tail+1,y);
            Tail=y;
        }
        
        last=hy[Tail]+hy[Tail];
        put(qwq);turn;
    }
}
View Code

 【4/20】(贪心&DP)[USACO08JAN](luogu【P2899】)手机网络Cell Phone Network

树形DP

不会。

贪心

对于一个点以及它的孩子,显然选则根更加划算。

那么我们可以从深度最深的点入手,然后把它的根节点以及兄弟节点都标记掉。

而且在一定情况下,最优的解法一定是尽量少的重复覆盖。

因为说已经最深了,所以它以下的都可以不用理,而且已经标记的点和当前没有关系了,尽情向上就可以了。

然后再重新找一个深度最深的节点,重复这种操作,直到没有节点可以贪心为止。

每一次贪心都取其根节点,ans++

如图,第一次是红色,第二次是橙色,第三次是黄色。

#include<iostream>
#include<cstdio>
#include<cstring>
#include <cmath>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=100010;
int a,b,c,d,num=0,tot=1,ans=0;
bool lyf[orzhjw]={0},u[orzhjw]={0};
int dad[orzhjw]={0};
int ru[orzhjw]={0},chu[orzhjw]={0};
int next[orzhjw*2]={0},to[orzhjw*2]={0},head[orzhjw]={0};
struct qwq{
    int id;
    int dep;
}whh[orzhjw];
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);
}
bool cmp(qwq x,qwq y){
    return x.dep>y.dep;
}
void add(int x,int y){
    to[++num]=y;
    next[num]=head[x];
    head[x]=num;
}
int work(int x){
    if(x==0)return ans;
    ans++;u[x]=1;
    for(int i=head[x];i;i=next[i]){
        int y=to[i];
        u[y]=1;
    }
    while(u[whh[tot].id]){
        tot++;
    }
    return work(dad[whh[tot].id]);
}
void build(int x,int dd){
    whh[x].dep=dd;whh[x].id=x;
    lyf[x]=1;
    for(int i=head[x];i;i=next[i]){
        int y=to[i];
        if(!lyf[y]){
            dad[y]=x;
            build(y,dd+1);
        }
    }
    return;
}
int main(){
    get(a);
    for(int i=1;i<a;i++){
        int x,y;
        get(x);get(y);
        add(x,y);add(y,x);
        ru[y]++;chu[x]++;
        if(i==a-1)chu[y]++;
        if(i==1)ru[x]++;
    }
    int op=0;
    for(int i=1;i<=a;i++){
        if(ru[i]==1&&chu[i]==1)op++;
    }
    if(op==a){
        put((a+2)/3);turn;return 0;
    }
    dad[1]=0;
    build(1,1);
    sort(whh+1,whh+a+1,cmp);
    int anss=work(dad[whh[1].id]);
    if(!u[1])anss++;
    put(anss);turn;
}
View Code

 【5/20】(数论)luogu 【P1069】细胞分裂

这题我不知道暴力怎么写233333

题意:

已知si,m1,m2

求满足sik % m1m2 ==0的最小k

没有满足的条件就输出 -1

题解:

我们表示si=a1*a2*...*an,那么sik=(p1*a1*p2*a2*...pn*an)k=(p1*a1)k*(p2*a2)k*...*(pn*an)k=k*p1个a1  *  k*p2个a2   *...*   k*pn个an

m1同理,m1=m2*p1个a1   *   m2*p2个a2   *...*   m2*pn个an

此处ai为si或m1的一个质因数,pi表示质因数ai的个数

显然,我们可以把分子和分母中相同的ai约掉,直到分母为1为止。

那显然,如果分母有ai,而分子没这个ai,就是无解。

否则k=max(ai分母中的个数 / ai分子中的个数)【记得向上取整】

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=100010;
int a,b,c,d,sum=0,MIN=1000000000;
int m1,m2;
int whh[orzhjw]={0};
int NEW[orzhjw]={0};
int nxt[orzhjw]={0};
bool lyf[orzhjw]={0};
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);return 0;
}
void find(int pp){
    int x=pp;
    for(int i=2;i;i=nxt[i]){
        int o=0;
        while(x&&x%i==0){
            x/=i;
            o++;
        }
        whh[++sum]=o*m2;
        if(x==1)return;
    }
}
int work(int X){
    int x=X,ans=0,summ=0;
    for(int i=2;i;i=nxt[i]){
        int o=0;
        while(x&&x%i==0){
            x/=i;
            o++;
        }
        NEW[++summ]=o;
        if((!o)&&whh[summ])return 0;
        if(x==1||summ>=sum)break;
    }
    if(summ<sum)return 0;
    for(int i=1;i<=summ;i++)
    if(NEW[i])ans=max(ans,(whh[i]-1)/NEW[i]+1);
    if(ans)MIN=min(MIN,ans);
    return ans;
}
int main(){
    get(a);get(m1);get(m2);
    
    if(a==0){
        put(-1);turn;
        return 0;
    }
    
    if(m1==1){
        put(0);turn;
        return 0;
    }
    
    lyf[1]=1;
    for(int i=2;i*i<=m1;i++)
    if(!lyf[i])
    for(int j=i+i;j<=m1;j+=i)
    lyf[j]=1;
    
    int last=2;
    for(int i=3;i<=m1;i++){
        if(!lyf[i]){
            nxt[last]=i;
            last=i;
        }
    }
    
    find(m1);
    
    for(int i=1;i<=a;i++){
        int x;get(x);
        work(x);
    }
    
    if(MIN==1000000000)put(-1);
    else put(MIN);
    turn;
}
View Code

 【6/20】(贪心)[USACO07JAN](luogu【P2878】)保护花朵Protecting the Flowers 

首先我们可以得到ans=min(∑Di*sumti);

显然TLE(23333333)

我们假装现在的时间累计是A,有x,y两只牛。

那么如果要让先选x更优,那么我们可以得到一个不等式:

A*Dx  +  (A+2*Tx)  *  Dy  <  A*Dy  +  (A+2*Ty)  *  Dy

我们展开,就是A*Dx  +  A*Dy  +  2*Tx*Dy  <  A*Dy  +  A*Dx  +  2*Ty*Dx;

把两边的A*Dx,A*Dy去掉,再把2去掉,就可以得到

Tx*Dy  <  Ty*Dx;

没错,我们只要求出满足这样的序列就可以了。

(快排一波~)qwq

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=100010;
ll a,b,c,d,sum=0,ans=0;
struct qwq{
    ll T;
    ll D;
}whh[orzhjw]={0};
inline ll get(ll &x){
    char s=getchar();ll f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);return 0;
}
bool cmp(qwq x,qwq y){
    return x.T*y.D<y.T*x.D;
}
int main(){
    get(a);
    for(ll i=1;i<=a;i++){
        get(whh[i].T);get(whh[i].D);
    }
    sort(whh+1,whh+a+1,cmp);
    for(ll i=1;i<=a;i++){
        ans+=sum*whh[i].D;
        sum+=whh[i].T+whh[i].T;
    }
    put(ans);
}
View Code

 

【7/20】(数论&找规律&暴力)luogu【P1134】阶乘问题

直接乘:

 如果一直暴力乘上去,那么你就完了。因为它的位数上天(OAO);

 然后你会发现,每次乘上去都是往前面的位数累加。

而我们只需要最后几位的乘积,前面一坨是没有用的;

因为即使最高位和最高位相乘,那也是会飞上天的位数,但是n的范围并没有那么大。

而后7位乘以某个n得到的答案是和后7位之前的位数无关的。

那就可以取膜掉。

而位数最多有7位,所以我们只需要mod 1e7就行了。

数学解:

我们考虑分解因数:

(此处*1省略掉)

我们可以发现,这些数里有很多偶数,而这些都是2的倍数。

当这些2的倍数与5相乘时,末尾就会至少出现一个0;

而本身末尾就有0的数字我们可以先把0筛掉;

接下来,把2的倍数都筛成非2的倍数,并记录这些数的因数为2的次数和,5也一样。

显然,2的次数比5的次数要多,我们假设它们的差值为k。

筛过之后继续相乘就不会出现10了,那么就可以大胆%10,此时只要顺带乘一遍2k就可以了。

//只有直接乘的代码qwq

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const int orzhjw=0;
ll a,b,c,d,ans=1,m=1e7;

inline ll get(ll &x){
    char s=getchar();ll f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);return 0;
}
int main(){
    get(a);
    for(int i=1;i<=a;i++){
        ans*=i;
        while(ans&&ans%10==0)ans/=10;
        ans%=m;
    }
    put(ans%10);turn;
}
View Code

 【8/20】(数论)luogu【P3197 】[HNOI2008]越狱 

首先看到题目我就懵逼了。。。。这毒瘤数据范围要怎么写啊啊啊啊!!!!!!

冷静!冷静。。。。。

咦,好像可以DP一波然后优化。。。呀,好像过(shi)了(zai)样(gua)例(can)

(兴奋地打了个矩阵快速幂。。。结果才发现只能过m=2的点qwq)

(要不是我不会减法的取膜(才不会告诉你我去看了题解233)

那我们何妨换成全部情况减去不会越狱的情况?

我们先假设DP[i]表示至今结尾为i的不越狱方案数,那么DP[i]=ΣDP[j](1<=j<=m且j ! = i)

初始都是1,那么有这么个东西:

对!DP[i]=(m-1)*(m-1)*(m-1)*...*(m-1)=(m-1)(i-1)

那么DP[n]就是(m-1)(n-1)

显然,全部情况的方案数就是m*m*m*m...*m=mn;

然后快速幂一下,输出((k(m,m)-(k(m-1,n-1))%m+m)%m

此处(A-B)%m=(A%m-B%m+m)%m。。。。

(别问我为什么我也不知道。。。。)【下面评论有讲】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
using namespace std;
const ll orzhjw=50010;
ll a,b,c,d,m=100003;
ll lyf[orzhjw]={0};
inline ll get(ll &x){
    char s=getchar();ll f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);return 0;
}
ll ksm(ll x,ll q){
    ll o=1;
    while(q){
        if(q&1)o=(o*x)%m;
        x=(x*x)%m;
        q>>=1;
    }
    return o;
}
int main(){
    get(a);get(b);
    put((ksm(a,b)-(a*ksm(a-1,b-1))%m+m)%m);
    turn;
}
View Code

 【9/20】(贪心)luogu【P1016】旅行家的预算

扯淡:

我们来看一下题目。。。。。。(其实看到这题的时候我对实数是拒绝的。。。)(吐槽:好乱而且其实不是实数的话更难233)

分析一下,对于从一个点x到下一个点y,我们会发现如果:

1.如果Px>Py,那么显然只要把油加到可以跑到Dy就可以了,再在y确定加多少油。

因为如果跑到Dy后面肯定要付出更大的价钱,还不如跑一段Px的再跑一段Py的。

2.如果是在x可以到达的范围内,只有Px<Py:

那么显然,能加就加,现在x把油加满,后面就不会亏了。

总结一下:

对于可走范围内:

1.有比当前小的P,就转移到那个点。

2.没有比当前小的P,把油加满,转移到范围内最小的那个P。

否则:

输出NO Solution

注意:

P[0]=P;

D[N+1]=D1,P[N+1]=0;(对于可走范围:因为情况1的代价是走到下一个需要的,情况2是后面未知的,所以我们不妨让终点从情况1转移过去)

【lyf==D,whh==P】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const int orzhjw=510;
int a,b,c,d;
int N;
float C,D1,D2,P;
float lyf[orzhjw]={0},whh[orzhjw]={0};
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);return 0;
}
void output(float x){
    printf("%.2f\n",x);exit(0);
}
void work(int now,float sum,float ans,float last){
    if(sum>=D1)output(ans);
    int MIN=now+1;
    for(int i=now+1;i<=N;i++){
        if(lyf[now]+D2*C<lyf[i])break;
        float X=lyf[i]-lyf[now];
        if(whh[i]<whh[now]){
            if(last<=X)work(i,lyf[i],ans+(X-last)/D2*whh[now],0);
            else work(i,lyf[i],ans,last-X);
        }
    }
    for(int i=now+1;i<=N;i++){
        if(lyf[now]+D2*C<lyf[i])break;
        if(whh[MIN]>whh[i])MIN=i;
    }
    float X=(C*D2-last)/D2;
    if(lyf[MIN]<=lyf[now]+D2*C)work(MIN,lyf[MIN],ans+X*whh[now],C*D2-lyf[MIN]+lyf[now]);
    printf("No Solution\n");
    exit(0);
}
int main(){
    scanf("%f%f%f%f%d",&D1,&C,&D2,&P,&N);
    for(int i=1;i<=N;i++){
        scanf("%f%f",&lyf[i],&whh[i]);
    }
    N++;
    whh[0]=P;
    lyf[N]=D1;whh[N]=0;
    work(0,0,0,0);
}
View Code

 【10/20】(贪心)luogu【P1525】关押罪犯

分析:

题目给你一个图,让你把这个图分成两个集合,使得两个集合里面最大边权最小。

显然,这个答案就在现有的边权里面,而且越多大的消失越好。

因为说是找最大的,那么显然当前只要考虑最大的那条边。

只有把最大的那条边去掉,即分开此边的两端点,才会改变答案的大小。

解决:敌人的敌人是我朋友

我们不妨将最终结果看成“没有e1且没有e2且没有e3且.....且没有eq”(这里1-q是从大到小排序的

也就是说,我们尽早把大的边的两端吸掉仇恨,那答案就会变小;反之,如果吸掉别的边,当前答案是不会改变的。

所以,当前最大边,都要建立起所连接点的对立关系。

我们用一个数组agst[i]来表示:与 i 对立的集合。

如果两个都暂时是单独的一个点(agst[x]==agst[y]==0)(CE):那就agst[]相互赋值

如果x是单独一个点(agst[x]==0):那就建立起gast[x]=y

如果y是单独一个点(agst[y]==0):那就建立起gast[y]=x

如果两个都有且不再同一集合(gast[x]&&agst[y]&&x!=y):那就合并x和agst[y],y和agst[x]

如果x和y在同一集合里,也就是说在满足比当前边大的左右边消失的情况下,在某个阵营里最大:输出答案,return 0

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const int orzhjw=100010;
int a,b,c,d,sum=0;
int agst[orzhjw]={0},dad[orzhjw]={0};
struct qwq{
    int from;
    int to;
    int w;
}edge[orzhjw]={0};
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);return 0;
}
bool cmp(qwq x,qwq y){
    return x.w>y.w;
}
int find(int x){
    if(dad[x]==x)return x;
    return dad[x]=find(dad[x]);
}
int main(){
    get(a);get(b);
    for(int i=1;i<=a;i++)dad[i]=i;
    for(int i=1;i<=b;i++){
        int x,y,z;
        get(x);get(y);get(z);
        edge[i].from=x;edge[i].to=y;
        edge[i].w=z;
    }
    sort(edge+1,edge+b+1,cmp);
    for(int i=1;i<=b;i++){
        int x=edge[i].from,y=edge[i].to,z=edge[i].w;
        int X=find(dad[x]),Y=find(dad[y]);
        if(X==Y){
            put(z);turn;return 0;
        }
        if(agst[X]==0&&agst[Y]==0){
            agst[X]=Y;agst[Y]=X;
        }
        if(agst[X]){
            dad[Y]=find(dad[agst[X]]);
        }
        if(agst[Y]){
            dad[X]=find(dad[agst[Y]]);
        }
    }
    put(0);turn;
    return 0;
}
View Code

 【11/20】(数论)luogu【p1414】又是毕业季II

分析:

题目要你从n个数里选i个数出来(1<=i<=n),问你取出i个时可以得到的最大公约数是多少?

tip1:显然取得越少答案越大;

tip2:肯定是有共同的一个因数;

解决:

我们可以将每一个数拆成若干个因数,每个因数k都在d[k]加上一个权值。

那么我们只要从小到大排序一波d[i],然后ans[d[i]]=k对应的因数。

这时候我们取个数为d[i]的对应k的最大值就好了。

显然,当选取一个因数为k时,d[i]统计的不会重复,恰好就是有这个因数的数字的个数。

显然如果没有恰好是当前取数i个的d[j],记得向上取max

(对于一个取值数i,如果没有刚好的d[],答案就是大取值里可以得到的最大值。

毕竟ans[i]只能在取数大于ians[k]里面找,因取值小的满足不了个数>=i个的条件)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const int orzhjw=1000010;
int a,b,c,d,mx=0;
int ans[orzhjw]={0};
struct qwq{
    int id;
    int sum;
}whh[orzhjw]={0};
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);return 0;
}
bool cmp(qwq x,qwq y){
    if(x.sum<y.sum)return 1;
    if(x.sum>y.sum)return 0;
    return x.id<y.id;
}
void add(int x){
    for(int i=1;i*i<=x;i++){
        if(x%i==0){
            int j=x/i;
            whh[i].sum++;whh[j].sum++;
            if(i==j)whh[i].sum--;
        }
    }
}
int main(){
    get(a);
    for(int i=1;i<=a;i++){
        int x;
        get(x);add(x);
        mx=max(mx,x);
    }
    for(int i=1;i<=mx;i++)whh[i].id=i;
    sort(whh+1,whh+mx+1,cmp);
    for(int i=1;i<=mx;i++){
        //put(whh[i].id);put(whh[i].sum);turn;
        ans[whh[i].sum]=whh[i].id;
    }
    ans[a+1]=1;
    for(int i=a;i>=1;i--)ans[i]=max(ans[i],ans[i+1]);
    for(int i=1;i<=a;i++){
        put(ans[i]);turn;
    }
    return 0;
}
View Code

 

【12/20】(贪心)luogu【P1645】 序列 

分析:

经典贪心。

题目要求最大长度,即最大的重合。(贪心思想)

我们可以从每个区间的选择先后关系来看。

解决:

对于当前区间,选择的数肯定是要最大程度地重合。(即对答案的贡献最大

那么我们可以看作“为后面服务”——按照右端点排序

遇到一个区间时,如果它所在的区域已经被前面的占用,且占用的数量大于等于要求的,就continue。

每次选择的数肯定是贪心地从最右边开始挑。

如果一个点已经被访问过了,那么你很幸运,跳过。没被访问过得点就标记顺便累加答案。

这样,就是每一个区间的选择都对后面的贡献最大。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const int orzhjw=1010;
int a,b,c,d,mx=0,ans=0;
bool lyf[orzhjw]={0};
struct qwq{
    int l;
    int r;
    int kk;
}ask[orzhjw]={0};
inline int get(int &x){
    char s=getchar();int f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline int put(int x){
    printf("%d ",x);return 0;
}
bool cmp(qwq x,qwq y){
    return x.r<y.r;
}
int main(){
    get(a);
    for(int i=1;i<=a;i++){
        int x,y,z;
        get(x);get(y);get(z);
        ask[i].l=x;ask[i].r=y;ask[i].kk=z;
    }
    sort(ask+1,ask+a+1,cmp);
    ask[0].r=1;
    for(int i=1;i<=a;i++){
        int x=ask[i].l,y=ask[i].r,k=ask[i].kk;
        int tot=0;
        for(int j=x;j<=y;j++)if(lyf[j])tot++;
        k-=tot;
        if(k<=0)continue;
        for(int j=y;j>=x&&k;j--)if(!lyf[j]){k--;ans++;lyf[j]=1;}
    }
    put(ans);turn;
}
View Code

 【13/20】(数论)luogu【P4296】[AHOI2007]密码箱

分析:

题目要求:x*x=kn+1             x*x-1=kn          (x+1)*(x-1)=kn

解决:

显然,我们可以分解       k=k1*k2,n=n1*n2     所以一定有一种分解,

使得:

1.(x+1)=k1*n1且(x-1)=k2*n2;

2.(x+1)=k2*n2且(x-1)=k1*n1;

枚举n的每个因子n1,n2=n/n1。若n1<=n2,那么再枚举k2

1.(x-1)=k2*n2(0<=k1<=n1),那么x就是k2*n2+1,(x+1)=k2*n2+2=k1*n1 【只要判断整数k1是否存在就好了】

2.(x+1)=k2*n2(1<=k<=n1),那么x就是k2*n2-1,(x-1)=k2*n2-2=k1*n1【一样,判断k1】

记得用map去重

效率是所以因数之和。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const int orzhjw=2000010;
ll a,b,c,d,tot=0;
map<ll,bool>lyf;
ll ans[orzhjw]={0};
inline ll get(ll &x){
    char s=getchar();ll f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);return 0;
}
int main(){
    get(a);
    for(ll i=1;i*i<=a;i++){
        ll x=i,y=a/i;
        if(x*y==a){
            for(ll j=1;j<=a;j+=y)
            if((j+1)%x==0&&(!lyf[j])){ans[++tot]=j;lyf[j]=1;}
            for(ll j=y-1;j<=a;j+=y)
            if((j-1)%x==0&&(!lyf[j])){ans[++tot]=j;lyf[j]=1;}
        }
    }
    sort(ans+1,ans+tot+1);
    for(ll i=1;i<=tot;i++){
        put(ans[i]);turn;
    }
    return 0;
}
View Code

 【14/20】(数论)luogu【P1463】 [POI2002][HAOI2007]反素数

分析:

x=p1k1*p2k2*...*pmkm (p为质因数)(显然有这样的唯一分解)

假设pi<pj

若存在任意的ki<kj

则可以通过交换kikj,得到x'<=x

并且g(x)=g(x')

那么显然,x'<x,而g(x)==g(x')x不是反素数。

即k递减。

解决:

显然,答案就是约数最大的数字的最小值。而约数计算可以通过质因数来递推起来。

设当前因数为pk (p为一个质因数),那么cnt=cnt+cnt*i。

可以看出,只与质因数的种类和指数有关,即让质因数种类尽量多。

那么你会发现无论如何,质数可以尽量靠前就靠前就靠前,即到第12个质数。

然后根据前12个质数,以及质因数指数递减,爆搜就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const ll orzhjw=20;
ll a,b,c,d,ans=1,mx=1;
ll whh[13]={0,2,3,5,7,11,13,17,19,23,29,31,37};
inline ll get(ll &x){
    char s=getchar();ll f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);return 0;
}
ll find(ll now,ll last,ll cnt,ll k){//当前乘积,上一个的指数,总个数,第k个质数 
    if(cnt>mx){
        mx=cnt;ans=now;
    }
    if(cnt==mx){
        ans=min(ans,now);
    }
    if(k>12||last==0)return ans;
    ll qwq=whh[k];
    find(now,last,cnt,k+1);
    for(ll i=1;i<=last;i++){
        if(now*qwq>a)break;
        find(now*qwq,i,cnt+cnt*i,k+1);
        qwq*=whh[k];
    }
    return ans;
}
int main(){
    get(a);
    find(1,31,1,1);
    put(ans);
    turn;
    return 0;
}
View Code

 【15/20】(数论)luogu【5174】圆点

分析:

嗯好我又回来了   

不要慌,虽然是个圆,但还是挺简单的。

圆内?r!勾股定理!

只要把所有X2+Y2(<=R)累加起来就好了。

解决:

我们知道,圆是轴对称图形,又是中心对称图形。

所以只要求出一个象限的解,就可以求出整个圆的解。

假装我们现在只有第一象限。

一:对于一个x,我们可以发现答案的累加形式就是

                          ans+=x2+12+x2+22...+x2+y2

                                  =x2*y+sumpower(1,y)

我们知道,1到y的平方和等于

 

那么只要用sqrt的向下取整,就可以算出来每一个x的贡献。

二:从累加条件的角度思考,我们发现当横坐标为x或纵坐标为x时,x才有贡献。

也就是说,只要求出在这两条直线上找出符合条件的点,其个数就是x2累加的次数。

即还是枚举x,求出对应的y,然后ans+=2*y*x2

作法二的代码:

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define ll long long
#define ull unsigned long long
#define turn printf("\n");
#define emp printf(" ");
using namespace std;
const int orzhjw=50010;
ll R,b,c,d,m=1e9+7,ans=0,tot=0;
inline ll get(ll &x){
    char s=getchar();ll f=1,o=0;
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();}
    return x=o*f;
}
inline ll put(ll x){
    printf("%lld ",x);return 0;
}
int main(){
    get(R);
    for(ll x=1;x*x<R;x++){
        ll k=R-x*x;
        ll y=sqrt(k);
        ans+=(((2*y*x)%m)*x)%m;
        ans%=m;
    }
    for(ll i=1;i*i<=R;i++){
        (ans+=i*i)%=m;
    }
    put((ans*4)%m);turn;
}
View Code

 

posted @ 2018-08-01 09:50  A-nice-orange  阅读(409)  评论(3编辑  收藏  举报