1. 程式人生 > >NOIP2017提高組DAY1題解

NOIP2017提高組DAY1題解

T1:小凱的疑惑

考察知識:數學,數論

演算法難度:XXX 實現難度:X

分析:這是一個推(cai)結(da)論(an)的題

一看資料範圍,就知道應該用時間複雜度O(n)或以下的演算法,如果猜有些或許你會發現答案就是ab-a-b,注意用long long

程式碼:

#include<iostream>
int main(){
    long long a,b;
    std::cin>>a>>b;
    std::cout<<a*b-a-b;
    return 0;
}

T2:時間複雜度

考察知識:模擬,棧

演算法難度:XXX 實現難度:XXX+

分析:一個在NOIP中中等規模的模擬類題目

大致思路:我們採用棧結構動態儲存每一層的時間複雜度,並在當前層更新時間複雜度。

這道題保證x<=y,更加簡化了這道題的難度

程式碼:

#include<map>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
map<string,int>mp;
int T,L,top,cur[105];
char O[255],F[255],var[105][255],x[255],y[255];
void check(){
    int index=0,index2=0,index_now=0;//時間複雜度指數 
    bool CE=false;
    memset(cur,0,sizeof(cur));
    top=0,cur[0]=1,mp.clear();
    scanf("%d%s",&L,O);
    for(int i=1;i<=L;i++){
        scanf("%s",F);
        if(F[0]=='F'){
            scanf("%s%s%s",var[++top],x,y);
            if(mp[var[top]]==2018) CE=true;//判斷變數是否重名 
            else mp[var[top]]=2018;
            int X=0,Y=0;//迴圈次數 
            if(x[0]=='n') X=2018;
            else for(int i=0;x[i]!='\0';i++) X=X*10+x[i]-'0';
            if(y[0]=='n') Y=2018;
            else for(int i=0;y[i]!='\0';i++) Y=Y*10+y[i]-'0';
            if(X>Y||cur[top-1]==0) cur[top]=0;//無法進入更深的迴圈 
            else if((X<=Y&&Y<2018)||X==Y) cur[top]=1;//當前時間複雜度O(1) 
            else cur[top]=2;//O(n) 
//			if(cur[top-1]==0) cur[top]=0;
            if(cur[top]==2) index=max(index,++index_now);//當前最大時間複雜度 
        } else {
            if(cur[top]==2) index_now--;
            mp[var[top--]]=0;//銷燬變數 
        }
    }
    if(top||CE) {printf("ERR\n");return;}
    if(O[2]=='n'){
        for(int i=4;isdigit(O[i]);i++) index2=index2*10+O[i]-'0';
    }
    if(index==index2) printf("Yes\n");
    else printf("No\n");
}
int main(){
    scanf("%d",&T);
    while(T--) check();
    return 0;
}

T3:逛公園

考察知識:圖論,記憶化搜尋

演算法難度:XXXX 實現難度:XXXX

分析:

遇到難題先看資料範圍,有3個K=0的點,就是最短路徑計數模板,所以我們至少可以得30分。

對於非搜尋類難題,資料範圍就算小也不一定想得出部分分演算法,但是注意到這道題K<=50,我們可以考慮用記憶化的方法儲存每個點比最短路大k的路徑數,然後採用記憶化搜尋即可。

這種方法類似於動態規劃,所以我們還是用動態規劃一般套路來描述:

定義:f(i,k)表示節點 i 到 n 所有路徑中路徑長度小於等於最短路徑加 k 的路徑數

狀態轉移:

設V是節點 i 可以直接到達的節點的集合,d_i表示 i 到 n 的最短路徑長度,c_{ij}表示節點 i,j 的距離

則有:f(i,k)=\sum_{j\in V}f(j,k-(d_j+c_{ij}-d_i))

邊界:f(n,k)至少等於1

注意事項:

1.由於有多組資料,每次處理完後要初始化

2.由於部分資料含0邊,所以我們設一個vising(i,k)陣列,表示f(i,k)正在被計算,如果繼續搜尋又達到 (i,k)說明有無窮多種情況

程式碼:

#include<map>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char in_c;
template<typename T>
void scan(T &in_n){
    for(in_c=getchar();in_c<'0'||in_c>'9';in_c=getchar());
    for(in_n=0;in_c>='0'&&in_c<='9';in_c=getchar()) in_n=in_n*10+in_c-'0';
}
const int maxn=100005;
struct edge{
    int to,next,l;
}e[maxn*2],e_[maxn*2];
int head[maxn],np,head_[maxn],np_;
void adde(int u,int v,int l){
    e[++np]=(edge){v,head[u],l};
    head[u]=np;
}
void adde_(int u,int v,int l){
    e_[++np_]=(edge){v,head_[u],l};
    head_[u]=np_;
}
int T,n,k,m,P,f[maxn][51],d[maxn];
bool vis[maxn],vising[maxn][51],err;
void dijstra(){//n->1反向搜尋
    priority_queue<pair<int,int> >pq;
    memset(d,0x3f,sizeof(d));
    memset(vis,0,sizeof(vis));
    pq.push(make_pair(0,n)),d[n]=0;
    int i,j,c;
    while(!pq.empty()){
        i=pq.top().second,pq.pop();
        vis[i]=true;
        for(int p=head_[i];p;p=e_[p].next){
            j=e_[p].to,c=e_[p].l;
            if(d[i]+c<d[j]){
                d[j]=d[i]+c;
                if(!vis[j]) pq.push(make_pair(-d[j],j));
            }
        }
    }
}
int dp(int i,int K){
    if(f[i][K]) return f[i][K];
    if(vising[i][K]) err=true;
    if(err) return 0;
    vising[i][K]=true;
    if(i==n) f[i][K]=1;//動態賦值 
    for(int p=head[i];p;p=e[p].next){
        int j=e[p].to,c=e[p].l;
        if(K>=d[j]+c-d[i]){
            int tmp=dp(j,K-d[j]-c+d[i]);
            f[i][K]=(f[i][K]+tmp)%P;
        }
    }
    vising[i][K]=false;//修改結束 
    return f[i][K];
}
void build(){
    int u,v,l;
    np=np_=0,err=false;
    memset(e,0,sizeof(e));
    memset(f,0,sizeof(f));
    memset(head,0,sizeof(head));
    memset(vising,0,sizeof(vising));
    memset(e_,0,sizeof(e_));
    memset(head_,0,sizeof(head_));
    scanf("%d%d%d%d",&n,&m,&k,&P);
    for(int i=1;i<=m;i++)
        scan(u),scan(v),scan(l),
        adde(u,v,l),adde_(v,u,l);
}
void solve(){
    dijstra();
    dp(1,k);
    printf("%d\n",err?-1:f[1][k]);
}
int main(){
    scanf("%d",&T);
    while(T--) build(),solve();
    return 0;
}