10.4

全国信息学奥林匹克联赛(NOIP2017)复赛模拟

提高组第

2017104 8:10-11:40

(请选手务必仔细阅读本页内容)

 

题目名称

密码

独立集

益智游戏

题目类型

传统

传统

传统

目录

substring

bubble

game

可执行文件名

substring

bubble

game

输入文件名

substring.in

bubble.in

game.in

输出文件名

substring.out

bubble.out

game.out

每个测试点时限

1

1秒

1秒

内存限制

256M

256M

256M

测试点数目

10

20

10

每个测试点分值

10

5

10

 

提交源程序文件名

对于C++语言

substring.cpp

bubble.cpp

game.cpp

对于C语言

substring.c

bubble.c

game.c

对于pascal语言

substring.pas

bubble.pas

game.pas

 

编译选项

对于C++语言

-lm

-lm

-lm

对于C语言

-lm

-lm

-lm

对于pascal语言

 

 

 

 

注意事项

1.文件名(程序名和输入输出文件名)必须使用英文小写。

2.除非特殊说明,结果比较方式均为忽略行末空格及文末回车的全文比较。

3.C/C++中函数main()的返回值类型必须是int,程序正常结束时的返回值必须是0

4.全国统一评测时采用的机器配置为:CPU 2.8GHz,内存4G,上述时限以此配置为准。

5.只提供Linux格式附加样例文件。

6.评测在NOI Linux下进行。

7.编译时不打开任何优化选项。

 

 

 

 

3781---密码

(substring.cpp/c/pas)

 

【问题描述】

    假发通过了不懈的努力,得到了将军家门锁的密码(一串小写英文字母)。但是假发被十四和猩猩他们盯上了,所以假发需要把密码传递出去。因为假发不想十四他们发现几松门前贴的小纸条就是将军家的密码,所以他加密了密码(新八:听起来有点诡异)。加密方法如下:随机地,在密码中任意位置插入随机长度的小写字符串。

不过,假发相信银桑和他那么多年小学同学,一定能猜中密码是什么的(新八:银桑什么时候成攮夷志士了!!!)。可是,写完了小纸条之后,假发觉得有点长,就想截去头和尾各一段(可以为空),让剩下的中间那一段依然包含真~密码。想着想着,假发就想知道有多少种可行方案。结果在沉迷于稿纸之际,假发被投进了狱门岛(新八:……)。于是,就由你计算了。  

【输入】

    两行非空字符串,纯小写英文字母,第一行是加密后的密码,第二行是原密码。

第一行长度不超过300000,第二行不超过200。

【输出】

一行,有多少种方案。注意:不剪也是一种方案。

【输入输出样例1

substring.in

substring.out

abcabcabc

cba

9

【样例1解释】

(L,R)表示一种方案,其中LR分别表示截去头和尾的长度。这9种方案分别是(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)

【输入输出样例2

substring.in

substring.out

abcabcaba

cba

12

【输入输出样例3

substring.in

substring.out

abcabcabac

cba

18

【数据说明】

30%的数据满足第一行长度不超过1000。

100%的数据满足第一行长度不超过300000,方案总数不超过10^18。

【题目分析】

    类型:暴力/暴力

    这道题有很多方法可以过。比如预处理每个位置上一个任意字母在什么位置,然后枚举开始位置,跳m下,就能O(nm)过。同样,也可以暴力设f[n][m]递推过。

30%:纯暴力;

#include<iostream>

#include<cstring>

#include<cstdio>

using namespace std;

string s,t;

int i,j,k,lens,lent,x=-1;

long long Ans=0;

int main()

{  cin>>s>>t;

   lens=s.length();lent=t.length();

   for(i=0;i<lens;i++)

   {  j=0;

      if(s[i]==t[j])

      {  k=i;k++;j++;

         while(k<lens&&j<lent)

           if(s[k]==t[j]){k++;j++;}

                else k++;

         if(k<=lens&&j==lent)

         {  Ans+=(i-x)*(lens-k+1);

            x=i;

         }

      }

      

   }

   cout<<Ans<<endl;

   return 0;

}

3782---独立集

(bubble.cpp/c/pas)

【问题描述】

