NOIP2016提高组 DAY1

10 篇文章 0 订阅
5 篇文章 0 订阅

T1:

玩具谜题

题目描述

小南有一套可爱的玩具小人,它们各有不同的职业。

有一天,这些玩具小人把小南的眼镜藏了起来。小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。

这时 singer 告诉小南一个谜题:“眼镜藏在我左数第3个玩具小人的右数第1个玩具小人的左数第2个玩具小人那里。”

小南发现,这个谜题中玩具小人的朝向非常关键,因为朝内和朝外的玩具小人的左右方向是相反的: 面朝圈内的玩具小人,它的左边是顺时针方向,右边是逆时针方向;而面向圈外的玩具小人,它的左边是逆时针方向,右边是顺时针方向。

小南一边艰难地辨认着玩具小人,一边数着:

singer 朝内,左数第3个是 archer

archer 朝外,右数第1个是 thinker

thinker 朝外,左数第2个是 writer

“所以眼镜藏在 writer 这里!”

虽然成功找回了眼镜,但小南并没有放心。如果下次有更多的玩具小人藏他的眼镜,或是谜题的长度更长,他可能就无法找到眼镜了。 所以小南希望你写程序帮他解决类似的谜题。这样的谜题具体可以描述为:

有n个玩具小人围成一圈,已知它们的职业和朝向。现在第 1个玩具小人告诉小南一个包含 m条指令的谜题,其中第i条指令形如“左数/右数第si个玩具小人”。

你需要输出依次数完这些指令后,到达的玩具小人的职业。

输入格式

从标准输入读入数据。

输入的第一行包含两个正整数 m,n,表示玩具小人的个数和指令的条数。

接下来n行,每行包含一个整数和一个字符串,以逆时针为顺序给出每个玩具小人的朝向和职业。其中0表示朝向圈内,1表示朝向圈外。 保证不会出现其他的数。字符串长度不超过10且仅由小写字母构成,字符串不为空,并且字符串两两不同。整数和字符串之间用一个空格隔开。

接下来 m行,其中第 i行包含两个整数ai,si,表示第 ii 条指令。若ai=0,表示向左数si个人;若ai=1,表示向右数si个人。 保证ai不会出现其他的数,1≤si<n

输出格式

输出到标准输出。

输出一个字符串,表示从第一个读入的小人开始,依次数完m条指令后到达的小人的职业。

样例一

input
7 3
0 singer
0 reader
0 mengbier
1 thinker
1 archer
0 writer
1 mogician
0 3
1 1
0 2

output
writer

explanation

这组数据就是题目描述中提到的例子。

样例二

input
10 10
1 c
0 r
0 p
1 d
1 e
1 m
1 t
1 y
1 u
0 v
1 7
1 1
1 4
0 5
0 3
0 1
1 6
1 2
0 8
0 4

output
y



正解:模拟
解题报告:
  将题目中的小人朝向和往左右走,看成一个0、1状态,容易发现我们可以通过异或和取模来实现每一步是走向编号变大还是编号变小。

 注意事项:

        涉及到取模时,一定要注意对于n取模之后的值域是[0,n-1],所以最好是一开始就把下标改成从0开始,避免不必要的错误。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <ctime>
#include <set>
#include <vector>
#include <queue>
using namespace std;
const int MAXN = 100011;
int n,m,dir[MAXN];
char ch[MAXN][14];

void work(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        scanf("%d",&dir[i]);
        scanf("%s",ch[i]);
    }
    int x,y,ljh; x=1;
    while(m--) {
        scanf("%d%d",&ljh,&y);
        if(dir[x]==0) {
            if(ljh==0) {
                x+=n; x-=y;
                if(x>n) x%=n;
            }
            else{
                x+=y;
                if(x>n) x%=n;
            }
        }
        else{
            if(ljh==0) {
                x+=y;
                if(x>n) x%=n;
            }
            else{
                x+=n; x-=y;
                if(x>n) x%=n;
            }
        }
    }
    printf("%s",ch[x]);
}

int main()
{
    work();
    return 0;    
}

