NOIP2011提高组 DAY2 题解&总结

考试时的心态:


  这次离线赛考的是NOIP2011,考得比较差,其实试卷比较水,水出新高度了。但是就考了160分,还是因为大意了,说实话,我一直在想第二题那个Sigma 是怎么计算的,很虚。虽然最后证明我的想法是正确的,但是由于这道题花的时间太少了,导致我WA了。就30分……
  第三题玄学贪心水了30分,还是比较好的,就是第二题可惜了。

题解:

第一题:计算系数


  这道题是道水题,纯属送分,只要把杨辉三角计算出来,再使用快速幂,处理出系数就可以了。不多解释。
  附上AC代码:

#include
#include
#include
#include
#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<1][m+1]<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<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
#include
#include
#include
#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(y1;
        else l=mid+1;
    }
    cout<return 0;
}

第三题:观光公交:


  这道题是一道贪心题。它的基本决策如下:每一次我们肯定会选择车上人数最多的一条道路使用加速器。但是,如果有人迟到了,那么用了跟没用一样。因此我们就要在没有人会迟到,使用人数最多的那条道路上使用加速器。
  因此我们的贪心决策就很明显了。接下来就是复杂度的问题。
  我们可以看到如果我们对于每一个k都扫一遍进行决策的话,显然要用前缀和进行优化。而倒着使用前缀和显然会给我们的运算带来许多方便。对于这道题,由于我们进行的仅仅是判断与加减语句,复杂度常数非常小。因此O(n*k)的复杂度显然是能过的(虽然有一亿……)。
  附上AC代码:

#include
#include
#include
#include
#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 ainline void checkmax(int &a,int b){if(b>a)a=b;}
inline void checkmin(int &a,int b){if(bint 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<return 0;
}

总结:


  这次考试考得很迷……有很多不该错的地方犯了一些错误。其实第二题我完全没有必要进行对拍,你说前缀和优化和直接循环在结果上有什么区别呢?因此对于一些显然对的算法,就没有必要进行对拍了。对拍反而是徒劳。因此在这种情况下,不如多去考虑一下二分的边界以及自己的代码有没有什么漏洞,能不能用一些数据来进行hack。没有考出应有的水平,这是比不会写要伤很多的情况。下次要避免啊。

你可能感兴趣的:(赛后总结,贪心算法,解题经验)