NOIP2016TGDay1

—————恭喜skt和ssg会师鸟巢T_T—————

总分:228 其实可以更高的


T1 玩具谜题


Description

由于题目过于简单,各位大佬显然可以一分钟A掉,故不提供原题


解题思路

这题初看时很蒙,完全被图片迷惑了。
这里写图片描述
自古弓兵多挂逼,自古枪兵幸运E
咳,扯远了。
这道题其实就是一道圆圈游戏题,加一下,减一下,膜一下就好了。
附上代码:

#include<bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 100005
int n,m;
string A[M];
int Dir[M];
int main(){
    scanf("%d%d",&n,&m);
    FOR(i,0,n-1){
        scanf("%d",&Dir[i]);
        cin>>A[i];
        if(Dir[i]==0)Dir[i]=-1;
    }
    int a,b;
    int st=0;
    FOR(i,1,m){
        scanf("%d%d",&a,&b);
        if(a==0)a=-1;
        if(a*Dir[st]==-1){
            st=(st+b)%n;
        }else {
            st=(st-b+n)%n;
        }
    }
    cout<<A[st]<<endl;
    return 0;
}

T2 天天爱跑跑

Description

小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含 n 个结点和 n − 1 条边的树,每条边连接两个结 点,且任意两个结点存在一条路径互相可达。树上结点编号为从 1 到 n 的连续正整数。 现在有 m 个玩家,第 i 个玩家的起点为 S i ,终点为 Ti 。 每天打卡任务开始时,所 有玩家在第 0 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短 路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)小C想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。 在结点 j 的观 察员会选择在第 Wj 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 Wj 秒也正好到达了结点 j 。 小C想知道每个观察员会观察到多少人?
注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点 j 作为终点的玩家:若他在第 Wj 秒前到达 终点,则在结点 j
的观察员不能观察到该玩家;若他正好在第 Wj 秒到达终点,则在结 点 j 的观察员可以观察到这个玩家。

解题思路

其实这道题最后我抄了题解暴力挺好打的,裸分就有80,可惜由于时间关系,我只拿了60分,暴力就是从每个玩家的起点dfs一遍,然后从终点往上爬,跟起点距离恰好为这个点的观察时间的,则这个点的答案+1。

struct P25{
    int dis[1005],fa[1005],ans[1005];
    void dfs(int x,int f,int cost){
        dis[x]=cost;
        fa[x]=f;
        FOR(i,0,G[x].size()-1){
            int y=G[x][i];
            if(y==f)continue;
            dfs(y,x,cost+1);
        }
    }
    void solve(){
        int st,ed;//前面的Input省略
        FOR(i,1,m){
            scanf("%d%d",&st,&ed);
            dfs(st,0,0);
            while(ed!=st){
                if(dis[ed]==W[ed])ans[ed]++;
                ed=fa[ed];
            }
            if(dis[st]==W[st])ans[st]++;//注意起点是没有被算过的
        }
        FOR(i,1,n)printf("%d ",ans[i]);
    }
}p25;

接下来是链的做法,其实也很简单,其实可以发现每个观察员只能向起点为i-w[i]和i+w[i]的选手要状态,瞎搞一下想到这就有链的15分了。

struct P_link{
    vector<int>Pos[Sm];
    int ans[Sm];
    void solve(){
        FOR(i,1,n)Pos[i].clear();
        int st,ed;
        FOR(i,1,m){
            scanf("%d%d",&st,&ed);
            Pos[st].push_back(ed);
        }
        FOR(i,1,n)sort(Pos[i].begin(),Pos[i].end());
        FOR(i,1,n){
            int res=0;
            if(i-W[i]){
                int s=i-W[i];
                int id=lower_bound(Pos[s].begin(),Pos[s].end(),i)-Pos[s].begin();
                res+=(int)Pos[s].size()-id;
            }
            if(i+W[i]<=n){
                int s=i+W[i];
                int id=upper_bound(Pos[s].begin(),Pos[s].end(),i)-Pos[s].begin();
                res+=id;
            }
            ans[i]=res;
        }
        FOR(i,1,n)printf("%d ",ans[i]);
    }
}p_link;

接下来是起点为1的情况。这种情况其实很无脑,因为一个观察员能观察到选手当且仅当他与1节点的距离等于他的观察时间。用一下树上差分算出每个选手的覆盖情况就好了。