有一天,一个名叫顺旺基的程序员从石头里诞生了。又有一天,他学会了冒泡排序和独立集。在一个图里,独立集就是一个点集,满足任意两个点之间没有边。于是他就想把这两个东西结合在一起。众所周知,独立集是需要一个图的。那么顺旺基同学创造了一个算法,从冒泡排序中产生一个无向图。

    这个算法不标准的伪代码如下:

Pascal版本

C/C++版本

procedure bubblesortgraph(n, a[])

/*输入:点数n1n的全排列a

输出:一个点数为n的无向图G*/

创建一个有n个点,0条边的无向图G

repeat

   swapped = false

   for i 1 n-1

       if a[i] > a[i + 1]

          在G中连接点a[i]和点a[i + 1]

          交换a[i]a[i + 1]

          swapped = true

until not swapped

输出图G

//结束。

void bubblesortgraph(n,a[])

  //输入:点数n1n的全排列a

  //输出:一个点数为n的无向图G

{  创建一个有n个点,0条边的无向图G

   do{  swapped=false

        for i 1 n-1

          if(a[i]>a[i+1])

           {  在G中连接点a[i]和点a[i+1]

              交换a[i]a[i+1]

              swapped =true

          }

     }while(swapped);

   输出图G

}

//结束。

    那么我们要算出这个无向图G最大独立集的大小。但是事情不止于此。顺旺基同学有时候心情会不爽,这个时候他就会要求你再回答多一个问题:最大独立集可能不是唯一的,但有些点是一定要选的,问哪些点一定会在最大独立集里。今天恰好他不爽,被他问到的同学就求助于你了。

【输入】

    输入包含两行,第一行为N,第二行为1到N的一个全排列。 

输出】

    输出包含两行,第一行输出最大独立集的大小,第二行从小到大输出一定在最大独立集的点的编号。

【输入输出样例】

bubble.in

bubble.out

3

3 1 2

2

2 3

【样例说明】

      

    如上图,顶点12一定在最大独立集中,其对应的编号为23

【数据范围】

    30%的数据满足 N<=16

    60%的数据满足 N<=1,000

100%的数据满足 N<=100,000

【题目分析】

    类型:最长上升子序列

观察给出的伪蟒蛇代码可以发现,当且仅当两个数为逆序对的时候有边。那么对于独立集,两两之间互不为逆序对。同样观察发现这就是上升子序列。那么最大独立集就是最长上升子序列。

至于必定会在最长上升子序列里的元素:

首先要满足它能在最长上升子序列里;

‚其次是以它结尾的最长上升子序列的长度独一无二(否则就可以交换);

这样做两遍最长上升子序列就可以了。

 

#include<iostream>

#include<cstdio>

#include<cstring>

using namespace std;

int d[100005]={0},f[100005]={0},g[100005]={0};

int ha[1000005]={0},a[100005]={0};

int main()

{  int n,i,j,k,len,L,R,mid;

   scanf("%d",&n);

   for(i=1;i<=n;i++)scanf("%d",&a[i]);

   len=0;

   for(i=1;i<=n;i++)

   {  if(a[i]>d[len]){d[++len]=a[i];f[i]=len;}

  else

  {  L=1;R=len;

 while(L<=R)

 {  mid=(L+R)/2;

if(d[mid]>=a[i])R=mid-1;

    else L=mid+1;

 }

 f[i]=L;

 d[L]=a[i];

  }

  }

  len=0;d[0]=0x7fffffff/2;

  for(i=n;i>=1;i--)

  {  if(a[i]<d[len]){d[++len]=a[i];g[i]=len;}

 else

 {  L=1;R=len;

while(L<=R)

{  mid=(L+R)/2;

   if(d[mid]<=a[i])R=mid-1;

       else L=mid+1;

}

g[i]=L;

d[L]=a[i];

 }

  }

  printf("%d\n",len);

  for(i=1;i<=n;i++)if(f[i]+g[i]==len+1)ha[f[i]]++;

  for(i=1;i<=n;i++)

     if(ha[f[i]]==1&&f[i]+g[i]==len+1)printf("%d ",i);

  return 0;

}

 

 

 

4491---益智游戏

game.cpp/c/pas

【问题描述】

