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。沒有考出應有的水平,這是比不會寫要傷很多的情況。下次要避免啊。