【浮*光】#省选真题# [JSOI2014]

T1:【p4039】拼图

 


 

 

T2:【p4040】宅男计划

 

  • 外卖店一共有N种食物。第i种食物有固定的价钱Pi和保质期Si。
  • 第i种食物会在Si天后过期。JYY是不会吃过期食物的。//233那当然鸭
  • 保质期可以为0天,这样这份食物就必须在购买当天吃掉。
  • JYY现在有M块钱,每一次叫外卖需要额外付给送外卖小哥外送费F元。
  • JYY想知道,在满足每天都能吃到至少一顿没过期的外卖的情况下,
  • 他可以最多宅多少天呢? //JYY心里有我就永远不会饿辣(→_→)

 

 ‘购买外卖的次数’与‘能够宅的天数’的关系 近似于单峰函数,选择三分‘购买外卖的次数’。

然后在get_maxday函数中进行贪心:将所有的钱减去送外卖的总费用(次数*F)后平均分成多份,

对每一份钱进行贪心(整周期),再把剩下的所有钱进行贪心,max_day=周期天数*份数+后面的天数。

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<deque>
using namespace std;
typedef long long ll;

/*【p4040】宅男计划 // 三分 + 贪心
外卖店一共有N种食物。第i种食物有固定的价钱Pi和保质期Si。
第i种食物会在Si天后过期。JYY是不会吃过期食物的。//233那当然鸭
保质期可以为0天,这样这份食物就必须在购买当天吃掉。
JYY现在有M块钱,每一次叫外卖需要额外付给送外卖小哥外送费F元。
JYY想知道,在满足每天都能吃到至少一顿没过期的外卖的情况下,
他可以最多宅多少天呢? */ //JYY心里有我就永远不会饿辣(→_→)

/* ‘购买外卖的次数’与‘能够宅的天数’的关系 近似于单峰函数,选择三分‘购买外卖的次数’。
然后在get_maxday函数中进行贪心:将所有的钱减去送外卖的总费用(次数*F)后平均分成多份,
对每一份钱进行贪心(整周期),再把剩下的所有钱进行贪心,max_day=周期天数*份数+后面的天数。*/

void reads(ll &x){ //读入优化(正负整数)
    ll f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;  /* hs_love_wjy */  }

ll m,f,n; struct node{ ll p,s; }a[519]; //先按价格排序,再按天数

bool cmp(node a,node b){ if(a.p==b.p) return a.s>b.s; return a.p<b.p; }

ll get(ll x){
    ll ans=0,now=0,p,j;
    ll v=m-x*f; //减去叫外卖所花费的钱
    ll w=v/x; //用w存每一份叫外卖的周期的花费
    ll k=v-w*x; //用k存剩下来的钱
    if(v<0) return 0; //钱花完了
    for(ll i=1;i<=n;i++){ //【整周期】
        if((a[i].s>=now)&&(w-a[i].p>=0)) //挑便宜的买,比较能买它的天数
           p=min(a[i].s+1-now,w/a[i].p),now+=p,w-=p*a[i].p;
        j=i; if(w-a[i].p<0) break; //因为按价格排序,当前的买不起了,剩下的都买不起了
    } k+=w*x; //将整周期剩下的钱全存到k里
    for(ll i=j;i<=n;i++){ //对k进行贪心,从j开始,能吃的就买
        if((a[i].s>=now)&&(k-a[i].p>=0))
            p=min(k/a[i].p,x),ans+=p,k-=p*a[i].p;
        if(ans>0) break; //剩余的钱不能支持每一周期都买了,所以能买几次买几次
    } return x*now+ans;
}

int main(/* hs_love_wjy */){
    reads(m),reads(f),reads(n); for(ll i=1;i<=n;i++) reads(a[i].p),reads(a[i].s);
    sort(a+1,a+n+1,cmp); ll l=1,r; if(f==0) r=m+1; else r=(m/f)+1; //外卖次数区间(l,r)
    while(l<r){ ll midl=l+(r-l)/3,midr=r-(r-l)/3;
        if(get(midl)>=get(midr)) r=midr-1; else l=midl+1;
    } cout<<get(l)<<endl; return 0; //JYY永远在我心里
}
【p4040】宅男计划 // 三分 + 贪心

 


 

 

