暑假集训日记——6.29(快速幂、前缀和、差分、二分、三分)

快速幂、前缀和、差分、二分、三分

目标:高效率的日常,cf rate++ (Fighing)

三分:求凹函数和凸函数的极值问题
模板:
凸函数求极大值:
int版: (解释:若求极大值,若judge(lm)更大,即 lm 更靠近极值点(但是不确定是在左侧还是右侧),但可以保证 rm 一定在极值点右侧),保留左端点,缩右端点 r 到 rm

while(l+1<r)
{
    int lm=(l+r)>>1,rm=(lm+r)>>1;
    if(judge(lm)>judge(rm))
        r=rm;
    else
        l=lm;
}
//答案取 l 

double版:

while(l+eps<r)
{
    double lm=(l+r)/2,rm=(lm+r)/2;
    if(judge(lm)>judge(rm))
        r=rm;
    else
        l=lm;
}
//答案取 l 或 (l+r)/2  (尽管此时 l 和 r 已经相等,但因为精度问题,取 r 可能会错)

凹函数求极小值:
int版:

while(l+1<r)
{
    int lm=(l+r)>>1,rm=(lm+r)>>1;
    if(judge(lm)>judge(rm))
        l=lm;
    else
        r=rm;
}
//答案取 r

double版:

while(l+eps<r)
{
    double lm=(l+r)/2,rm=(lm+r)/2;
    if(judge(lm)>judge(rm))
        l=lm;
    else
        r=rm;
}
//答案取 r 或 (l+r)/2  (尽管此时 l 和 r 已经相等,但因为精度问题,取 l 可能会错)

例题:
C -Elections
题解:(三分)
无单调性,多变量,适合三分 难点就是函数关系怎么确定
三分最终的票数,当有人比自己票数高时,从费用小的开始贿赂他的选民。剩下的从小开始买,用这个函数求得最小费用。
ps:三分的题,除了直接给函数外,真不好想

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll a[N];
char str[N];
vector<int>p[100005];
vector<int>ans;
int n,m,k,q;
int x,y,z;
int mx;

int judge(int x)
{
    ans.clear();
    int sum=0;
    int need=x-p[0].size();//需要贿赂的最少人数
    for(int i=1;i<=mx;i++)
    {
        for(int j=0;j<p[i].size();j++)
        {
            if(p[i].size()-j>=x)//不存在比自己的票多的选举者
            {
                need--;
                sum+=p[i][j];
            }
            else//虽然此竞选者票数少,但是便宜
            {
                ans.push_back(p[i][j]);
            }

        }
    }
    if(need<=0)//满足条件的范围
            return sum;
    int sss=0;
    sort(ans.begin(),ans.end());//找到最便宜的
    while(need--)
    {
        sum+=ans[sss++];
    }
    return sum;

}

int solve(int l,int r)//三分当我被选举时的人数,使得花费 最小
{
    while(l+1<r)
    {
        int lm=(l+r)>>1,rm=(lm+r)>>1;
        if(judge(lm)>judge(rm))
            l=lm;
        else
            r=rm;
    }
    return min(judge(l),judge(r));
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
   cin>>n;
    mx=0;
   for(int i=0;i<n;i++)
   {
       cin>>x>>y;
       p[x].push_back(y);
       if(mx<x)
        mx=x;
   }
   for(int i=0;i<=mx;i++)
   {
       sort(p[i].begin(),p[i].end());
   }
   cout<<solve(p[0].size(),n);

}

B - Monitor
题解:(差分)
由于数据的量比较大,所以二维数组存不下,所以采用二维指针动态分配内存解决
其余的就是基本的差分问题了

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
const int N=1e7+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll a[N];
int n,m;