// Made by zengzihang
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <cctype>
#include <ctime>
#include <queue>
#include <sstream>
#include <vector>
using namespace std;

const int Max=1000010;
long long n,m,head;
struct toy1{char job[105];int fx;};
toy1 a[Max];

int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(c<'0'||c>'9')&&(c!='-');c=getchar());
   if(c=='-') {f=-1; c=getchar();}
   for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

int main()
{
   //freopen("lx.in","r",stdin);
   //freopen("lx.out","w",stdout);

   n=get_int();
   m=get_int();
   for(int i=1;i<=n;i++)
   {
   	 scanf("%d %s\n",&a[i].fx,a[i].job);
   }

   head=1;
   for(int i=1;i<=m;i++)
   {
   	 int x,y;
   	 scanf("%d%d",&x,&y);
   	 if(a[head].fx==0)
   	 {
   	   if(x==0) head-=y;
   	   if(x==1) head+=y;
   	 }
   	 else
   	 {
   	   if(x==0) head+=y;
   	   if(x==1) head-=y;
   	 }
   	 while(head<1) head+=n;
   	 while(head>n) head-=n;;
   }

   cout<<a[head].job<<endl;
   return 0;
}



T2:
天天爱跑步

Description

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

Input

第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。
接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。
接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N
 

Output

输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。

 

Sample Input

6 3
2 3
1 2 
1 4 
4 5 
4 6 
0 2 5 1 2 3 
1 5 
1 3 
2 6

Sample Output

1 2 1 0 1

HINT

 

对于1号点,Wi=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共有2人被观察到。

对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。

对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。

对于4号点,玩家1被观察到,共1人被观察到。

对于5号点,玩家1被观察到,共1人被观察到。

对于6号点,玩家3被观察到,共1人被观察到。
 
 
正解: lca  +统计
解题报告:

 

25分

  考虑此时 n 很小,可以对于每条路径上暴力模拟,经过某个点时可以看一下当前时刻,是否跟经过的点的 w 相等,如果相等,则贡献加一。

 

45分

  注意到测试点 912 时,保证 m 条路径的出发点都是 1 ,那么我们可以考虑如果将 1 作为树根,那么一条路径怎样才能对于它经过的点产生贡献。

  不难看出对于一个点 i i,只有在 deep[i]=w[i] ,才有可能有贡献。

  我在考场上是直接用的链剖 + 线段树,因为这就变成模板题了,而且 n 不到 10w ,尽管复杂度偏高,但是不易错。

  直接对于每条路径经过的点在线段树上增加 1 次经过次数,显然只有 deep w 相等的点才会产生贡献。

  事实上对于 S=1 的情况有线性的算法,正解会详细介绍,不再赘述。

 

60分

  注意到测试点 68 时,题目保证树退化成链。我们观察一下对于链而言,有什么特别的地方。首先要明确,此时m条路径在链上肯定是要么往左要么往右,即 S<=T 或者 S>T

  先只考虑 S<=T 的情况,如果对于 S T 之间的点i,要产生贡献的话,肯定满足 iS=w[i] ,移项可得 S=iw[i] 时才可以满足要求。

  注意到等式右边只与 i i本身有关,不妨设为 K[i] ,所以题目变成了查询 S T 之间 K[i] 等于 S i 的数量。

  因为题目只涉及到首和尾,我们可以很容易联想到差分,即对于 S 打上 +1 标记, T 打上 1 标记。

  根据上述思路,我们考虑具体做法:对于每个点 i i,我们很容易发现只有从一个特定的点出发才有可能对 i i产生贡献。

  我们考虑维护一个统计数组 A A[k] 表示的是处理到当前的结点时,从 k 出发的路径(而且还没有走到终点)有多少条。

  这样对于每个点 i i,我们只要查询一下所对应的 A[K[i]]   就可以了,根据上面的分析,这就是我们的答案了。

  有一点注意处理:处理一个点 i i时,我们需要把以 i i为起点的路径加入统计数组 A ,再计算这个结点的贡献,最后再把以这个结点为终点的路径从 A 中消除,具体可以用 vector  实现(上述处理顺序的必要性仔细想想就很容易想通了)。

  而对于 S>T 的情况完全类似,只是需要把 K[i] 定义为 i+w[i] ,其余做法完全类似。

      