T3:【p4041】奇怪的计算器

 

  • 给出一串数,设置上下界l、r,进行操作:
  • +a:表示将当前的结果加上a ; -a:表示将当前的结果减去a;
  • *a:表示将当前的结果乘以a ;@a:表示将当前的结果加上a*X(X已知)。

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<deque>
using namespace std;
typedef long long ll;

/*【p4041】JYY的计算器
给出一串数,设置上下界l、r,进行操作:
1.+a:表示将当前的结果加上a;
2.-a:表示将当前的结果减去a;
3.*a:表示将当前的结果乘以a;
4.@a:表示将当前的结果加上a*X(X是一开始JYY输入的数)。 */

void reads(int &x){ //读入优化(正负整数)
    ll f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f; //正负号
}

const int N=100019;

struct trees{ int l,r; ll minn,maxx,p1,p2,p3; }tree[N<<2];

struct ques{ int op; ll x; }q[N];

struct node{ int id; ll x; }a[N];

int n,m; ll ans[N],L,R;

void PushUp(int rt){ tree[rt].maxx=tree[rt<<1|1].maxx,tree[rt].minn=tree[rt<<1].minn; }

void PushDown(int rt,ll k1,ll k2,ll k3){
    tree[rt].p1*=k1,tree[rt].p2=tree[rt].p2*k1+k2,tree[rt].p3=tree[rt].p3*k1+k3;
    tree[rt].maxx=tree[rt].maxx*k1+k2*a[tree[rt].r].x+k3;
    tree[rt].minn=tree[rt].minn*k1+k2*a[tree[rt].l].x+k3;
}

void build(int l,int r,int rt){
    tree[rt].l=l,tree[rt].r=r,tree[rt].p1=1,tree[rt].p2=tree[rt].p3=0;
    tree[rt].maxx=a[r].x,tree[rt].minn=a[l].x;
    if(l==r) return; int mid=(l+r)>>1;
    build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
}

void modify1(int rt){

    if(tree[rt].l==tree[rt].r) return PushDown(rt,0,0,L);
    
    PushDown(rt<<1,tree[rt].p1,tree[rt].p2,tree[rt].p3),
    PushDown(rt<<1|1,tree[rt].p1,tree[rt].p2,tree[rt].p3),
    tree[rt].p1=1,tree[rt].p2=0,tree[rt].p3=0;
    
    if(tree[rt<<1|1].minn<L) //左边所有都超出L了
        PushDown(rt<<1,0,0,L),modify1(rt<<1|1);
    else modify1(rt<<1); //左边部分超出L
    PushUp(rt); //向上统计管理节点
}

void modify2(int rt){
    
    if(tree[rt].l==tree[rt].r) return PushDown(rt,0,0,R); //区间修改
    
    PushDown(rt<<1,tree[rt].p1,tree[rt].p2,tree[rt].p3),
    PushDown(rt<<1|1,tree[rt].p1,tree[rt].p2,tree[rt].p3),
    tree[rt].p1=1,tree[rt].p2=0,tree[rt].p3=0; //标记下移
    
    if(tree[rt<<1].maxx>R) //右边所有都超出R了
        PushDown(rt<<1|1,0,0,R),modify2(rt<<1);
    else modify2(rt<<1|1); //右边部分超出R
    PushUp(rt); //向上统计管理节点
}


void query(int rt){ //↓↓叶子节点的l=r,minn=maxx
    if(tree[rt].l==tree[rt].r){ ans[a[tree[rt].l].id]=tree[rt].minn; return; }
    PushDown(rt<<1,tree[rt].p1,tree[rt].p2,tree[rt].p3), //标记下移
    PushDown(rt<<1|1,tree[rt].p1,tree[rt].p2,tree[rt].p3),
    tree[rt].p1=1,tree[rt].p2=0,tree[rt].p3=0,query(rt<<1),query(rt<<1|1);
}

bool cmp(node aa,node bb){return aa.x<bb.x;}

