NOIP2012提高组复赛题4题

恶心得要死的几题和看起来灰常兹磁的几题拼凑在一起,vijos分别为1779,1780,1782,1783。

题解时间

vijos1779

vijos地址:https://vijos.org/p/1779

描述

恰逢H国国庆,国王邀请n位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这n位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。

输入格式

第一行包含一个整数n,表示大臣的人数。

第二行包含两个整数a和b,之间用一个空格隔开,分别表示国王左手和右手上的整数。接下来n行,每行包含两个整数a和b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。

输出格式

输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。

样例输入

3
1 1
2 3
7 4
4 6

样例输出

2

限制

每个测试点1s

提示

对于20%的数据,有1≤ n≤ 10,0 < a、b < 8;
对于40%的数据,有1≤ n≤20,0 < a、b < 8;
对于60%的数据,有1≤ n≤100;
对于60%的数据,保证答案不超过10^9;
对于100%的数据,有1 ≤ n ≤1,000,0 < a、b < 10000。

来源

Noip2012提高组复赛Day1T2

做法

贪心,使得每个大臣左右手的乘积从小到大排列即是最佳答案,下面给出证明。

首先需要明确一点,对于相邻的两个大臣,交换他们的位置对于他们前方的大臣以及后面的大臣所拿金币没有影响,所以答案具有单调性。

设a大臣现在在b大臣前方一个位置,设a大臣前方所有人的左手数字乘积为S,a大臣自己左右手数字分别为x1,y1,b大臣为x2,y2.

因此,a大臣所得金币数为S/y1,b大臣所得为S*x1/y2.所以a,b大臣中所获金币最多者的金币为
max{S/y1,S*x1/y2}。

如果交换a,b大臣的位置,可得此时最多者的金币是
max{S/y1,S*x2/y1}。

对比两个式子,第一个式子中第一项肯定小于第二个式子中第二项,同理,第一个式子中第二项肯定大于第二个式子中第一项,因此只要max{S*x2/y1,S*x1/y2}最小即可。

显然只要x1*y1<=x2*y2即可。

然后再弄个前缀和就好了。

因此我们就有了完美的60分做法。

60分做法

#include <bits/stdc++.h>
#define ll long long
#define M 10010
using namespace std;
struct dachen{ll a,b,t;}d[M];
ll pre[M],n,m,i,j,ans,tmp,a1,b1;
inline bool cmp(const dachen aa,const dachen bb){return aa.t<bb.t;}
int main(){
    for(scanf("%lld%lld%lld",&n,&a1,&b1),i=1;i<=n;i++)
        scanf("%lld%lld",&d[i].a,&d[i].b),d[i].t=d[i].a*d[i].b;
    for(sort(d+1,d+n+1,cmp),pre[0]=a1,i=1;i<=n;i++){
        pre[i]=pre[i-1]*d[i].a;
        tmp=pre[i-1]/d[i].b;
        ans=max(ans,tmp);
    }return printf("%lld\n",ans),0;
}

短的灰常滋磁,性价比极高。然而如果要拿100分,还要打乘高精和除高精,代码瞬间长了一倍。

100分代码

#include <bits/stdc++.h>
#define M 10010
#define ll long long
#define C(a) memset(a,0,sizeof(a))
using namespace std;
ll t[M],ans[M],mx[M],a[M],b[M];
ll len,n,m,i,j,k,g;
inline void cheng(ll x){
    for(C(ans),g=0,i=len;i>=1;ans[i]=(g*10+t[i])/x,g=(g*10+t[i])%x,i--);
}
inline void chu(ll x){
    for(g=0,i=1;i<=len;t[i]=t[i]*x+g,g=t[i]/10,t[i]%=10,i++);
    for(;g!=0;t[i]+=g,g=t[i]/10,t[i]%=10,i++);
    len=i-1;
}
int main(){
    for(scanf("%lld",&n),i=1;i<=n+1;i++) scanf("%lld%lld",&a[i],&b[i]);
    for(i=2;i<=n;i++) for(j=i+1;j<=n+1;j++)
        if(a[i]*b[i]>a[j]*b[j]) swap(a[i],a[j]),swap(b[i],b[j]);
    for(len=0;a[1]!=0;)
        t[++len]=a[1]%10,a[1]/=10;
    for(j=2;j<=n+1;j++){
        for(cheng(b[j]),i=len;i>=1;i--)
            if(mx[i]<ans[i]){memcpy(mx,ans,sizeof(ans));break;}
            else if(mx[i]>ans[i]) break;
        chu(a[j]);
    }for(len=M-1;mx[len]==0;) len--;
    for(i=len;i>=1;i--) printf("%lld",mx[i]);
    return puts(""),0;
}

vijos1780

vijos地址:https://vijos.org/p/1780

描述