100分

  题目中设计的几个档次的部分分其实暗示已经很明显了。

  链的做法离正解就不远了。

  而 S=1 T=1 是在告诉我们什么呢?

  拆路径!

  很容易发现,一条 S T 的路径可以拆成一条 S LCA 的路径和 LCA T 的路径,然后对于这两条路径,一条往上,一条往下,都可以对应成链的处理方式了!

  考虑对于每条路径,先将其拆分成两条路径(为了简化对 LCA 在两条路径中都出现的各种情况,我们可以先就让 LCA 出现两次,如果最后发现 LCA 是有贡献的,只需 1 即可),同样,我们先只考虑向上的路径。

  如果我们对于 S 在统计数组 A 上打上 1 的标记, LCA 在统计数组 A 上打上 1 的标记,那么题目转化为求一个点的子树和。

  考虑上述做法正确性:因为只有 S LCA 路径之间的点会产生贡献,而当这个点位于路径之间时,子树和会产生 1 的贡献,而在 S 的子树中或者 LCA 的上方都不会产生贡献。

  具体实现呢?

  对于一个点 i ,产生贡献的条件是 deep[S]deep[i]=w[i]      ,同样令 K[i]=deep[i]+w[i]    ,当我们 dfs i 时查询 A[k[i]]    的值即为贡献。

  为了保证正确性,我们思考统计答案的方式和顺序。

  首先我们肯定是在处理完 i i的子树之后再来处理 i (想想就知道了),然后我们需要再把以 i 出发的向上的路径加入统计数组,再进行查询,最后把以 i i为终点的路径所产生的贡献在统计数组 A 中消除即可。

  注意到我们上面维护的仅仅是一个点的深度,由于同一深度的点很多,所以我们查询的时候会发现会把不在同一子树的点统计入答案,那怎么办呢?我们考虑对于一个点要查询子树和,肯定是只要单独地考虑这一个子树的贡献,所以我们可以记录进入 i i A[k[i]]    的值,再在访问完 i i的子树之后统计答案时,看一下此时新的 A[k[i]]    的值。

  容易发现新的值减掉进入时的,才是真正的 i 的子树中的 A[k[i]]    的值。

  这样我们就可以避免把别的子树的答案统计进来了。

  对于向下的点做法类似,有一点复杂的地方就是等式变成了 deep[T]deep[i]=lenw[i]     len  为路径长度),发现如果这样做的话会出现负数,那么我们就把统计数组向右平移 3105 位就可以了。

  上述做法如果采用的是倍增求 LCA 的话,复杂度就是 O(nlogn)

  如果用 tarjan  离线求 LCA 的话,可以做到 O(n+m)

 

 

 注意事项

  对于树上每个结点,统计答案时不能直接查询在统计数组中的对应的路径条数,

  而应该统计 dfs 进入 i 时,和访问完 i i的子树时的变化量。


代码如下
O(nlogn):
//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <ctime>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>
using namespace std;
typedef long long LL;
const int MAXN = 300011;
const int MAXM = 600011; 
int n,m,ecnt,first[MAXN],next[MAXM],to[MAXM],f[MAXN][20],deep[MAXN],ans[MAXN],val[MAXN],tong[MAXN],MAXD,w[MAXN],num[1000011];
vector<int>ljh[MAXN],ljh2[MAXN],ljh3[MAXN];
struct node{ int s,t,lca,len;}a[MAXN];
inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}
inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; }
inline void init(int x,int fa){ for(int i=first[x];i;i=next[i]) { int v=to[i]; if(v==fa) continue; deep[v]=deep[x]+1; init(v,x); f[v][0]=x; } }
inline int lca(int x,int y){
    if(deep[x]<deep[y]) swap(x,y); int t=0; while((1<<t)<=deep[x]) t++; t--;
    for(int i=t;i>=0;i--) if(deep[x]-(1<<i)>=deep[y]) x=f[x][i]; if(x==y) return y;
    for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0];
}

