【浮*光】#省选真题# [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永远在我心里 }
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; }
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; }
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; //↑↑注意建图方式 }
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; }
——时间划过风的轨迹,那个少年,还在等你