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");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章