inline void dfs(int x,int fa){
    int now=w[x]+deep[x],cun; if(now<=MAXD) cun=tong[now];
    for(int i=first[x];i;i=next[i]) {
        int v=to[i]; if(v==fa) continue;
        dfs(v,x);
    }
    tong[deep[x]]+=val[x]; if(now<=MAXD) ans[x]=tong[now]-cun;
    for(int i=0,ss=ljh[x].size();i<ss;i++) tong[deep[ljh[x][i]]]--;
}

inline void DFS(int x,int fa){
    int now=deep[x]-w[x],cun; now+=300000; cun=num[now];
    for(int i=first[x];i;i=next[i]) {
        int v=to[i]; if(v==fa) continue;
        DFS(v,x);
    }
    for(int i=0,ss=ljh2[x].size();i<ss;i++) num[300000+ljh2[x][i]]++;
    ans[x]+=num[now]-cun;
    for(int i=0,ss=ljh3[x].size();i<ss;i++) num[300000+ljh3[x][i]]--;
}

inline void work(){  
    n=getint(); m=getint(); int x,y; for(int i=1;i<n;i++) { x=getint(); y=getint(); link(x,y); link(y,x); }
    for(int i=1;i<=n;i++) w[i]=getint(); deep[1]=1; init(1,0); for(int i=1;i<=n;i++) MAXD=max(MAXD,deep[i]);
    for(int j=1;j<=19;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; 
    for(int i=1;i<=m;i++) {
        a[i].s=getint(),a[i].t=getint(),val[a[i].s]++;
        a[i].lca=lca(a[i].s,a[i].t),a[i].len=deep[a[i].s]+deep[a[i].t]-deep[a[i].lca]*2;
        ljh[a[i].lca].push_back(a[i].s);
    }
    dfs(1,0); 
    for(int i=1;i<=m;i++) {
        ljh2[a[i].t].push_back(deep[a[i].t]-a[i].len);
        ljh3[a[i].lca].push_back(deep[a[i].t]-a[i].len);
    }
    DFS(1,0);
    for(int i=1;i<=m;i++) if(deep[a[i].s]-deep[a[i].lca]==w[a[i].lca]) ans[a[i].lca]--;
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);    
}

int main()
{
    work();
    return 0;
}

O(n+m)
//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <ctime>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>
using namespace std;
typedef long long LL;
const int MAXN = 300011;
const int MAXM = 600011; 
int n,m,ecnt,first[MAXN],next[MAXM],to[MAXM],f[MAXN][20],deep[MAXN],ans[MAXN],val[MAXN],tong[MAXN],MAXD,w[MAXN],num[1000011];
int head[MAXN],tt[MAXM],nn[MAXM],father[MAXN],vis[MAXN];
vector<int>ljh[MAXN],ljh2[MAXN],ljh3[MAXN];
struct node{ int s,t,lca,len;}a[MAXN];
inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}
inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; }
inline void LINK(int x,int y){ nn[++ecnt]=head[x]; head[x]=ecnt; tt[ecnt]=y; }
inline int find(int x){ if(father[x]!=x) father[x]=find(father[x]); return father[x]; }
inline void init(int x,int fa){ 
    father[x]=x; vis[x]=1;
    for(int i=head[x];i;i=nn[i]) {
        int v=tt[i];
        if(x==a[v].s&&vis[a[v].t]) a[v].lca=find(a[v].t);
        if(x==a[v].t&&vis[a[v].s]) a[v].lca=find(a[v].s);
    }
    for(int i=first[x];i;i=next[i]) {
        int v=to[i]; if(v==fa) continue; 
        deep[v]=deep[x]+1; init(v,x); father[v]=x;
        f[v][0]=x; 
    }    
}

inline int lca(int x,int y){
    if(deep[x]<deep[y]) swap(x,y); int t=0; while((1<<t)<=deep[x]) t++; t--;
    for(int i=t;i>=0;i--) if(deep[x]-(1<<i)>=deep[y]) x=f[x][i]; if(x==y) return y;
    for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0];
}