小A和小B决定利用假期外出旅行,他们将想去的城市从1到N编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市i 的海拔高度为Hi,城市i 和城市j 之间的距离d[i,j]恰好是这两个城市海拔高度之差的绝对值,即d[i,j] = |Hi - Hj|。

旅行过程中,小A和小B轮流开车,第一天小A开车,之后每天轮换一次。他们计划选择一个城市S作为起点,一直向东行驶,并且最多行驶X公里就结束旅行。小A和小B的驾驶风格不同,小B总是沿着前进方向选择一个最近的城市作为目的地,而小A总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出X公里,他们就会结束旅行。

在启程之前,小A想知道两个问题:

1.对于一个给定的X=X0,从哪一个城市出发,小A开车行驶的路程总数与小B行驶的路程总数的比值最小(如果小B的行驶路程为0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小A开车行驶的路程总数与小B行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

  1. 对任意给定的X=Xi 和出发城市Si,小A开车行驶的路程总数以及小B行驶的路程总数。

输入格式

第一行包含一个整数N,表示城市的数目。

第二行有N个整数,每两个整数之间用一个空格隔开,依次表示城市1到城市N的海拔高度,即H1,H2,……,Hn,且每个Hi 都是不同的。

第三行包含一个整数X0。

第四行为一个整数M,表示给定M组Si和Xi。

接下来的M行,每行包含2个整数Si 和Xi,表示从城市Si 出发,最多行驶Xi 公里。

输出格式

输出共M+1行。

第一行包含一个整数S0,表示对于给定的X0,从编号为S0的城市出发,小A开车行驶的路程总数与小B行驶的路程总数的比值最小。

接下来的M行,每行包含2个整数,之间用一个空格隔开,依次表示在给定的Si 和Xi 下小A行驶的里程总数和小B行驶的里程总数。

样例输入1

4
2 3 1 4
3
4
1 3
2 3
3 3
4 3

样例输出1

1
1 1
2 0
0 0
0 0

样例输入2

10
4 5 6 1 2 3 7 8 9 10
7
10
1 7
2 7
3 7
4 7
5 7
6 7
7 7
8 7
9 7
10 7

样例输出2

2
3 2
2 4
2 1
2 4
5 1
5 1
2 1
2 0
0 0
0 0

限制

每个测试点1s

提示

对于30%的数据,有1≤N≤20,1≤M≤20;
对于40%的数据,有1≤N≤100,1≤M≤100;
对于50%的数据,有1≤N≤100,1≤M≤1,000;

对于70%的数据,有1≤N≤1,000,1≤M≤10,000;
对于100%的数据,有1≤N≤100,000,1≤M≤10,000,-1,000,000,000≤Hi≤1,000,000,000,0≤X0≤1,000,000,000,1≤Si≤N,0≤Xi≤1,000,000,000,数据保证Hi 互不相同。

来源

Noip2012提高组复赛Day1T3

做法
这道题目太恶心了,做不来,于是去参考别人的题解。

虽然不会正解,但是看看数据暴力还是能过几个点的。

一个简单直白的思路就是预处理出小A和小B从每个点出发的去往的城市,然后对于这个处理,我们可以用线段树来处理。

用了线段树维护之后就可以打倍增啦!

设f[i][j][0]表示从城市i出发,小A经过2^j轮之后走到路程,f[i
][j][1]则表示小B走的路程,g[i][j]表示走到哪个城市。

第一个询问暴力枚举起点,第二个询问倍增。

代码如下

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define DB double
using namespace std;
const int M=100010,inf=0x7fffffff;
DB ans,sum;
ll n,m,tot,x,y,i,j,k,a1,a2;
ll h[M],a[M],b[M],w[M],g[M][18],f[M][18][2],v[M];
struct node{ll d,v;}p[M];
struct Node{int mx,mn;}t[M<<3];
inline bool cmp(node aa,node bb){return aa.v<bb.v||aa.v==bb.v&&aa.d<bb.d;}
inline void mdf(int k,int l,int r,int x){
    if(l==r){t[k].mx=t[k].mn=l;return;}
    int mid=l+r>>1;
    if(x<=mid) mdf(k<<1,l,mid,x);
    else mdf(k<<1|1,mid+1,r,x);
    t[k].mx=max(t[k<<1].mx,t[k<<1|1].mx);
    t[k].mn=min(t[k<<1].mn,t[k<<1|1].mn);
}
inline int getmx(int k,int l,int r,int x,int y){
    if(x>y) return 0;
    if(l==x&&r==y) return t[k].mx;
    int mid=l+r>>1;
    if(y<=mid) return getmx(k<<1,l,mid,x,y);
    else if(x>mid) return getmx(k<<1|1,mid+1,r,x,y);
    else return max(getmx(k<<1,l,mid,x,mid),getmx(k<<1|1,mid+1,r,mid+1,y));
}
inline int getmn(int k,int l,int r,int x,int y){
    if(x>y) return n+1;
    if(l==x&&r==y) return t[k].mn;
    int mid=l+r>>1;
    if(y<=mid) return getmn(k<<1,l,mid,x,y);
    else if(x>mid) return getmn(k<<1|1,mid+1,r,x,y);
    else return min(getmn(k<<1,l,mid,x,mid),getmn(k<<1|1,mid+1,r,mid+1,y));
}
inline void qu(int x,int y){
    for(a1=a2=0,j=17;j>=0;j--)
        if(f[x][j][0]+f[x][j][1]<=y)
            y-=f[x][j][0]+f[x][j][1],a1+=f[x][j][0],a2+=f[x][j][1],x=g[x][j];
    if(f[x][0][0]<=y) a1+=f[x][0][0];
}
int main(){
    //freopen("out.out","w",stdout);
    for(scanf("%lld",&n),i=1;i<=n;i++) scanf("%lld",&v[i]),p[i].v=v[i],p[i].d=i;
    for(sort(p+1,p+n+1,cmp),v[0]=inf,i=1;i<=n;i++) h[p[i].d]=++tot,w[tot]=p[i].d;
    for(i=1;i<=n*5;i++) t[i].mn=n+1;
    for(i=n;i>=1;i--){
        p[1].d=getmn(1,1,n,h[i]+1,n);p[2].d=getmx(1,1,n,1,h[i]-1);
        p[3].d=getmn(1,1,n,p[1].d+1,n);p[4].d=getmx(1,1,n,1,p[2].d-1);
        for(j=1;j<=4;j++) p[j].v=abs(v[i]-v[w[p[j].d]]);
        sort(p+1,p+5,cmp);
        if(p[1].d!=0&&p[1].d!=n+1) b[i]=w[p[1].d];
        if(p[2].d!=0&&p[2].d!=n+1) a[i]=w[p[2].d];
        mdf(1,1,n,h[i]);
    }for(i=1;i<=n;i++)
        g[i][0]=b[a[i]],
        f[i][0][0]=abs(v[i]-v[a[i]]),
        f[i][0][1]=abs(v[a[i]]-v[b[a[i]]]);
    for(j=1;j<=17;j++)  for(i=1;i<=n;i++)
        g[i][j]=g[g[i][j-1]][j-1],
        f[i][j][0]=f[i][j-1][0]+f[g[i][j-1]][j-1][0],
        f[i][j][1]=f[i][j-1][1]+f[g[i][j-1]][j-1][1];
    for(scanf("%lld",&x),k=0,ans=inf,i=1;i<=n;i++){
        qu(i,x);if(!a2) sum=inf;else sum=a1*1.0/a2;
        if(sum<ans||sum==ans&&v[i]>v[k]) ans=sum,k=i;
    }for(printf("%lld\n",k),scanf("%lld",&m);m--;){
        scanf("%d%d",&x,&y);qu(x,y);
        printf("%lld %lld\n",a1,a2);
    }return 0;
}

vijos1782

vijos地址:https://vijos.org/p/1782

描述

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

输入格式

第一行包含两个正整数n,m,表示天数和订单的数量。
第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。
接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。
每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数0。否则(订单无法完全满足)输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。

输入样例

4 3
2 5 4 3
2 1 3
3 2 4
4 2 4

输出样例

-1
2

限制

每个测试点1s。

提示

对于10%的数据,有1≤ n,m≤ 10;
对于30%的数据,有1≤ n,m≤1000;
对于70%的数据,有1≤ n,m≤ 10^5;
对于100%的数据,有1≤n,m≤10^6,0≤ri,dj≤10^9,1≤sj≤tj≤n。

来源

Noip2012提高组复赛Day2T2

做法

一看数据100W,再看看输出的格式,明显是个二分题。

每次枚举第几份订单不能达成,枚举检验答案时前缀和。判一下有没有mid前某天前缀和大于r[i]。

代码简单好写。

代码如下

#include <bits/stdc++.h>
#define M 1000010
#define ll long long
#define G ch=getchar()
#define C(a) memset(a,0LL,sizeof(a))
using namespace std;
int s[M],t[M],d[M],r[M];
int n,m,i,j,x,y,ans;ll a[M];
inline int rd(){
    int x=0;char G;for(;ch<48||ch>57;) G;
    for(;ch>47&&ch<58;) x=x*10+ch-48,G;return x;
}
inline bool ok(int k){
    for(C(a),i=1;i<=k;i++)
        a[s[i]]+=(ll)d[i],a[t[i]+1]-=(ll)d[i];
    for(ll t=0,i=1;i<=n;i++){
        t+=a[i];
        if(t>r[i]) return 0;
    }return 1;
}
int main(){
    for(scanf("%d%d",&n,&m),i=1;i<=n;i++) r[i]=rd();
    for(i=1;i<=m;i++) d[i]=rd(),s[i]=rd(),t[i]=rd();
    for(x=1,y=m;x<y;){
        int mid=x+y>>1;
        if(!ok(mid)) ans=mid,y=mid;
        else x=mid+1;
    }if(ans) printf("-1\n%d\n",ans);
    else puts("0");return 0;
}

vijos1783

vijos地址:https://vijos.org/p/1783

描述

H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。

H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。

现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。

请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

输入格式

第一行一个整数n,表示城市个数。

接下来的n-1行,每行3个整数,u、v、w,每两个整数之间用一个空格隔开,表示从城市u到城市v有一条长为w的道路。数据保证输入的是一棵树,且根节点编号为1。

接下来一行一个整数m,表示军队个数。

接下来一行m个整数,每两个整数之间用一个空格隔开,分别表示这m个军队所驻扎的城市的编号。

输出格式

共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。

输入样例

4
1 2 1
1 3 2
3 4 3
2
2 2

输出样例

3

做法

这道题也是恶心的可以,贪心还能这样
我不会正解。

求最大最小的首选二分。

首先贪心是可以理解的,每支军队都争取往首都走,这样就可以控制更多的城市,关键就是都往上走之后怎么办。

既然是越往上越优,那么我们都往上。

现在就分两种情况了:

1、到达不了根节点:那就到达他最多能到的节点,然后打一个标记。

2、能到达根节点:把所有能到达根节点的点记下来排个序,然后我们把根节点没打标记的子节点放在另一个数组里面排一个序,最后比较。

如果当前这个军队从x走到了根节点,然后派到了其他地方,但是别的节点到不了x,这个把当前这支军队派到x。还有,标记之后要把整棵树重新标记一下,如果一个节点的所有子节点都被标记了,那么这个点就要被标记。

树上跳点打一个倍增就好了。

代码如下

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=100010,inf=0x3fffffff;
typedef long long ll;
struct node{ll v,w;}b[M],c[M];
ll n,x,y,z,k,m,i,j,tot,l,r,mid;
ll he[M],ne[M<<1],to[M<<1],g[M][17],a[M],dep[M],f[M][17],v[M<<1],f1[M];
inline bool cmp(node x,node y) {return x.v<y.v;}
inline void add(int x,int y,int z){
    to[++tot]=y;ne[tot]=he[x];he[x]=tot;v[tot]=z;
}
inline void dfs(int x,int y) {
    for(int i=he[x];i;i=ne[i]) if(to[i]!=y) {
        dep[to[i]]=dep[x]+1;g[to[i]][0]=x;f[to[i]][0]=v[i];dfs(to[i],x);
    }
}
void cal(int x) {
    int p=1,q=0,i;
    for(i=he[x];i;i=ne[i])
        if(to[i]!=g[x][0])
            cal(to[i]),p&=f1[to[i]],q=1;
    if(p&&q&&x!=1) f1[x]=1;
}
bool ok(ll x) {
    memset(f1,0,sizeof(f1));
    int tot=0,top=0,i,j;
    for(i=1;i<=m;i++){
        for(y=a[i],z=0,k=a[i],j=16;j>=0;j--) 
            if(g[y][j]&&z+f[y][j]<=x) z+=f[y][j],y=g[y][j];
        if(y!=1) f1[y]=1;
        else{
            for(b[++tot].v=x-z,y=a[i],j=16;j>=0;j--)
                if(g[y][j]>1) y=g[y][j];b[tot].w=y;
        }
    }for(cal(1),i=he[1];i;i=ne[i])
        if(!f1[to[i]]) c[++top].v=v[i],c[top].w=to[i];
    sort(b+1,b+tot+1,cmp);sort(c+1,c+top+1,cmp);
    for(j=1,c[top+1].v=inf,i=1;i<=tot;i++) {
        if(!f1[b[i].w]) f1[b[i].w]=1;
        else if(b[i].v>=c[j].v) f1[c[j].w]=1;
        for(;f1[c[j].w];) j++;
    } if(j>top) return 1;
    else return 0;
}
int main(){
    for(scanf("%lld",&n),i=1;i<=n-1;i++)
        scanf("%lld%lld%lld",&x,&y,&z),add(x,y,z),add(y,x,z),r+=z;
    for(scanf("%lld",&m),i=1;i<=m;i++) scanf("%d",&a[i]);
    for(dep[1]=1,dfs(1,0),j=1;j<=16;j++)
        for(i=1;i<=n;i++)
            g[i][j]=g[g[i][j-1]][j-1],
            f[i][j]=f[g[i][j-1]][j-1]+f[i][j-1];
    for(;l<r&&(mid=(l+r)/2);)
        if(ok(mid)) r=mid;
        else l=mid+1;
    if(ok(l)) printf("%lld",l);
    else printf("-1");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值