P和小R在玩一款益智游戏。游戏在一个正权有向图上进行。

P控制的角色要从A点走最短路到B点,小R控制的角色要从C点走最短路到D点。

一个玩家每回合可以有两种选择,移动到一个相邻节点或者休息一回合。

假如在某一时刻,小P和小R在相同的节点上,那么可以得到一次特殊奖励,但是在每个节点上最多只能得到一次。

    求最多能获得多少次特殊奖励。

【输入格式】

第一行两个整数n,m表示有向图的点数和边数。

接下来m行每行三个整数xiyili,表示从xiyi有一条长度为li的边。

最后一行四个整数A,B,C,D,描述小P的起终点,小R的起终点。

【输出格式】

输出一个整数表示最多能获得多少次特殊奖励。若小P不能到达B点或者小R不能到达D点则输出-1。

【样例输入输出

game.in

game.out

5 5

1 2 1

2 3 2

3 4 4

5 2 3

5 3 5

1 3 5 4

2

【数据规模】

对于30%的数据,满足n≤50

对于60%的数据,满足n≤1000,m≤5000

对于100%的数据,满足n≤50000,m≤200000,1≤li≤500000000

【题目分析】

    (1)特殊奖励的点一定是连续的

    (2)若s-->x+(x,y)+y-->t = s-->t 则(x,y)在s到t的最短路上

    (3)所有在A-->B,C-->D的最短路上的边构成一个有向无环图

    在正向图上求A,C出发的最短路

    在反向图上求到达B,D的最短路

    最短路可以用dijkstra+优先队列

    筛选出在A-->B,C-->D的最短路上的边作为新图

    对新图拓扑排序+DP求最长路

时间复杂度(MlogN)

【算法步骤】

①读入数据建立正向图和反向图;

②在正向图上分别求出以A,C为源点的最短路;在反向图上分别求出以B,D为源点的最短路;

③筛选出在A→B且在C→D的最短路径上的边建立新图

④对新图拓扑排序+DP求最长路(问题变为求一个DAG图的最长路问题)

#include<iostream>

#include<cstdio>

#include<queue>

using namespace std;

const int INF=0x7fffffff/3;

struct front_star{int ne,to,v;}a[500005],fa[500005],na[500005];

struct Edge{int x,y,v;}e[500005];

struct jgt{int num,V;};

bool operator < (const jgt &x,const jgt &y){return x.V>y.V;}

priority_queue<jgt>q;

int d1[500005]={0},d2[500005]={0},h[500005]={0},cnt=0,Q[500005]={0};

int fh[500005]={0},fcnt=0,n,m,rd[500005]={0},f[500005]={0},vst[500005]={0};

int ha[500005]={0},ncnt=0,nh[500005]={0};

int ans=0,A,B,C,D;

void Addedge(int x,int y,int v)

{  a[++cnt].to=y;a[cnt].ne=h[x];a[cnt].v=v;h[x]=cnt;}

void fAddedge(int x,int y,int v)

{  fa[++fcnt].to=y;fa[fcnt].ne=fh[x];fa[fcnt].v=v;fh[x]=fcnt;}

void nAddedge(int x,int y,int v)

{  na[++ncnt].to=y;na[ncnt].ne=nh[x];na[ncnt].v=v;nh[x]=ncnt;}

void Read()

{  int i;

   scanf("%d%d",&n,&m);

   for(i=1;i<=m;i++)

   {  scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v);

  Addedge(e[i].x,e[i].y,e[i].v);

  nAddedge(e[i].y,e[i].x,e[i].v);

   }

   scanf("%d%d%d%d",&A,&B,&C,&D);

}

void Dijkstra(int x,int d[],front_star a[],int h[])

{  int i,j,k,v;

   for(i=1;i<=n;i++){d[i]=INF;vst[i]=0;}

   d[x]=0;

   q.push((jgt){x,0});

   while(!q.empty())

   {  i=q.top().num;

  q.pop();

  if(vst[i])continue;

  vst[i]=1;

  for(k=h[i];k;k=a[k].ne)

  {  j=a[k].to;

 if(!vst[j]&&d[j]>d[i]+a[k].v)

 {  d[j]=d[i]+a[k].v;

q.push((jgt){j,d[j]});

 }

  }

   }

}