struct PS1{
    int dep[M],mark[M],sum[M];
    void dfs(int x,int f){
        dep[x]=dep[f]+1;
        FOR(i,0,G[x].size()-1){
            int y=G[x][i];
            if(y==f)continue;
            dfs(y,x);
        }
    }
    void Do(int x,int f){
        FOR(i,0,G[x].size()-1){
            int y=G[x][i];
            if(y==f)continue;
            Do(y,x);
            sum[x]+=sum[y];
        }
    }
    void solve(){
        dfs(1,0);
        FOR(i,1,n){
            dep[i]--;
            if(dep[i]==W[i])mark[i]=1;
        }
        int st,ed;
        FOR(i,1,m){
            scanf("%d%d",&st,&ed);
            sum[ed]++;
        }
        Do(1,0);
        FOR(i,1,n){
//          printf("%d\n",sum[i]);
            if(!mark[i])printf("0 ");
            else printf("%d ",sum[i]);
        }
    }
}pS1;

之后的写法考试时我并没有写,再加上我对图论一窍不通。就附上两份代码。
终点为1节点。

struct PS2{
    int mx,L[M],dep[M],R[M],T,cnt[M];
    vector<int>Q[M];
    void dfs(int x,int f){
        L[x]=++T;
        dep[x]=dep[f]+1;
        if(dep[x]>mx)mx=dep[x];
        if(Q[dep[x]].size()==0)Q[dep[x]].push_back(0);
        Q[dep[x]].push_back(L[x]);
        FOR(i,0,G[x].size()-1){
            int y=G[x][i];
            if(y==f)continue;
            dfs(y,x);
        }
        R[x]=T;
    }
    int S[M];
    void solve(){
        dfs(1,0);
        int u;
        FOR(i,1,m){
            scanf("%d%d",&S[i],&u);
            cnt[L[S[i]]]++;
        }
        FOR(i,1,mx)FOR(j,1,Q[i].size()-1)cnt[Q[i][j]]+=cnt[Q[i][j-1]];
        FOR(i,1,n){
            int tmp=dep[i]+W[i];
            if(tmp>mx)printf("0 ");
            else {
                int l=lower_bound(Q[tmp].begin(),Q[tmp].end(),L[i])-Q[tmp].begin();
                int r=upper_bound(Q[tmp].begin(),Q[tmp].end(),R[i])-Q[tmp].begin()-1;
                printf("%d ",cnt[Q[tmp][r]]-cnt[Q[tmp][l-1]]);
            }
        }
    }
}pS2;

AC代码抄的方法很玄学。
写法和起点终点为1的写法类似。就是把一条路径拆成从起点向上到LCA的路径,和从LCA向下到终点的路径。