int main(){
    scanf("%d%lld%lld",&m,&L,&R);
    for(int i=1;i<=m;i++){
        char ss[2]; scanf("%s%lld",ss,&q[i].x);
        switch(ss[0]){
            case '+':{q[i].op=1;break;}
            case '-':{q[i].op=2;break;}
            case '*':{q[i].op=3;break;}
            default :{q[i].op=4;break;}
        }
    } scanf("%d",&n); //↓↓记得记录原id,便于输出
    for(int i=1;i<=n;i++) scanf("%lld",&a[i].x),a[i].id=i;
    sort(a+1,a+n+1,cmp),build(1,n,1);
    for(int i=1;i<=m;i++){
        switch(q[i].op){
            case 1 :{PushDown(1,1,0,q[i].x);break;}
            case 2 :{PushDown(1,1,0,-q[i].x);break;}
            case 3 :{PushDown(1,q[i].x,0,0);break;}
            default:{PushDown(1,1,q[i].x,0);break;}
        } if(tree[1].minn<L) modify1(1); //进行区间修改
          if(tree[1].maxx>R) modify2(1); //将序列中的数控制在L、R中
    } query(1); for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
}
【p4041】JYY的计算器 // 有限制的线段树

 


 

 

T4:【p4042】骑士游戏

 

  • JYY又挖掘出一款RPG游戏。 //这里是此题的重点2333

 

  • 他会扮演一位英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽。

 

莫嚣张 有位骑士已刺出长枪 惩恶扬善游侠四方

正是我 堂吉柯德拉曼查的英豪 这命运召唤我启航

狂风吹开我道路 日月照我征途 无论它要通向何方

不管它通向何方 光辉在邀我前往 ~~~~~~

 

  • 有两种攻击方式,一种是普通攻击,一种是法术攻击。两种攻击方式都会消耗JYY一些体力。
  • 采用普通攻击,i号怪兽死亡后会产生Ri个新的怪兽。已知这Ri个新出现的怪兽的编号。
  • 而采用法术攻击则可以彻底将一个怪兽杀死。一般,法术攻击会消耗更多的体力值。
  • 有N种怪兽,初始只有1号怪兽,JYY想知道,最少花费多少体力值才能消灭怪兽呢?

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<deque>
using namespace std;
typedef long long ll;

/*【p4042】骑士游戏 // 思维 + SPFA

JYY又挖掘出一款RPG游戏。他会扮演英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽。

(    莫嚣张 有位骑士已刺出长枪 惩恶扬善游侠四方 
        正是我 堂吉柯德拉曼查的英豪 这命运召唤我启航 
      狂风吹开我道路 日月照我征途 无论它要通向何方 
          不管它通向何方 光辉在邀我前往 ~~~~~~          )

有两种攻击方式,一种是普通攻击,一种是法术攻击。两种攻击方式都会消耗JYY一些体力。
采用普通攻击,i号怪兽死亡后会产生Ri个新的怪兽。已知这Ri个新出现的怪兽的编号。
而采用法术攻击则可以彻底将一个怪兽杀死。一般,法术攻击会消耗更多的体力值。
有N种怪兽,初始只有1号怪兽,JYY想知道,最少花费多少体力值才能消灭怪兽呢? */

// https://111961.blog.luogu.org/solution-p4042

void reads(ll &x){ //读入优化(正负整数)
    ll f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;  /* hs_love_wjy */  }

const ll N=200019;

ll n,a[N],dis[N],vis[N];

std::vector<ll> ver[N],las[N];

void spfa(){ //借助SPFA松弛操作(可以处理环),结合优先队列更新dis[]
    queue<ll> q; for(ll i=1;i<=n;i++) q.push(i),vis[i]=1; //初始全入队
    while(!q.empty()){ ll u=q.front(); q.pop(); vis[u]=0;
      ll tmp=a[u]; //选择普通攻击,求出此时的费用
      for(ll i=0;i<ver[u].size();i++) tmp+=dis[ver[u][i]];
      if(tmp>=dis[u]) continue; dis[u]=tmp; //判断更新dis[]
      for(ll i=0;i<las[u].size();i++) if(!vis[las[u][i]])
        vis[las[u][i]]=1,q.push(las[u][i]); //重新入队,判断松弛操作
    }
}

int main(/* hs_love_wjy */){
    reads(n); for(ll i=1,t_,x;i<=n;i++){
        reads(a[i]),reads(dis[i]),reads(t_);
        while(t_--) reads(x), //输入每个分裂的怪物
            ver[i].push_back(x),las[x].push_back(i);
    } spfa(); cout<<dis[1]<<endl; return 0;
}
【p4042】骑士游戏 // 思维转化 + SPFA

 

 


 