int Pre()

{  int i;

   Dijkstra(A,d1,a,h);//A点为源点在正向图上求最短路

   Dijkstra(B,d2,na,nh);//B点为源点在反向图上求最短路

   if(d1[B]==INF||d2[A]==INF)return 0;//无解情况

   for(i=1;i<=m;i++)//判断那些边在A-B的最短路径上

      if(d1[e[i].x]+e[i].v+d2[e[i].y]==d1[B])ha[i]++;

   Dijkstra(C,d1,a,h);//C点为源点在正向图上求最短路

   Dijkstra(D,d2,na,nh);//D点为源点在反向图上求最短路

   if(d1[B]==INF||d2[A]==INF)return 0;//无解

   for(i=1;i<=m;i++)//判断那些边在C-D的最短路径上

   {  if(d1[e[i].x]+e[i].v+d2[e[i].y]==d1[D])ha[i]++;

  if(ha[i]==2)//建立新图

  {  fAddedge(e[i].x,e[i].y,e[i].v);

 rd[e[i].y]++;

  }

   }

   return 1;

}

void SortDp()

{  int i;

   for(i=1;i<=n;i++)

   {  f[i]=1;

  if(rd[i]==0)Q[++Q[0]]=i;

   }

   for(i=1;i<=Q[0];i++)

   {  int x=Q[i];

  for(int k=fh[x];k;k=fa[k].ne)

  {  int y=fa[k].to;

 f[y]=max(f[y],f[x]+1);

 ans=max(ans,f[y]);

 rd[y]--;

 if(rd[y]==0)Q[++Q[0]]=y;

  }

   }

   printf("%d",ans);

}

int main()

{  int i,j,k,x,y,v;

   Read();

   if(Pre())SortDp();

      else printf("-1");

   return 0;

}

 

 

3783---道路改建

(rebuild.pas/c/cpp)

【问题描述】

    人称不死将军的林登·万,与他的兄弟林登·图两人的足迹踏遍了地球的每一寸土地。他们曾将战火燃遍了世界。即使是lifei888这样的强悍人物也从来没有将他彻底击败。

这一次,林登·万在N个城市做好了暴动的策划。然而,在起事的前一天,将军得知计划已经泄漏,决定更改计划,集中力量掌握一部分城市。

具体来说,有M条单向的道路连接着这N座城市。对于两座城市A,B,如果它们能够通过路径直接或间接的互相到达,那么就林登·万可以同时控制A,B两座城市而不至于分散力量,反之则会被lifei888各个击破。

为了扩大成果,将军还组织了人手改建道路。这些人可以在起事前将其中一条单向道路改变成双向。现在,将军想要知道他最多能通过改建道路控制几座城市,以及通过改建哪些道路,可以使他控制的城市到达最多。

【输入】

    第一行为两个正整数N,M

接下来M行每行两个范围在1~N内的正整数x,y,表示有一条从xy的单向路径。

输入保证任意两点的任意方向最多只有一条边直接相连。

【输出】

    输出共三行。

第一行一个正整数,将军最多能控制的城市数量。

第二行一个正整数L,表示有L种改建方案使得将军能控制最多的城市。

第三行L个按递增顺序给出的正整数ki,表示改建输入中的第ki条边能使将军能控制最多的城市。

【输输出样例1

rebuild.in

rebuild.out

5 4

1 2

2 3

1 3

4 1

3

1

3

 

【输输出样例2

rebuild.in

rebuild.out

3 4

1 2

2 1

1 3

3 1

3

4

1 2 3 4

【数据规模】

    对于30%的数据,N,M<=10

    对于60%的数据,N<=1500M<=100000

对于100%的数据,N<=2000 M<=N*N

【题目大意】给你N个点M条单向边连接的图,问可以把一条边改成双向边,所得到的最大强连通分量的大小是多少?

【题目考点】

【题目分析】

    题目就是要求把一条有向边变为无向边后,求最大的强连通分量的大小。

30%的数据,暴力枚举每一条边,然后求强连通分量即可。因为N,M非常小,所以求强连通分量这一步可以用floyd完成。

void doit()