#include<bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 300005
#define eM 600006
int n,m;
int W[M];
int St[M],Ed[M],Lca[M],Len[M],cnt[M],ans[M];
int sum[eM];
vector<int>Q1[M];
vector<int>Q2[M];
vector<int>Q3[M];
vector<int>G[M];
void Input(){
    cin>>n>>m;
    int a,b;
    FOR(i,1,n-1){
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    FOR(i,1,n)scanf("%d",&W[i]);
    FOR(i,1,m)scanf("%d%d",&St[i],&Ed[i]);
}
int fa[20][M],dep[M],mx;
void dfs(int x,int f){
    dep[x]=dep[f]+1;
    if(dep[x]>mx)mx=dep[x];
    fa[0][x]=f;
    FOR(i,0,G[x].size()-1){
        int y=G[x][i];
        if(y==f)continue;
        dfs(y,x);
    }
}
void Up(int &x,int step){FOR(i,0,19)if(step&(1<<i))x=fa[i][x];}
int LCA(int a,int b){
    if(dep[a]>dep[b])swap(a,b);
    Up(b,dep[b]-dep[a]);
    if(a==b)return a;
    REP(i,19,0)if(fa[i][a]!=fa[i][b])a=fa[i][a],b=fa[i][b];
    return fa[0][a];
}
void solve1(int x,int f){
    int now=dep[x]+W[x];
    int s=now<=mx?sum[now]:0;
    FOR(i,0,G[x].size()-1){
        int y=G[x][i];
        if(y==f)continue;
        solve1(y,x);
    }
    sum[dep[x]]+=cnt[x];ans[x]=now<=mx?sum[now]-s:0;
    FOR(i,0,Q1[x].size()-1)sum[dep[Q1[x][i]]]--;
}
void solve2(int x,int f){
    int now=dep[x]-W[x];now+=300000;
    int s=sum[now];
    FOR(i,0,G[x].size()-1){
        int y=G[x][i];
        if(y==f)continue;
        solve2(y,x);
    }
    FOR(i,0,Q2[x].size()-1)sum[Q2[x][i]+300000]++;
    ans[x]+=sum[now]-s;
    FOR(i,0,Q3[x].size()-1)sum[Q3[x][i]+300000]--;
}
void solve(){
    dfs(1,0);
    FOR(i,1,19)FOR(j,1,n)fa[i][j]=fa[i-1][fa[i-1][j]];
    FOR(i,1,m){
        Lca[i]=LCA(St[i],Ed[i]);
        Len[i]=dep[St[i]]+dep[Ed[i]]-2*dep[Lca[i]];
        cnt[St[i]]++;
        Q1[Lca[i]].push_back(St[i]);
        Q2[Ed[i]].push_back(dep[Ed[i]]-Len[i]);
        Q3[Lca[i]].push_back(dep[Ed[i]]-Len[i]);
    }
    solve1(1,0);
    memset(sum,0,sizeof(sum));
    solve2(1,0);
    FOR(i,1,m)if(dep[St[i]]-dep[Lca[i]]==W[Lca[i]])ans[Lca[i]]--;
    FOR(i,1,n)printf("%d ",ans[i]);
    puts("");
}
int main(){
    Input();
    solve();
    return 0;
}

T3 换教室


Description

对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的
课程。
在可以选择的课程中,有 2n 节课程安排在 n 个时间段上。 在第 i ( 1 ≤ i ≤ n )个 时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教 室 ci上课,而另一节课程在教室 di 进行。在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 n 节安排好的课程。如果学生想更换第 i 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 i 个时间段去教室 di 上课,否则仍然在教室 ci 上课。
由于更换教室的需求太多,申请不一定能获得通过。 通过计算,牛牛发现申请更 换第 i 节课程的教室时,申请被通过的概率是一个己知的实数 ki ,并且对于不同课程 的申请,被通过的概率是互相独立的。
学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 m 节课程进行申请。 这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请:牛牛可以申请自己最希望更换 教室的 m 门课程,也可以不用完这 m 个申请的机会,甚至可以一门课程都不申请。因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从 一间教室赶到另一间教室。牛牛所在的大学有 v 个教室,有 e 条道路。 每条道路连接两间教室,并且是可 以双向通行的。 由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会 有所不同。 当第 i ( 1 ≤ i ≤ n − 1 )节课结束后,牛牛就会从这节课的教室出发,选择 一条耗费体力最少的路径前往下一节课的教室。
现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。
也只能用题目描述凑字数了

解题思路

当时考试时思路比较迷,用floyd处理最短路后,想到把m个申请一个一个放进去,用dp算最优子结构。三层for能拿88分,然而我只有68分
附上考试时的代码。
88分

#include<bits/stdc++.h>
using namespace std;
#define ee 1e-9
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 2005
typedef double db;
int n,m,v,e;
int C[M],D[M],dis[305][305];
db K[M];
struct P10 {
    db dp[M][M];
    int sum[M];
    db ans;
    void solve() {
        FOR(k,1,v)FOR(i,1,v)FOR(j,1,v)dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]);
        FOR(i,1,v)dp[i][i]=0;
        FOR(i,2,n) {
            sum[i]=sum[i-1]+dis[C[i]][C[i-1]];
        }
        ans=sum[n];
        FOR(i,1,m)FOR(j,1,n)dp[i][j]=1e9;
        FOR(i,1,n) {
            dp[1][i]=sum[i-1]+K[i]*dis[C[i-1]][D[i]]+(1-K[i])*dis[C[i-1]][C[i]];
            db tmp=K[i]*dis[D[i]][C[i+1]]+(1-K[i])*dis[C[i]][C[i+1]];
            db res=0;
            if(i<n)res=1.0*(sum[n]-sum[i+1]);
            if(m!=0&&dp[1][i]+tmp+res<ans)ans=dp[1][i]+tmp+res;//考试忘了m可以等于0,20分就没了
        }
        FOR(i,2,m) {
            FOR(j,1,n) {
                FOR(k,1,j-1) {
                    if(k==j-1) {
                        db tmp1=K[j]*K[k]*dis[D[j]][D[k]];
                        db tmp2=K[j]*(1-K[k])*dis[D[j]][C[k]];
                        db tmp3=(1-K[j])*K[k]*dis[C[j]][D[k]];
                        db tmp4=(1-K[j])*(1-K[k])*dis[C[j]][C[k]];
                        dp[i][j]=min(dp[i][j],dp[i-1][k]+tmp1+tmp2+tmp3+tmp4);
                    } else {
                        db tmp1=K[j]*K[k]*(dis[D[j]][C[j-1]]+dis[D[k]][C[k+1]]);
                        db tmp2=K[j]*(1-K[k])*(dis[D[j]][C[j-1]]+dis[C[k]][C[k+1]]);
                        db tmp3=(1-K[j])*K[k]*(dis[C[j]][C[j-1]]+dis[D[k]][C[k+1]]);
                        db tmp4=(1-K[j])*(1-K[k])*(dis[C[j]][C[j-1]]+dis[C[k]][C[k+1]]);
                        dp[i][j]=min(dp[i][j],dp[i-1][k]+sum[j-1]-sum[k+1]+tmp1+tmp2+tmp3+tmp4);
                    }
                }
            }
            FOR(j,1,n) {
                db tmp=K[j]*dis[D[j]][C[j+1]]+(1-K[j])*dis[C[j]][C[j+1]];
                db res=0;
                if(j<n)res=1.0*(sum[n]-sum[j+1]);
                if(dp[i][j]+tmp+res<ans)ans=dp[i][j]+tmp+res;
            }
        }
        printf("%.2f\n",ans);
    }
} p10;
int main() {
    cin>>n>>m>>v>>e;
    FOR(i,1,n)scanf("%d",&C[i]);
    FOR(i,1,n)scanf("%d",&D[i]);
    FOR(i,1,n)scanf("%lf",&K[i]);
    int a,b,c;
    FOR(i,1,v)FOR(j,i+1,v)dis[i][j]=dis[j][i]=1e9;
    FOR(i,1,v)dis[i][0]=dis[0][i]=0;
    FOR(i,1,e) {
        scanf("%d%d%d",&a,&b,&c);
        dis[a][b]=min(dis[a][b],c);
        dis[b][a]=min(dis[b][a],c);
    }
    p10.solve();
    return 0;
}