inline void dfs(int x,int fa){
    int now=w[x]+deep[x],cun; if(now<=MAXD) cun=tong[now];
    for(int i=first[x];i;i=next[i]) {
        int v=to[i]; if(v==fa) continue;
        dfs(v,x);
    }
    tong[deep[x]]+=val[x]; if(now<=MAXD) ans[x]=tong[now]-cun;
    for(int i=0,ss=ljh[x].size();i<ss;i++) tong[deep[ljh[x][i]]]--;
}

inline void DFS(int x,int fa){
    int now=deep[x]-w[x],cun; now+=300000; cun=num[now];
    for(int i=first[x];i;i=next[i]) {
        int v=to[i]; if(v==fa) continue;
        DFS(v,x);
    }
    for(int i=0,ss=ljh2[x].size();i<ss;i++) num[300000+ljh2[x][i]]++;
    ans[x]+=num[now]-cun;
    for(int i=0,ss=ljh3[x].size();i<ss;i++) num[300000+ljh3[x][i]]--;
}

inline void work(){  
    n=getint(); m=getint(); int x,y; for(int i=1;i<n;i++) { x=getint(); y=getint(); link(x,y); link(y,x); }
    for(int i=1;i<=n;i++) w[i]=getint(); ecnt=0;
    for(int i=1;i<=m;i++) { a[i].s=getint(),a[i].t=getint(),val[a[i].s]++; LINK(a[i].s,i); LINK(a[i].t,i);}
    deep[1]=1; init(1,0); for(int i=1;i<=n;i++) MAXD=max(MAXD,deep[i]);
    for(int j=1;j<=19;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; 
    for(int i=1;i<=m;i++) {
        a[i].len=deep[a[i].s]+deep[a[i].t]-deep[a[i].lca]*2;
        ljh[a[i].lca].push_back(a[i].s);
    }
    dfs(1,0); 
    for(int i=1;i<=m;i++) {
        ljh2[a[i].t].push_back(deep[a[i].t]-a[i].len);
        ljh3[a[i].lca].push_back(deep[a[i].t]-a[i].len);
    }
    DFS(1,0);
    for(int i=1;i<=m;i++) if(deep[a[i].s]-deep[a[i].lca]==w[a[i].lca]) ans[a[i].lca]--;
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);    
}

int main()
{
    work();
    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)节课结束后,牛牛就会从这节课的教室出发,选择一
条耗费体力最少的路径前往下一节课的教室。现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体
力值的总和的期望值最小,请你帮他求出这个最小值。

Input

第一行四个整数n,m,v,e。n表示这个学期内的时间段的数量;m表示牛牛最多可以申请更换多少节课程的教室;
v表示牛牛学校里教室的数量;e表示牛牛的学校里道路的数量。
第二行n个正整数,第i(1≤i≤n)个正整数表示c,,即第i个时间段牛牛被安排上课的教室;保证1≤ci≤v。
第三行n个正整数,第i(1≤i≤n)个正整数表示di,即第i个时间段另一间上同样课程的教室;保证1≤di≤v。
第四行n个实数,第i(1≤i≤n)个实数表示ki,即牛牛申请在第i个时间段更换教室获得通过的概率。保证0≤ki≤1。
接下来e行,每行三个正整数aj,bj,wj,表示有一条双向道路连接教室aj,bj,通过这条道路需要耗费的体力值是Wj;
保证1≤aj,bj≤v,1≤wj≤100。
保证1≤n≤2000,0≤m≤2000,1≤v≤300,0≤e≤90000。
保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。
保证输入的实数最多包含3位小数。
 

Output

输出一行,包含一个实数,四舎五入精确到小数点后恰好2位,表示答案。你的
输出必须和标准输出完全一样才算正确。
测试数据保证四舎五入后的答案和准确答案的差的绝对值不大于4*10^-3。(如果你不知道什么是浮点误差,这段话
可以理解为:对于大多数的算法,你可以正常地使用浮点数类型而不用对它进行特殊的处理)
 

Sample Input

3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5 
1 2 5
1 3 3
2 3 1

Sample Output

2.80
 
 
 
正解:概率DP+floyd
解题报告:

