NOIP2017提高组DAY1题解

T1:小凯的疑惑

考察知识:数学,数论

算法难度:XXX 实现难度:X

分析:这是一个推(cai)结(da)论(an)的题

一看数据范围,就知道应该用时间复杂度O(n)或以下的算法,如果猜有些或许你会发现答案就是ab-a-b,注意用long long

关于结论的证明就参考这里吧:P3951 小凯的疑惑 题解

代码:

#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;
}

 

展开阅读全文
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值