int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int x,y,x1=0,y1=0,x2=0,y2=0;

    while(scanf("%d%d",&m,&n)!=EOF)
{
    int **ptr=new int*[n+5];//
    int **ps=new int*[n+5];//
    for(int i=0;i<=n+1;i++)//
    {
        ptr[i]=new int[m+5];//
        ps[i]=new int[m+5];//
    }
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            ps[i][j]=0;
            ptr[i][j]=0;
        }
    }
    scanf("%d",&x);
    while(x--)
    {
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        ps[y1][x1]+=1;
        ps[y2+1][x2+1]+=1;
        ps[y2+1][x1]-=1;
        ps[y1][x2+1]-=1;
    }
    int flag=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            ps[i][j]=ps[i][j]+ps[i-1][j]+ps[i][j-1]-ps[i-1][j-1];//关键
            //cout<<ps[i][j]<<" ";
        }
       // cout<<endl;
    }
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            if(ps[i][j]>=1) ps[i][j]=1;
            else ps[i][j]=0;
            //cout<<ps[i][j]<<" ";
        }
        //cout<<endl;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            ps[i][j]=ps[i][j]+ps[i-1][j]+ps[i][j-1]-ps[i-1][j-1];
            //cout<<ps[i][j]<<" ";
        }
        //cout<<endl;
    }
    scanf("%d",&y);
    while(y--)
    {
        flag=0;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        if(ps[y2][x2]+ps[y1-1][x1-1]-ps[y1-1][x2]-ps[y2][x1-1]!=(x2-x1+1)*(y2-y1+1))//关键
            flag=1;
        if(flag==0)
        cout<<"YES"<<endl;
        else if(flag==1)
        cout<<"NO"<<endl;

    }
    for(int i=0;i<=n+1;i++)
    {
        delete[]ptr[i];
        delete[]ps[i];
    }
    delete[]ptr;
    delete[]ps;
}
}

G - Molly’s Chemicals
题解:(前缀和)
就是前缀和来判断某个连续区间是否是p的非负幂级数
开始用的是p[i+1]-p[i]=k^m,暴力的话复杂度太高
然后化简一下p[i+1]=k^m+p[i],把p[i+1]的值标记,判断p[i+1]是否等于k ^ m+p[i]

这注意:一开始我是从开始就把p[i+1]的值全部标记了,就出现问题
1.由于数据的无序性,可能造成不满足一段连续区间的情况。
2.如果p[i+1]相同的值有很多,计算结果又不对。

然而展开为p[i+1]-k^m=p[i],并且对于不同的k ^m,就把p[i+1]的值重新标记一边。就不会出现上述问题,我果然还是太菜了…详细见代码

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
const int N=1e7+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll a[N];
ll vis[N];
ll p[N];
int n,m;

map<ll,int>d;
ll slove(ll x)
{
    ll sum=0;
    map<ll,int>c;
    c[0]=1;
    for(int i=1;i<=n;i++)
    {
        sum+=c[p[i]-x];
        c[p[i]]++;
            //cout<<c[p[i]-x]<<endl;
    }
    return sum;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    //int x,y,x1,y1,x2,y2;
    ll sum=0,mx=0;
    cin>>n>>m;
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        p[i]=p[i-1]+a[i];
    }
    if(m==1)
    {
        ll an=slove(1);
        if(an>0) sum+=an;
    }
    else if(m==-1)
    {
        ll an=slove(1);
        if(an>0) sum+=an;
         an=slove(-1);
        if(an>0) sum+=an;
    }
    else
    for(int i=0;abs(ans)<1e15;i++)
    {
            ll an=slove(ans);
            if(an>0) sum+=an;
            ans*=m;
    }
    cout<<sum<<endl;
}

E - DNA Alignment
题解:(快速幂)
题目不难就是要找到规律:
最多且相同数目字母的个数^字符串长度
剩下就是快速幂就好了。

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
const int N=1e7+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll a[N];
int n;
char s[N];

ll mul(ll a,ll b,ll mod){//快速乘
	ll ans=0,res=a;
	while(b){
		if(b&1) ans=(ans+res)%mod;
		res=(res+res)%mod;
		b>>=1;
	}
	return ans;
}