T5:【p4043】支线剧情

 

  • 求把所有支线剧情都看完需要的min时间。

 

【有上下界费用流的建图方法】 1.建立超级源汇S和T。

 

2.判断是否有要求的源汇,从汇点向源点(此题中为1节点)连容量为inf,费用为0的边。

 

3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用;

 

4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。

 

5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[ ]),费用为0。

 

题目要求每条边都需要走一次,所以每条边下界为1,上界为inf。

然后转化为普通费用流来求。本题中具体建图为:

 i[i>1]->1 (inf,0),S->y (1,z),x->y (inf,z),x->T (cd[x],0)。

 

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

//【p4043】支线剧情 //有上下界的费用流
//求:把所有支线剧情都看完需要的min时间。

//果然大家都喜欢玩RPG游戏的吗(我昨天过Shin的单人线都玩到1:30...)
//果然所谓的“快进”都是骗人的吗...(过剧情真心痛苦...)

/* 有上下界费用流的建图方法: 1.建立超级源汇S和T。

2.判断是否有要求的源汇,从汇点向源点(此题中为1节点)连容量为inf,费用为0的边。

3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用;

4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。

5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[]),费用为0。*/

/*【分析】题目要求每条边都需要走一次,所以每条边下界为1,上界为inf。
然后转化为普通费用流来求。本题中具体建图为:
 i[i>1]->1 (inf,0),S->y (1,z),x->y (inf,z),x->T (cd[x],0)。*/

void reads(int &x){ //读入优化(正负整数)
    int fx=1;x=0;char ch_=getchar();
    while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
    while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
    x*=fx; //正负号
}

const int N=100019,inf=0x3f3f3f3f;

struct edge{ int ver,nextt,flow,cost; }e[2*N];

int tot=-1,n,S,T,maxf=0,minc=0,f[N];

int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];

void add(int a,int b,int f,int c)
{ e[++tot].nextt=head[a],head[a]=tot,
  e[tot].ver=b,e[tot].flow=f,e[tot].cost=c;
  e[++tot].nextt=head[b],head[b]=tot,
  e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } 

bool spfa(int S,int T){
    queue<int> q;
    memset(inq,0,sizeof(inq));
    memset(flow,0x7f,sizeof(flow));
    memset(dist,0x7f,sizeof(dist));
    q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
    while(!q.empty()){
        int x=q.front(); q.pop(); inq[x]=0;
        for(int i=head[x];i!=-1;i=e[i].nextt){
            if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                dist[e[i].ver]=dist[x]+e[i].cost;
                pre[e[i].ver]=x,lastt[e[i].ver]=i;
                flow[e[i].ver]=min(flow[x],e[i].flow);
                if(!inq[e[i].ver])
                    q.push(e[i].ver),inq[e[i].ver]=1;
            }
        }
    } return pre[T]!=-1;
}

void mcmf(){
    while(spfa(S,T)){
        int now=T; //↓↓最小费用最大流
        maxf+=flow[T],minc+=dist[T]*flow[T];
        while(now!=S){ //↓↓正边流量-,反边流量+
            e[lastt[now]].flow-=flow[T];
            e[lastt[now]^1].flow+=flow[T]; 
            //↑↑利用xor1“成对储存”的性质
            now=pre[now]; //维护前向边last,前向点pre
        }
    }
}

int main(){
    reads(n); S=0,T=n+1; memset(head,-1,sizeof(head));
    for(int i=1,k,y,z;i<=n;i++)
     { reads(k); if(k) add(i,T,k,0); if(i!=1) add(i,1,inf,0);
       while(k--) reads(y),reads(z),add(S,y,1,z),add(i,y,inf,z); } 
    mcmf(); printf("%d\n",minc); return 0; //↑↑注意建图方式
}
【p4043】支线剧情 //有上下界的费用流【可能是因为费用流板子不够好所以只有70...】

 


 

T6:【p4044】保龄球

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<deque>
using namespace std;
typedef long long ll;

//【p4044】保龄球 // 分情况讨论 + 超级复杂的dp

// https://www.cnblogs.com/clrs97/p/8531832.html

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;  /* hs_love_wjy */  }

