1. 程式人生 > >NOIP2011提高組 DAY2 題解&總結

NOIP2011提高組 DAY2 題解&總結

考試時的心態:


  這次離線賽考的是NOIP2011,考得比較差,其實試卷比較水,水出新高度了。但是就考了160分,還是因為大意了,說實話,我一直在想第二題那個Sigma 是怎麼計算的,很虛。雖然最後證明我的想法是正確的,但是由於這道題花的時間太少了,導致我WA了。就30分……
  第三題玄學貪心水了30分,還是比較好的,就是第二題可惜了。

題解:

第一題:計算係數


  這道題是道水題,純屬送分,只要把楊輝三角計算出來,再使用快速冪,處理出係數就可以了。不多解釋。
  附上AC程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm> #include<iostream> #define M 1050 #define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i) #define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i) using namespace std; const int P=10007; const int S=20; //ÎļþÃû Êä³öµ÷ÊÔ cstdio long long 1LL int a,b,k,m,n; int
dp[M][M];//¼ÆËãÑî»ÔÈý½Ç struct water { void solve() { dp[1][1]=1; FOR(i,2,k+1) { FOR(j,1,i) { dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%P; } } cout<<dp[k+1][m+1]<<endl; } } p50; struct _pAC { int fast(int a,int b) { int
res=1; int x=a; while(b) { if(b&1) { res=(res*x)%P; } b/=2; x=(x*x)%P; } return res; } void solve() { dp[1][1]=1; if(!(a||b||k||n||m)) { puts("0"); exit(0); } FOR(i,2,k+1) { FOR(j,1,i) { dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%P; } } long long ans; ans=(1LL*(fast(a,n)*fast(b,m))%P*dp[k+1][m+1])%P; cout<<ans<<endl; } } pAC; int main() { cin>>a>>b>>k>>n>>m; pAC.solve(); return 0; }

第二題:智障質檢員:


  這道題顯然可以用二分法來寫,要注意的是S屬於[1,1e12],因此要用long long 來儲存。除此之外就是對W進行二分查找了。
  至於為什麼用二分法,我們不難看出,當W變小時,滿足條件的j會變多,Y自然會變大,因此Y與W的關係是單調的。
  之後就是對這個Sigma的處理了。
  這個問題也是比較好處理的。我們只需要用字首和優化一下就好了。
  先預處理出1到i區間中的滿足條件的j的數量cnt[i],同時記錄滿足條件的價值和val[i]。
  對於每一個區間[L,R]只需要計算出(cnt[B[i].R]-cnt[B[i].L-1])*(val[B[i].R]-val[B[i].L-1])即可。
  附上AC程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define ll long long
#define M 200050
#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)
using namespace std;
int n,m;
ll stan;
struct Stone {
    int value,weight;
} A[M];
struct node {
    int L,R;
} B[M];
ll cnt[M];
ll val[M];
long long res;
long long check(int mid){
    cnt[0]=val[0]=0;
    FOR(i,1,n){
        cnt[i]=cnt[i-1];
        val[i]=val[i-1];
        if(A[i].weight>=mid) {
            cnt[i]++;
            val[i]+=A[i].value;
        }
    }
    res=0;
    FOR(i,1,m)res+=1LL*(cnt[B[i].R]-cnt[B[i].L-1])*(val[B[i].R]-val[B[i].L-1]);
    return res;
}
int main() {
    cin>>n>>m>>stan;
    int R=0,L=2e9;
    FOR(i,1,n)scanf("%d%d",&A[i].weight,&A[i].value),R=max(R,A[i].weight),L=min(L,A[i].weight);
    FOR(i,1,m)scanf("%d%d",&B[i].L,&B[i].R);
    int l=0,r=R;
    long long ans=2e14;
    while(l<=r) {
        int mid=(l+r)>>1;
        ll y=check(mid);
        ans=min(ans,abs(y-stan));
        if(y<stan)r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
    return 0;
}

第三題:觀光公交:


  這道題是一道貪心題。它的基本決策如下:每一次我們肯定會選擇車上人數最多的一條道路使用加速器。但是,如果有人遲到了,那麼用了跟沒用一樣。因此我們就要在沒有人會遲到,使用人數最多的那條道路上使用加速器。
  因此我們的貪心決策就很明顯了。接下來就是複雜度的問題。
  我們可以看到如果我們對於每一個k都掃一遍進行決策的話,顯然要用字首和進行優化。而倒著使用字首和顯然會給我們的運算帶來許多方便。對於這道題,由於我們進行的僅僅是判斷與加減語句,複雜度常數非常小。因此O(n*k)的複雜度顯然是能過的(雖然有一億……)。
  附上AC程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ShimaKZ 404 Not Found
#define M 100086
#define max sdjkl
#define min sdjll
#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)
using namespace std;
int n,m,k;
int dis[M];
int leave[M];
int arrive[M];
int sum[M];
int down[M];
int ans=0;
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void checkmax(int &a,int b){if(b>a)a=b;}
inline void checkmin(int &a,int b){if(b<a)a=b;}
int main(){
    cin>>n>>m>>k;
    FOR(i,1,n-1)scanf("%d",&dis[i]);
    FOR(i,1,m){
        int t,a,b;
        scanf("%d%d%d",&t,&a,&b);
        checkmax(leave[a],t);
        ans-=t;
        down[b]++;
    }
    while(k--){
        FOR(i,1,n)arrive[i]=max(arrive[i-1],leave[i-1])+dis[i-1];
        int now=0;
        DOR(i,n,2){
            if(!dis[i-1])sum[i-1]=0;
            else{
                sum[i-1]=down[i];
                if(arrive[i]>leave[i])sum[i-1]+=sum[i];
            }
        }
        int id=0,max=0;
        FOR(i,1,n-1){
            if(sum[i]>max){
                max=sum[i];
                id=i;
            }
        }
        dis[id]--;
    }
    FOR(i,1,n)arrive[i]=max(arrive[i-1],leave[i-1])+dis[i-1];
    FOR(i,1,n)ans+=arrive[i]*down[i];
    cout<<ans<<endl;
    return 0;
}

總結:


  這次考試考得很迷……有很多不該錯的地方犯了一些錯誤。其實第二題我完全沒有必要進行對拍,你說字首和優化和直接迴圈在結果上有什麼區別呢?因此對於一些顯然對的演算法,就沒有必要進行對拍了。對拍反而是徒勞。因此在這種情況下,不如多去考慮一下二分的邊界以及自己的程式碼有沒有什麼漏洞,能不能用一些資料來進行hack。沒有考出應有的水平,這是比不會寫要傷很多的情況。下次要避免啊。