[ NOIP 2008 ] TG

\(\\\)

\(\#A\) \(Word\)


给出一个长为\(N\)的小写字母串,判断出现所有字母中最多出现次数减最少出现次数得到的答案是否是质数。

  • \(N\in [1,100]\)
  • 直接按题意开桶记录,试除法判断即可。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 30
#define M 110
#define R register
using namespace std;

bool v1[N],v2[N];
int to[N],now[M],p;

int main(){
  char c=getchar();
  while(!isupper(c)) c=getchar();
  while(isupper(c)){
    now[++now[0]]=c-'A'+1;
    v1[c-'A'+1]=1; c=getchar();
  }
  for(R int i=1;i<=26;++i) if(!v1[i]){puts("Failed");return 0;}
  while(!isupper(c)) c=getchar();
  while(isupper(c)){
    int x=now[++p];
    if(to[x]){if('A'+to[x]-1!=c){puts("Failed");return 0;}}
    else if(v2[c-'A'+1]){puts("Failed");return 0;}
    else to[x]=c-'A'+1;
    v2[c-'A'+1]=1; c=getchar();
  }
  while(!isupper(c)) c=getchar();
  while(isupper(c)){
    putchar((char)'A'+to[c-'A'+1]-1);
    c=getchar();
  }
  return 0;
}

\(\\\)

\(\#B\) \(Matches\)


已知用火柴棒拼出每个数字所需要的数量,加号和等号各需要两根,求恰好用掉\(N\)根火柴棒构成等式\(A+B=C\)的数量,其中数字不能有前导零,当\(A\not=B\)时交换位置视作两个等式。

  • \(N\in [0,24]\)
  • 用暴力跑一跑,发现最大的数据范围能做到的等式数字大小不会超过\(1000\)
  • 于是预处理前\(1000\)个数每个数所需个数,\(N^2\)暴力枚举判断即可。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define R register
using namespace std;

const int cost[10]={6,2,5,5,4,5,6,3,7,6};

int n,cnt,f[2500]={6};

inline void calc(int x){
  int tmp=x,res=0;
  while(tmp){
    res+=cost[tmp%10];
    tmp/=10;
  }
  f[x]=res;
}

int main(){
  scanf("%d",&n); n-=4;
  for(R int i=1;i<=2300;++i) calc(i);
  for(R int i=0;i<=1111;++i)
    for(R int j=0;j<=1111;++j)
      if(f[i]+f[j]+f[i+j]==n) ++cnt;
  printf("%d\n",cnt);
  return 0;
}

\(\\\)

\(\#C\) \(Massage\)


给出一个\(N\times M\)的矩阵,选则两条从\((1,1)\)\((N,M)\)的路径,使得两条路径上的权值和最大,注意每个格点的权值只能被计入答案一次。

  • \(N,M\in [0,50]\)
  • 状态显然跟步数以及两条路径的当前终点\((x_1,y_1)(x_2,y_2)\)有关,注意到两个终点的横纵坐标之和是固定的,因为起点为\((1,1)\),所以有\(x_1+y_1=x_2+y_2=2+\)当前步数。

  • 精简状态,设\(f[i][j][k]\)表示当前两节点横纵坐标之和(即\(2+\)当前步数)为\(i\),第一条路径终点为\((j,i-j)\),第二条路径终点为\((k,i-k)\)的最大路径权值和。有显然的初始化\(f[2][1][1]=val[1][1]\)

  • 考虑对于已知状态\(f[i][j][k]\)的转移,根据下一步两条路径的行动方向讨论,有\(2\times 2=4\)种情况:

    • 两个都向下:若当前两终点重合,则下一步也重合,权值只计算一次,否则分开累加答案:
    if(j==k) f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+val[j+1][i-j]);
    else f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+val[j+1][i-j]+val[k+1][i-k]);
    
    • 两个都向右:讨论同上:
    if(j==k) f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+val[j][i+1-j]);
    else f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+val[j][i+1-j]+val[k][i+1-k]);
    
    • 第一个向右,第二个向下:若\(j=k+1\),则代表下一步重合,权值只计算一次,否则分开累加:
    if(k==j-1) f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+val[j][i+1-j]);
    else f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+val[j][i+1-j]+val[k+1][i-k]);
    
    • 第一个向下,第二个向右:讨论同上,条件改为\(k=j+1\)
    if(j==k-1) f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+val[k][i+1-k]);
    else f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+val[k][i+1-k]+val[j+1][i-j]);
    
  • 答案即为\(f[N+M][N][N]\)

#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 120
#define R register
#define gc getchar
using namespace std;

int n,m,val[N][N],f[N<<1][N][N];