ll quickPower(ll a,ll b,ll mod){
    ll ans=1,base=a;
    while(b>0){
        if(b&1)
            ans=mul(ans,base,mod);
        base=mul(base,base,mod);
        b>>=1;
    }
    return ans;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int x,y,x1=0,y1=0,x2=0,y2=0;
    ll sum=0,mx=0;
    ll mod=1e9+7;
    cin>>n;
    scanf("%s",s);
    for(int i=0;i<n;i++)
    {
        if(s[i]=='A')
            x1++;
        if(s[i]=='T')
            x2++;
        if(s[i]=='G')
            y1++;
        if(s[i]=='C')
            y2++;
    }
    mx=max(x1,max(x2,max(y1,y2)));
    if(mx==x1) sum++;
    if(mx==y1) sum++;
    if(mx==x2) sum++;
    if(mx==y2) sum++;
    cout<<quickPower(sum,n,mod)<<endl;
}

D - Vasya and Robot
题解:(二分+前缀和)
二分答案,枚举 (用前缀和表示的区间)判断是否符合条件,并找到满足条件的最小值。

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll a[N];
char str[N];
int xtr[N];
int ytr[N];
int n,dis;
int x,y;
int slove(int sta,int len)
{
    int x1=xtr[n]-xtr[sta+len-1]+xtr[sta-1];
    int y1=ytr[n]-ytr[sta+len-1]+ytr[sta-1];
    int redis=abs(x-x1)+abs(y-y1);//计算除了区间以外所需要的步数
    //cout<<sta<<" "<<len<<endl;
    //cout<<x1<<" "<<y1<<endl;
    //cout<<redis<<" "<<len<<endl;
    //cout<<endl;
    if(redis<=len&&redis%2==len%2)//临界条件:当可以操作的步数大于距离时满足条件
        return 1;
    return 0;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

   cin>>n;
   scanf("%s",str+1);
   for(int i=1;i<=n;i++)//分别计算x,y的前缀和
   {
       xtr[i]=xtr[i-1];
       ytr[i]=ytr[i-1];
       if(str[i]=='U') xtr[i]=xtr[i-1]+1;
       if(str[i]=='D') xtr[i]=xtr[i-1]-1;
       if(str[i]=='L') ytr[i]=ytr[i-1]-1;
       if(str[i]=='R') ytr[i]=ytr[i-1]+1;
   }
   //cout<<xtr[n]<<" "<<ytr[n]<<endl;
   cin>>y>>x;
    dis=abs(x)+abs(y);
   if(dis>n||(n-dis)%2!=0)// 特判
    {
        cout<<-1<<endl;
        return 0;
    }
    int l=0,r=n,mid;
    int ans=0;
    while(l<=r)
    {
        mid=(l+r)>>1;
        int flag=0;
        for(int i=1;i<=(n-mid+1);i++)//区间起点的覆盖范围
        {
            if(slove(i,mid))
            {
                flag=1;
                break;
            }

        }
        if(flag==1)
        {
            ans=mid;
            r=mid-1;
        }
        else
        l=mid+1;
    }
    cout<<ans<<endl;

}

Monitor
题解:(二分+前缀和)
提醒注意下变量的范围…太粗心了吧
以及初始话的问题(也是范围的因素…)

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll a[N];
char str[N];
int mapp[1000][1000];
int maps[1000][1000];
int n,m,k,q;
int x,y,z;
int slove(int x)
{
    memset(maps,0,sizeof(maps));
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            if(mapp[i][j]<=x&&mapp[i][j]!=-1)
                maps[i][j]=1;
            maps[i][j]=maps[i][j]+maps[i-1][j]+maps[i][j-1]-maps[i-1][j-1];

            if(i>=k&&j>=k&&maps[i][j]-maps[i-k][j]-maps[i][j-k]+maps[i-k][j-k]==k*k)
                return 1;
        }
    return 0;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n>>m>>k>>q;
    int mx=0;
     for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            mapp[i][j]=-1;
        }
    for(int i=0;i<q;i++)
    {
        cin>>x>>y>>z;
        mapp[x][y]=z;
        if(z>mx)
            mx=z;
    }

    int l=0,r=mx,mid;
    int ans=mod;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(slove(mid))
        {
            ans=mid;
            r=mid-1;
        }
        else
        l=mid+1;
    }
    if(ans==mod)
        cout<<-1<<endl;
    else
        cout<<ans<<endl;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值