24分

  注意到有6个测试点m=0,则说明不能提出申请,那么只需要求出全图的两两之间的最短路,路径唯一确定。

 

52分

  注意到另外有7个测试点m=1,只能提出一次申请。我们可以直接枚举在哪里提出申请,再与不申请的时候取一个min就可以了。

 

76分

  注意到还有6个测试点m=2,只能提出两次申请,暴力枚举哪两个点申请即可。

 

80分

  其实我们并不需要分那么多类情况讨论,考虑直接爆搜,在每个点是否提出申请,最后暴力计算贡献。因为这样的复杂度是C(n,m)的,所以m<=2和n<=10是完全没有问题的,直接可以用搜索拿到80分。

 

100分

  这显然是一道概率DP裸题。考虑f[i][j][0、1]表示前i堂课,已经申请了j次,这次申不申请的最小期望值,则:

f[i][j][0]=min(f[i1][j][0]+dis(c[i1],c[i])f[i1][j][1]+dis(c[i1],c[i])(1p[i1])+dis(d[i1],c[i])p[i1])

 

f[i][j][1]=min(f[i1][j1][0]+dis(c[i1],d[i])p[i]+dis(c[i1],c[i])(1p[i]),f[i1][j1][1]+dis(c[i1],c[i])(1p[i1])(1p[i])+dis(c[i1],d[i])(1p[i1])p[i]+dis(d[i1],c[i])p[i1](1p[i]))+dis(d[i1],d[i])p[i1]p[i])

  

 (dis(i,j)表示i到j的最短距离)

 

  需要说明的是,期望是可以线性相加的,也就是说一条路径上的距离总期望值实际上就等于每两个相邻点之间的距离的期望值的总和。另外上面的转移式可以这样理解:在我们已经提出申请的情况下,对于所有情况的讨论就是在现有的申请情况下的能得到的距离期望值,事实上是根据计算了每条边的贡献。

  这样即可获得满分。时间复杂度:O(V3+nm)

 

注意事项

      如果状态的0、1表示的是地点在c还是d的话会有不少问题,因为压根就不能体现出申请的时候的成功与失败结果,但是对于m<=2的点基本不会出错,大点的出错概率也不是特别大

代码如下:

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <ctime>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>
using namespace std;
typedef long long LL;
const int MAXN = 2011;
const int MAXD = 311;
const int inf = (1<<29); 
int n,m,D,bian,c[MAXN],d[MAXN],dis[MAXD][MAXD];
double f[MAXN][MAXN][2],k[MAXN],ans;

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void work(){
    n=getint(); m=getint(); D=getint(); bian=getint(); int x,y,z,lim; double minl;
    for(int i=1;i<=n;i++) c[i]=getint(); for(int i=1;i<=n;i++) d[i]=getint(); for(int i=1;i<=n;i++) scanf("%lf",&k[i]);
    for(int i=1;i<=D;i++) for(int j=1;j<=D;j++) dis[i][j]=inf;
    for(int i=1;i<=bian;i++) {
        x=getint(); y=getint(); z=getint();    if(dis[x][y]==inf) dis[x][y]=dis[y][x]=z;
        else dis[x][y]=min(dis[x][y],z),dis[y][x]=dis[x][y];
    }
    for(int l=1;l<=D;l++) for(int i=1;i<=D;i++) if(i!=l) for(int j=1;j<=D;j++) if(j!=l&&i!=j) dis[i][j]=min(dis[i][l]+dis[l][j],dis[i][j]); 
    for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) f[i][j][0]=f[i][j][1]=1e30; f[1][0][0]=f[1][1][1]=0; 
    for(int i=1;i<=D;i++) dis[i][i]=0;
    for(int i=2;i<=n;i++) {
        lim=min(i,m);
        for(int j=0;j<=lim;j++) {
            minl=f[i-1][j][1]+dis[c[i-1]][c[i]]*(1.0-k[i-1])+dis[d[i-1]][c[i]]*k[i-1];
            minl=min(minl,f[i-1][j][0]+dis[c[i-1]][c[i]]);
            f[i][j][0]=min(f[i][j][0],minl);
            if(j>=1) {
                minl=f[i-1][j-1][1]+dis[c[i-1]][c[i]]*(1.0-k[i])*(1.0-k[i-1])+dis[c[i-1]][d[i]]*(1.0-k[i-1])*k[i];
                minl+=dis[d[i-1]][c[i]]*k[i-1]*(1-k[i])+dis[d[i-1]][d[i]]*k[i-1]*k[i];
                minl=min(minl,f[i-1][j-1][0]+dis[c[i-1]][d[i]]*k[i]+dis[c[i-1]][c[i]]*(1.0-k[i]));
                f[i][j][1]=min(f[i][j][1],minl);
            }
        }
    }
    ans=1e30; for(int i=0;i<=m;i++) ans=min(ans,min(f[n][i][0],f[n][i][1]));
    printf("%.2lf",ans);
}