{  int i,j,k,num;

   for(k=1;k<=n;k++)  //枚举中间点

      for(i=1;i<=n;i++)

         for(j=1;j<=n;j++)g[i][j]=g[i][j]||(g[i][k]&&g[k][j]);

   num=0;

   for(i=1;i<=n;i++)  //统计连通分量数

      if(vst[i]==0)

      {  num++;vst[i]=num;  //顶点i属于新的连通分量

         for(j=1;j<=n;j++)  

           if(g[i][j]&&g[j][i])vst[j]=num; //ij属于同一连通分量

      }

}

 

    对于60%的数据,首先对原图求一次强连通分量。然后我们就得到了一个拓扑图。用f1[i]记录i这个节点能到达的节点组成的集合,f2[i]记录能到达i这个节点的节点组成的集合。显然f1,f2可以通过两次DPO(N*M)的时间内求出来。

    接下来我们枚举改变的边(u,v)。显然最大的强连通分量一定包含了(u,v)。那么我们只要求包含了(u,v)的强连通分量即可。这个新的强连通分量就是f1[u]f2[v]的交集。最大的交集即为答案,这一步同样可以在O(N*M)的时间内解决。

    

对于100%的数据,因为求f1,f2的过程中用到了andor操作,把f1,f230位的整数压缩,时间复杂度仍然是O(N*M)的,但常数只有1/32,可以通过全部数据。

算法步骤:

读入数据,建立图

‚强连通分量的缩点

ƒ建立新图,拓扑排序,求出f1[i]记录i这个节点能到达的节点组成的集合,f2[i]记录能到达i这个节点的节点组成的集合

枚举m条边,对于每条边<x,y>,即新的强连通分量就是f1[fa[x]]f2[fa[y]]的交集,最大交集即为答案。

    

【扩展知识】STL模板bitset

#include<bitset>

#include<iostream>

using namespace std;

bitset<2010>a[2010],b[2010],tmp;

int main()

{  int x=1,y=1;

   a[x][1]=1;a[x][2]=1;a[x][4]=1;//1 2 4

   b[y][1]=1;b[y][3]=1;b[y][4]=1;b[y][5]=1;//1 3 4 5

   a[x]|=b[y]; //合并两个集合中的1   1 2 3 4 5

   tmp=a[x]&b[y];//求两个集合的交集  1 3 4 5

   int Ans=a[x].count();//统计tmp中二进制位为1的个数

   cout<<Ans<<endl;

   return 0;

}

【方法2

#include<cstdio>

#include<cstring>

#include<ctime>

#include<vector>

#include<bitset>

using namespace std;

int U[8000005],V[8000005],N,M,Ans[8000005];

bitset<2005> F[2005],G[2005];

void Init()

{  scanf("%d%d",&N,&M);

   for(int i=1;i<=N;i++)F[i][i]=1;

   for(int i=1;i<=M;i++)

   {  scanf("%d%d",&U[i],&V[i]);

      F[U[i]][V[i]]=1;

   }

   for(int k=1;k<=N;k++)//枚举中间点

      for(int i=1;i<=N;i++)//枚举起点,省掉终点

         if(F[i][k])F[i]|=F[k];//相当于f[i,j]=f[i,j]||(f[i,k]&&f[k,j])

          //F[i]表示结点i到达的点的集合

   for(int i=1;i<=N;i++)//G[i]表示到达i点的集合

      for(int j=1;j<=N;j++)G[j][i]=F[i][j];

}

void Work()

{  for(int i=1;i<=M;i++)//枚举边反向并求交集

   {  Ans[i]=((F[U[i]]&G[V[i]])).count();//交集中点的个数

      if(Ans[0]<Ans[i])Ans[0]=Ans[i];//求最大值

   }

}

void Output()

{  int Cnt=0;

   printf("%d\n",Ans[0]);

   for(int i=1;i<=M;i++)

       if(Ans[i]==Ans[0])Cnt++;

   printf("%d\n",Cnt);

   for(int i=1;i<=M;i++)

      if(Ans[i]==Ans[0])printf("%d ",i);

}

int main()

{  Init();

   Work();

   Output();

   return 0;

}

 

posted @ 2017-10-20 13:26  keshuqi  阅读(434)  评论(1编辑  收藏  举报