正解其实也是dp,不过状态要简单的多,由于每个点的决策只与这个点的前一个点和后一个点有关,dp[i][j][k]表示前i个点用了j次申请机会第i个点是k状态时的最优解,k是0或1,表示第i个点取或不取。考后想想真的简单

#include<bits/stdc++.h>
using namespace std;
#define ee 1e-9
#define FOR(i,a,b) for(register int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(register int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 2005
typedef double db;
int n,m,v,e;
int C[M],D[M],dis[305][305];
db dp[2][M][M];
db K[M];
void Input() {
    cin>>n>>m>>v>>e;
    FOR(i,1,n)scanf("%d",&C[i]);
    FOR(i,1,n)scanf("%d",&D[i]);
    FOR(i,1,n)scanf("%lf",&K[i]);
    int a,b,c;
    FOR(i,1,v)FOR(j,i+1,v)dis[i][j]=dis[j][i]=1e9;
    FOR(i,1,v)dis[i][i]=dis[i][0]=dis[0][i]=0;
    FOR(i,1,e) {
        scanf("%d%d%d",&a,&b,&c);
        dis[a][b]=min(dis[a][b],c);
        dis[b][a]=min(dis[b][a],c);
    }
}
void floyd() {
    FOR(k,1,v)FOR(i,1,v)FOR(j,1,v)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
void solve() {
    db ans=0;
    FOR(i,2,n) {
        ans=ans+dis[C[i]][C[i-1]];
    }
    FOR(i,1,n)FOR(j,0,m)dp[0][i][j]=dp[1][i][j]=1e9;
    dp[1][1][1]=dp[0][1][0]=0;
    FOR(i,2,n) {
        FOR(j,0,m) {
            db tmp1=K[i]*K[i-1]*dis[D[i]][D[i-1]];
            db tmp2=K[i]*(1-K[i-1])*dis[D[i]][C[i-1]];
            db tmp3=(1-K[i])*K[i-1]*dis[C[i]][D[i-1]];
            db tmp4=(1-K[i])*(1-K[i-1])*dis[C[i]][C[i-1]];
            if(j)dp[1][i][j]=min(dp[1][i][j],dp[1][i-1][j-1]+tmp1+tmp2+tmp3+tmp4);
            dp[0][i][j]=min(dp[0][i][j],dp[1][i-1][j]+K[i-1]*dis[D[i-1]][C[i]]+(1-K[i-1])*dis[C[i-1]][C[i]]);
            dp[0][i][j]=min(dp[0][i][j],dp[0][i-1][j]+dis[C[i-1]][C[i]]);
            if(j)dp[1][i][j]=min(dp[1][i][j],dp[0][i-1][j-1]+K[i]*dis[C[i-1]][D[i]]+(1-K[i])*dis[C[i-1]][C[i]]);
        }
    }
    FOR(i,0,m)ans=min(ans,min(dp[0][n][i],dp[1][n][i]));
    printf("%.2f\n",ans);
}
int main() {
    Input();
    floyd();
    solve();
    return 0;
}

这场考试我觉得我尽(meng)力(you)了,分数还是比较满意的,尽管只打了暴力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值