int main()
{
    work();
    return 0;
}

// Made by zengzihang
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <cctype>
#include <ctime>
#include <vector>
#include <queue>
#include <sstream>
using namespace std;

const int Max=2010;
int n,m,v,e;
int dis[Max][Max];
int c[Max],d[Max];
double ans,p[Max],f[Max][Max][2];

int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(c<'0'||c>'9')&&(c!='-');c=getchar());
   if(c=='-') {f=-1;c=getchar();}
   for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

int main()
{
   //freopen("lx.in","r",stdin);
   //freopen("lx.out","w",stdout);

   n=get_int();
   m=get_int();
   v=get_int();
   e=get_int();
   for(int i=1;i<=n;i++) c[i]=get_int();
   for(int i=1;i<=n;i++) d[i]=get_int();
   for(int i=1;i<=n;i++) scanf("%lf",&p[i]);
   for(int i=1;i<=v;i++)
     for(int j=1;j<=v;j++)
       if(i!=j) dis[i][j]=1e+8;
   for(int i=1;i<=e;i++)
   {
   	 int x,y,z;
   	 x=get_int();
   	 y=get_int();
   	 z=get_int();
   	 dis[x][y]=min(dis[x][y],z);
   	 dis[y][x]=min(dis[y][x],z);
   }

   for(int k=1;k<=v;k++)
     for(int i=1;i<=v;i++)
       for(int j=1;j<=v;j++)
         dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

   for(int i=1;i<=n;i++)
     for(int j=0;j<=m;j++)
       f[i][j][0]=f[i][j][1]=1e+8;
   f[1][1][1]=f[1][0][0]=0;

   for(int i=1;i<n;i++)
	 for(int j=0;j<=m;j++)
 	 {
		f[i+1][j][0]=min(f[i][j][1]+p[i]*dis[d[i]][c[i+1]]+(1.0-p[i])*dis[c[i]][c[i+1]],f[i][j][0]+dis[c[i]][c[i+1]]);
		f[i+1][j+1][1]=min(f[i][j][1]+p[i]*p[i+1]*dis[d[i]][d[i+1]]+(1.0-p[i])*p[i+1]*dis[c[i]][d[i+1]]+p[i]*(1.0-p[i+1])*dis[d[i]][c[i+1]]+(1.0-p[i])*(1.0-p[i+1])*dis[c[i]][c[i+1]],f[i][j][0]+p[i+1]*dis[c[i]][d[i+1]]+(1.0-p[i+1])*dis[c[i]][c[i+1]]);
	 }

   ans=1e+8;
   for(int i=0;i<=m;i++) 
     ans=min(ans,min(f[n][i][0],f[n][i][1]));
   printf("%0.2f",ans);

   return 0;
}

动态规划是近年来NOIP的热点,这道概率DP算是新题型,未来几年可能考到,有关概率DP的经典题和技巧详见

http://blog.csdn.net/auto_ac/article/details/9907881

总结得很好,可以参考一下


以上题解及部分代码分别转载自ljh2000的博客

http://www.cnblogs.com/ljh2000-jump/p/6189050.html

http://www.cnblogs.com/ljh2000-jump/p/6189054.html

http://www.cnblogs.com/ljh2000-jump/p/6189053.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值