NOIp 2011 Day2 解题报告

1.计算系数

本人比较耿直,没有想到递推的组合数公式,而是用了快速幂求逆元。

复杂度O(Klog10007)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 
 8 //variable//
 9 int a,b,k,n,m,mod=10007;
10 
11 //function prototype//
12 int qpower(int,int,int);
13 
14 //solve//
15 int main(){
16     scanf("%d%d%d%d%d",&a,&b,&k,&n,&m);
17     int ans=1;
18     ans=(ans*qpower(a,n,mod))%mod;
19     ans=(ans*qpower(b,m,mod))%mod;
20     for (int i=1;i<=k;++i){
21         ans=(ans*i)%mod;
22     }
23     for (int i=1;i<=n;++i){
24         ans=(ans*qpower(i,mod-2,mod))%mod;
25     }
26     for (int i=1;i<=m;++i){
27         ans=(ans*qpower(i,mod-2,mod))%mod;
28     }
29     printf("%d\n",ans);
30     return 0;
31 }
32 
33 int qpower(int a,int b,int m){
34     int ret=1;
35     a%=m;
36     while (b){
37         if (b&1){
38             ret=(ret*a)%m;
39         }
40         b>>=1;
41         a=(a*a)%m;
42     }
43     return ret;
44 }
View Code

2.聪明的质检员

我们看出,随着基准值W的增大,检验值Y减小。于是想到二分。

满足wj≥W的数和这种数的数量都满足前缀和性质。于是对于二分到的W,可以O(N+M)求出每个区间的Y。

总复杂度O((N+M)logN)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 
 8 //variable//
 9 int n,m,l[200100],r[200100],w[200100],v[200100],tot[200100];
10 long long s,sum[200100];
11 
12 //function prototype//
13 bool judge(int);
14 void calc_ans(void);
15 long long calc_y(int);
16 
17 //solve//
18 int main(){
19     scanf("%d%d%lld",&n,&m,&s);
20     for (int i=1;i<=n;++i){
21         scanf("%d%d",w+i,v+i);
22     }
23     for (int i=1;i<=m;++i){
24         scanf("%d%d",l+i,r+i);
25     }
26     calc_ans();
27     return 0;
28 }
29 
30 void calc_ans(){
31     int left=1,right=1000000;
32     while (left<=right){
33         int mid=left+right>>1;
34         if (judge(mid)){
35             left=mid+1;//sum>=s
36         }else{
37             right=mid-1;//sum<s
38         }
39     }
40     long long yl=calc_y(left),yr=calc_y(right);
41     printf("%lld\n",min(s-yl,yr-s));
42 }
43 
44 bool judge(int x){
45     long long y=calc_y(x);
46     return (y>=s);
47 }
48 
49 long long calc_y(int x){
50     tot[0]=0;sum[0]=0ll;
51     for (int i=1;i<=n;++i){
52         sum[i]=sum[i-1];
53         tot[i]=tot[i-1];
54         if (w[i]>=x){
55             sum[i]+=(long long)v[i];
56             ++tot[i];
57         }
58     }
59     long long y=0ll;
60     for (int i=1;i<=m;++i){
61         y+=((long long)tot[r[i]]-tot[l[i]-1])*(sum[r[i]]-sum[l[i]-1]);
62     }
63     return y;
64 }
View Code

3.观光公交

一直到60%的数据都是可以DP的。真正的正解是贪心。

首先考虑如何计算结果,我们令maxArrivei表示从i点出发的人,Ti的最大值,也就是最迟的到达时间,没人从i出发则为0。再令enteri表示到达i点的最早时间,那么

enteri=Max(enteri−1,maxArrivei−1)+Di−1 (3)

那么对于从Ai点到Bi点的旅客i,他用的时间为

enterBi−Ti (4)

那么我们消耗的总时间就是

Summi=1(enterBi−Ti)=(Summi=1enterBi)−(Summ i=1Ti) (5)

由于减去的是一个常量,所以我们只要得到Summi=1enterBi的最小值即可。

设第i个点有cnti个人以该点为终点。那么答案也就是

Sumni=1cnti∗enteri (6)

接下来我们进一步观察enter的计算方法,可以发现3式等价于

enteri=Maxi−1 j=1(maxArrivej+Si−Sj) (7)

其中Si表示1点到i点的距离。

我们令Vi=maxArrivei−Si那么

enteri=Maxi−1 j=1(Vj)+Si (8)