const int N=59;

int n,flag,cnt1,cnt2,cnt3,cd,ans,f[N][N][N][N][2][3];

struct P{ int x,y,z; }a[N],b[N],c[N],d,_c[N];

inline bool cmpx(const P&a,const P&b){return a.x>b.x;}

inline bool cmpz(const P&a,const P&b){return a.z>b.z;}

void solve(){
    
    int w,t,x,y,cana,cane;
    
    for(int i=0;i<=cnt1;i++) for(int j=0;j<=cnt2;j++) for(int r=cnt2;r>=j;r--) 
        for(int k=0;k<=cnt3;k++) for(int S=0;S<=cd;S++) for(int o=0;o<3;o++) 
            f[i][j][r][k][S][o]=-1; f[0][0][cnt2][0][0][0]=0; //初始化
    
    for(int i=0;i<=cnt1;i++) for(int j=0;j<=cnt2;j++) for(int r=cnt2;r>=j;r--) 
      for(int k=0;k<=cnt3;k++) for(int S=0;S<=cd;S++) for(int o=0;o<3;o++){
        
        w=f[i][j][r][k][S][o]; if(w<0)continue; cana=cane=1;
        
        if(i+j+cnt2-r+k+S+1==n-1) cana=flag,cane=flag^1;
        
        if(cana&&i<cnt1){ t=10; if(o) t<<=1; 
            f[i+1][j][r][k][S][1]=max(f[i+1][j][r][k][S][1],w+t);
        } if(!cane) continue;
        
        if(j<r){ x=b[j+1].x,y=b[j+1].y; t=x+y;
            if(o==1) t=(x+y)<<1; if(o==2) t=(x<<1)+y;
            f[i][j+1][r][k][S][2]=max(f[i][j+1][r][k][S][2],w+t);
            x=b[r].x,y=b[r].y; t=x+y;
            if(o==1) t=(x+y)<<1; if(o==2) t=(x<<1)+y;
            f[i][j][r-1][k][S][2]=max(f[i][j][r-1][k][S][2],w+t); }
        
        if(k<cnt3){ x=c[k+1].x,y=c[k+1].y; t=x+y;
            if(o==1) t=(x+y)<<1; if(o==2) t=(x<<1)+y;
            f[i][j][r][k+1][S][0]=max(f[i][j][r][k+1][S][0],w+t); }

        if(S<cd){ x=d.x,y=d.y; t=x+y;
            if(o==1) t=(x+y)<<1; if(o==2) t=(x<<1)+y;
            f[i][j][r][k][1][0]=max(f[i][j][r][k][1][0],w+t); }
    }

    for(int j=0;j<=cnt2;j++) for(int o=0;o<3;o++)
        ans=max(ans,f[cnt1][j][j][cnt3][cd][o]);

}

void cal(){ sort(c+1,c+cnt3+1,cmpz),solve(),sort(c+1,c+cnt3+1,cmpx),solve(); }

int main(){
    reads(n); for(int i=1;i<=n;i++)
        reads(a[i].x),reads(a[i].y),a[i].z=a[i].x+a[i].y;
    if(a[n].x==10) flag=1,reads(a[++n].x),reads(a[n].y),a[n].z=a[n].x+a[n].y;
    for(int i=1;i<=n;i++){ if(a[i].x==10) cnt1++; 
        else if(a[i].z==10) b[++cnt2]=a[i]; else c[++cnt3]=a[i]; }
    sort(b+1,b+cnt2+1,cmpx); cal();
    for(int i=1;i<=cnt3;i++){ int k_=0,k; cd=1,d=c[i];
        for(int j=1;j<=cnt3;j++) _c[j]=c[j];
        for(int k=1;k<=cnt3;k++) if(k!=i) c[++k_]=c[k]; cnt3=k_;
        cal(); for(cnt3++,k=1;k<=cnt3;k++) c[k]=_c[k];
    } printf("%d\n",ans); return 0;
}
【p4044】保龄球 // 分情况讨论 + 超级复杂的dp

 


 

 

 

                                                               ——时间划过风的轨迹,那个少年,还在等你

 

posted @ 2019-03-15 16:07  花神&缘浅flora  阅读(176)  评论(0编辑  收藏  举报