inline int rd(){
  int x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

int main(){
  n=rd(); m=rd();
  for(R int i=1;i<=n;++i)
    for(R int j=1;j<=m;++j) val[i][j]=rd();
  f[2][1][1]=val[1][1];
  for(R int i=2;i<=n+m-1;++i)
    for(R int j=1;j<=n;++j)
      for(R int k=1;k<=n;++k){
        if(j==k){
          f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+val[j][i+1-j]);
          f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+val[j+1][i-j]);
        }
        else{
          f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+val[j][i+1-j]+val[k][i+1-k]);
          f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+val[j+1][i-j]+val[k+1][i-k]);
        }
        if(j==k-1) f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+val[k][i+1-k]);
        else f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+val[k][i+1-k]+val[j+1][i-j]);
        if(k==j-1) f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+val[j][i+1-j]);
        else f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+val[j][i+1-j]+val[k+1][i-k]);
      }
  printf("%d\n",f[n+m][n][n]);
  return 0;
}

\(\\\)

\(\#D\) \(Twostack\)


试通过\(2\)个栈\(S_1\)\(S_2\),借助以下\(4\)种操作实现将输入的一个\(N\)的排列升序排序。

  • 操作\(a\):如果输入序列不为空,将第一个元素压入栈\(S_1\)
  • 操作\(b\):如果栈\(S_1\)不为空,将\(S_1\)栈顶元素弹出至输出序列
  • 操作\(c\):如果输入序列不为空,将第一个元素压入栈\(S_2\)
  • 操作\(d\):如果栈\(S_2\)不为空,将\(S_2\)栈顶元素弹出至输出序列

如果输入的排列不是“可双栈排序排列”,输出\(0\)。否则输出字典序最小的操作序列。

  • \(N\in [0,1000]\)
  • 当两个数不能连续进入同一个栈,首先需要满足前面的数小于后面的数,其次,前面的数不能在后面的数进栈之前弹出。当一个数可以弹栈,证明小于它的数字已经全部弹出,所以我们可以这样判断:
    • 预处理出每一个数的后缀\(min\)
    • \(N^2\)枚举两个数\(a,b\)\(a\)\(b\)之前出现):若\(a<b\)\(a>min_b\)则证明两者不能进入同一个栈。
  • 如何判断是否有合法操作序列呢?注意到若合法这些点应该能通过互斥关系至多分成两类,所以不妨在不能进入同一个栈的两个点间连边,进行二分图染色,若该图是二分图则证明有合法操作关系。
  • 因为要最小字典序答案,所以第一个数必定进\(S_1\)栈,注意到可能这张图不连通,所以将每次第一个遇到的点都染成黑色,代表进入\(S_1\)栈。
  • 然后就可以模拟进出栈的过程了:每到一个点就按照染色入栈,然后检查是否有需要弹栈的数字,因为我们确定了输入序列为一个排列,所以直接通过栈顶权值判断。
  • 但注意不能直接在插入之后把所有能弹出的数字都弹出,因为可能会存在先让数字进入第一个栈再弹出第二个栈的更优解法,所以操作变为:若插入\(S_2\),则能弹则弹;若插入\(S_1\),则先将一号栈该弹出的弹出(注意可能会附加二号栈的弹出),入栈,再将二号栈该弹出的弹出。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 1010
#define R register
#define gc getchar
#define top1 stk1[0]
#define top2 stk2[0]
using namespace std;

int num[N],mn[N],to[N],stk1[N],stk2[N];

int n,m,tot,hd[N];
struct edge{int to,nxt;}e[N*N];
inline void add(int u,int v){
  e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot;
}

inline int rd(){
  int x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

inline bool dfs(int u,int tmp){
  to[u]=tmp;
  for(R int i=hd[u],v;i;i=e[i].nxt)
      if(!to[v=e[i].to]){if(dfs(v,3-tmp))return 1;}
      else if(to[v]==tmp) return 1;
  return 0;
}

int main(){
  n=rd();
  for(R int i=1;i<=n;++i) num[i]=rd();
  mn[n]=num[n];
  for(R int i=n-1;i>=1;--i) mn[i]=min(mn[i+1],num[i]);
  for(R int i=1;i<n;++i)
    for(R int j=i+1;j<=n;++j)
      if(num[i]<num[j]&&num[i]>mn[j]){add(i,j);add(j,i);}
  for(R int i=1;i<=n;++i)
      if(!to[i]) if(dfs(i,1)){putchar('0');return 0;};
  m=1;
  for(R int i=1;i<=n;++i){
      if(to[i]==1){putchar('a'); putchar(' '); stk1[++top1]=num[i];}
      else{putchar('c'); putchar(' '); stk2[++top2]=num[i];}
      while(stk1[top1]==m||stk2[top2]==m){
        if(stk1[top1]==m){putchar('b');putchar(' ');--top1;}
        else{putchar('d');putchar(' ');--top2;} ++m;
      }
  }
  return 0;
}
posted @ 2018-09-04 14:49  SGCollin  阅读(167)  评论(0编辑  收藏  举报