那么考虑减少一个Di,会产生什么影响。可以发现所有的j≤i的点的S,V都没有变化,所有j>i的点,Sj=Sj−1,Vj=Vj+1由于每个点的enteri值都取决于前面最大的V值,如果有多个最大的话,不妨让它被最后一个决定。那么考虑一个点的V值,他假如要决定后面一些enter值的话,必须满足之前的所有V值都不比他大。 我们可以令决定了后面的一些enter值的点分别为a0,a1,a2...ap−1,那么显然他们的V值也是单调不减的(由于条件)。同时易知每个ai必然决定了连续的一个子序列。也就是讲整个序列分成了一块一块,每块都有一个vai决定。ai决定了ai+1∼ai+1这一段的enter值。

我们可以发现,假如我们减少ai∼ai+1−1这些点中一个的D值,那么ai前面的块 完全不受影响,ai+1和之后的块的点x,Maxx−1 j=1(Vj)+了1,但是Sx−了1,所以enter值不变,同时也没有任何其它的影响。所以每个块之间是独立的。 考虑块[l,r],被[l,r]之前一个数决定,那么我们将Dl−1−1,可以将结果减少Sumi=r i=lcnti,同时由于将[l,r]中的数的V值变大了,[l,r]可能被分成几块,那样之后的操作就比之前的 操作烂了(因为每次只能减少一个块的Sumi=r i=lcnti,而块变小了),也就是说对于每个块来说,一次操作能取得的总时间减少量只会越来越少。 那么考虑当前能节省最大时间的块,如果不取他,那么由于之后的操作都比他要劣,所以随便把之后的一个操作换成他就能更优,而那样就不是最优解了。所以一定要取他。

其实只需要开一个堆,每次从堆里取出能减少最大时间的那个块[l,r],消耗k将Dl−1减小,那么有3种情况。

(1):要么Dl−1被减成0,那么就只能减Dl了,将[l+1,r]放进堆

(2):要么k被减完,结束。

(3):要么减到一半时,[l,r]中的一个数变成了新的可以决定后面enter值的数,那么[l,r]分裂。

(1)和(3)都最多出现n次,(2)最多出现1次,每次操作复杂度O(logn)。所以总复杂度 O(m+nlogn)

然而不开堆暴力貌似也是可以的。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 
 7 //variable//
 8 int n,m,k,a[10010],b[10010],c[10010],t[10010];
 9 int r[1010],get[1010],off[1010],in[1010],leave[1010],d[1010],s[1010];
10 long long ans;
11 
12 //solve//
13 int main(){
14     scanf("%d%d%d",&n,&m,&k);
15     for (int i=1;i<n;++i) scanf("%d",d+i);
16     for (int i=0;i<m;++i){
17         scanf("%d%d%d",t+i,a+i,b+i);
18         off[b[i]]++;in[a[i]]++;
19         leave[a[i]]=max(leave[a[i]],t[i]);
20     }
21     for (int i=2;i<=n;++i){
22         get[i]=max(get[i-1],leave[i-1])+d[i-1];
23     }
24     int p=1;
25     for (int i=1;i<=n;++i){
26         while (p<n&&leave[p]<get[p]||p<=i) p++;
27         r[i]=p;
28         s[i]=s[i-1]+off[i];
29     }
30     while (k){
31         int maxp=0,station;
32         for (int i=1;i<n;++i)
33             if (s[r[i]]-s[i]>maxp&&d[i]>0){
34                 maxp=s[r[i]]-s[i];
35                 station=i;
36             }
37         if (maxp==0) break;
38         int maxt=0x7fffffff;
39         for (int j=station+1;j<n&&leave[j]<get[j];++j)
40             maxt=min(maxt,get[j]-leave[j]);
41         int maxDecK=min(d[station],min(maxt,k));
42         k-=maxDecK;
43         d[station]-=maxDecK;
44         for (int j=station+1;j<=r[station];++j)
45             get[j]=max(get[j-1],leave[j-1])+d[j-1];
46         for (int j=p=station;j<r[station];++j){
47             while (p<n&&leave[p]<get[p]||p<=j) p++;
48             if (p>=r[j]) break;
49             r[j]=p;
50         }
51     }
52     int ans=0;
53     for (int i=0;i<m;++i){
54         ans+=(long long)(get[b[i]]-t[i]);
55     }
56     cout<<ans<<endl;
57     return 0;
58 }
View Code

 

posted @ 2015-09-20 18:57  DeAR3327  阅读(149)  评论(0编辑  收藏  举报