noip2015 总结

神奇的幻方

题目描述


幻方是一种很神奇的NN矩阵:它由数字1,2,3,……,NN构成,且每行、每列及两条对角线上的数字之和都相同。

当N为奇数时,我们可以通过以下方法构建一个幻方:
首先将1写在第一行的中间。
之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N):

  1. 若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列;
  2. 若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行;
  3. 若(K−1)在第一行最后一列,则将K填在(K−1)的正下方;
  4. 若(K−1)既不在第一行,也不在最后一列,如果(K−1)的右上方还未填数,则将K填在(K−1)的右上方,否则将K填在(K−1)的正下方。

现给定N请按上述方法构造N*N的幻方。

输入输出格式

输入格式:
输入文件只有一行,包含一个整数N即幻方的大小。

输出格式:
输出文件包含N行,每行N个整数,即按上述方法构造出的N*N的幻方。相邻两个整数之间用单个空格隔开。

输入输出样例

输入样例#1:

3

输出样例#1:

8 1 6
3 5 7
4 9 2

思路

无难度模拟

代码

#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;

int n,x,y;
int mp[200][200];
int main()
{
    scanf("%d",&n);
    x = 1,y = (n+1)/2;
    mp[x][y]=1;
    for(int i=2;i<=n*n;i++)
    {
        if(x==1&&y!=n)
            x=n,y++;
        else if(x!=1&&y==n)
            x--,y=1;
        else if(x==1&&y==n)
            x++;
        else if(!mp[x-1][y+1])
            x--,y++;
        else
            x++;
        mp[x][y]=i;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            printf("%d ",mp[i][j]);
        printf("\n");
    }
}

信息传递

题目描述

有n个同学(编号为1到n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?

输入输出格式

输入格式:
输入共2行。
第1行包含1个正整数n表示n个人。
第2行包含n个用空格隔开的正整数T1,T2,……,Tn其中第i个整数Ti示编号为i
的同学的信息传递对象是编号为Ti的同学,Ti≤n且Ti≠i
数据保证游戏一定会结束。

输出格式:
输出共 1 行,包含 1 个整数,表示游戏一共可以进行多少轮。

输入输出样例
输入样例#1:52 4 2 3 1

输出样例#1:3

说明

样例1解释

游戏的流程如图所示。当进行完第 3 轮游戏后, 4 号玩家会听到 2 号玩家告诉他自
己的生日,所以答案为 3。当然,第 3 轮游戏后, 2 号玩家、 3 号玩家都能从自己的消息
来源得知自己的生日,同样符合游戏结束的条件。

对于 30%的数据, n ≤ 200;
对于 60%的数据, n ≤ 2500;
对于 100%的数据, n ≤ 200000。

思路

找最小环
先把不可能的人踢了
即:先把入度为0的点删除,然后把这个点指向的点的入度-1,如果入度为0,也删去,这样就只保留有用的点,那么从任意一个点开始,用vis数组记录是否被访问过,访问到一个新节点就累加计数器,然后就做出来了.bfs和dfs都可以

代码

#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <algorithm>

using namespace std;

int to[200010],vis[200010], rubian[200010];

int n,ans;

queue <int> q;

int main()
{
    memset(to, 0, sizeof(to));
    memset(vis, 0, sizeof(vis));
    memset(rubian, 0, sizeof(rubian));
    scanf("%d", &n);
    ans = n;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &to[i]);
        ++rubian[to[i]];
    }
    for (int i = 1; i <= n; i++)
        if (rubian[i] == 0)
        {
            q.push(i);
            vis[i] = 1;
        }
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        --rubian[to[u]];
        if (rubian[to[u]] == 0)
        {
            vis[to[u]] = 1;
            q.push(to[u]);
        }
    }
    int temp,j;
    for (int i = 1; i <= n; i++)
    {
        if (vis[i] == 0 && rubian[i] != 0)
        {
            vis[i] = 1;
            temp = 1;
            j = to[i];
            while (!vis[j])
            {
                vis[j] = 1;
                j = to[j];
                temp++;
            }
            if (temp <= ans)
                ans = temp;
        }
    }
    printf("%d\n", ans);

    return 0;
}

跳石头

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终 点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达 终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳 跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能 移走起点和终点的岩石)。

输入输出格式

输入格式:
输入文件名为 stone.in。

输入文件第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终 点之间的岩石数,以及组委会至多移走的岩石数。

接下来 N 行,每行一个整数,第 i 行的整数 Di(0 < Di < L)表示第 i 块岩石与 起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同 一个位置。

输出格式:
输出文件名为 stone.out。 输出文件只包含一个整数,即最短跳跃距离的最大值。

输入输出样例

输入样例#1:

25 5 2 
2
11
14
17 
21

输出样例#1:

4

思路

我们对于一个长度x,想看看它是否可以符合删除石头数小于等于m,可以这样做:

从位置的小到大扫遍所有石头,用一个变量存储上一个跳到的点。第一个与这上一个点的距离大于等于x的石头即是下一个跳到的点。这里用了一点贪心的思想:因为如果不跳到第一个符合条件的点上,那么整个队列的稀疏度就会提高,最终需要删除的石头也会更多。因为我们要取最优状态,所以要保证跳过的石头数最少。当然,如果某个石头到终点的距离小于x,那它不能被统计到——所以得删去后面这些无法跳到的石头。我自认为这应该也是一个坑点(虽然我第一遍就判断了)。

这样,便求出了这个x是否可行,如果可行,那就往右边二分,但要记得范围要包括x;若不行,则往左边二分,右限制不包括x。然后,二分到左右边界相等,输出即可。

然后此题就做完了。大家应该很容易理解。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
int sto[100000];//开大一点,保险
int main()
{
    int s,n,m;
    scanf("%d%d%d",&s,&n,&m);
    int zuo=1,you=s,mid;//所有边界为1、s
    for(int i=0;i<n;i++)scanf("%d",&sto[i]);
    sort(sto,sto+n);//从小到大排序
    int sg,cnt,ii;
    while(zuo!=you)
    {
        mid=(zuo+you+1)>>1;//位运算加速
        sg=cnt=0;//初始化
        for(ii=0;ii<n;ii++)
        {
            if(s-sto[ii]<mid)break;//如解析中所述,若再跳x已超过终点,则不可取此点,它后面的也显然不可取
            if(sto[ii]-sg<mid)cnt++;//跳过
            else sg=sto[ii];//贪心,直接跳到
        }
        cnt+=n-ii;//统计最后被删除的点数
        if(cnt<=m)zuo=mid;
        else you=mid-1;//二分边界更新,具体请见解析
    }
    printf("%d",zuo);//输出
    return 0;
}

字串

题目描述

有两个仅包含小写英文字母的字符串 A 和 B。现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一 个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?注意:子串取出 的位置不同也认为是不同的方案。

输入输出格式

输入格式:
输入文件名为 substring.in。
第一行是三个正整数 n,m,k,分别表示字符串 A 的长度,字符串 B 的长度,以及问
题描述中所提到的 k,每两个整数之间用一个空格隔开。 第二行包含一个长度为 n 的字符串,表示字符串 A。 第三行包含一个长度为 m 的字符串,表示字符串 B。

输出格式:
输出文件名为 substring.out。 输出共一行,包含一个整数,表示所求方案数。由于答案可能很大,所以这里要求[b]输出答案对 1,000,000,007 取模的结果。[/b]

输入输出样例

输入样例#1:

6 3 1 
aabaab 
aab

输出样例#1:

2

输入样例#2:

6 3 2 
aabaab 
aab

输出样例#2:

7

输入样例#3:

6 3 3 
aabaab 
aab

输出样例#3:

7

说明

对于第 1 组数据:1≤n≤500,1≤m≤50,k=1;
对于第 2 组至第 3 组数据:1≤n≤500,1≤m≤50,k=2;
对于第 4 组至第 5 组数据:1≤n≤500,1≤m≤50,k=m;
对于第 1 组至第 7 组数据:1≤n≤500,1≤m≤50,1≤k≤m;
对于第 1 组至第 9 组数据:1≤n≤1000,1≤m≤100,1≤k≤m;
对于所有 10 组数据:1≤n≤1000,1≤m≤200,1≤k≤m。

思路

最后数组压到二维的就可以省空间了。

代码

//不压维
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1000 + 5, maxm = 200 + 5, moder = 1000000007;
int m, n, K;
char stra[maxn], strb[maxm];
int f[maxn][maxm][maxm], s[maxn][maxm][maxm];

void solve() {
    s[0][0][0] = 1;
    for(int i = 1; i <= n; ++i) {
        s[i][0][0] = 1; //注意如果不压到二维,这是需要的
        for(int j = 1; j <= m; ++j)
            if(stra[i-1] == strb[j-1]) {
                for(int k = 1; k <= min(K, j); ++k) {
                    f[i][j][k] = (s[i-1][j-1][k-1] + f[i-1][j-1][k]) % moder,
                    s[i][j][k] = (s[i-1][j][k] + f[i][j][k]) % moder;
                }
            }else for (int k = 1; k <= min(K, j); ++k) s[i][j][k] = s[i-1][j][k]; //注意如果不压到二维,这是需要的
    }
    printf("%d\n", s[n][m][K]);
}

int main() {
    scanf("%d%d%d%s%s", &n, &m, &K, stra, strb);
    solve();
    return 0;
}
//压维
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1000 + 5, maxm = 200 + 5, moder = 1000000007;
int m, n, K;
char stra[maxn], strb[maxm];
int f[maxm][maxm], s[maxm][maxm];

void solve() {
    s[0][0] = 1;
    for(int i = 1; i <= n; ++i) {
        for(int j = m; j > 0; --j)
            if(stra[i-1] == strb[j-1]) {
                for(int k = min(K, j); k > 0; --k) {
                    f[j][k] = (s[j-1][k-1] + f[j-1][k]) % moder,
                    s[j][k] = (s[j][k] + f[j][k]) % moder;
                }
            }else fill(f[j], f[j] + min(K, j) + 1, 0);
    }
    printf("%d\n", s[m][K]);
}

int main() {
    scanf("%d%d%d%s%s", &n, &m, &K, stra, strb);
    solve();
    return 0;
}

斗地主

题目描述

牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。

现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。

需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。

具体规则如下:

输入输出格式

输入格式:
第一行包含用空格隔开的2个正整数T和n,表示手牌的组数以及每组手牌的张数。

接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。

输出格式:
共T行,每行一个整数,表示打光第i手牌的最少次数。

输入输出样例

输入样例#1:

1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1

输出样例#1:

3

思路

搜索+剪枝

忽略花色,统计每种码数出现次数方便出牌。
每次都先出顺子,对于手中剩下的牌我们贪心地将剩下的组合牌需要打的次数计算出来,然后更新ans以剪枝。
双王算作对牌。顺子不包括2和双王。

代码

  #include<cstdio>
  #include<cstring>
  #define FOR(a,b,c) for(int a=(b);a<=(c);a++)
  using namespace std;
  const int N = 25;
  int a[N],c[N];
  int n,T,ans;

 int Qans() {
     memset(c,0,sizeof(c));
     FOR(i,0,13) c[a[i]]++;
     int tot=0;                                    //tot带牌 
     while(c[4]&&c[2]>1)  c[4]--,c[2]-=2,tot++;      
     while(c[4]&&c[1]>1) c[4]--,c[1]-=2,tot++;
     while(c[4]&&c[2]) c[4]--,c[2]--,tot++;
     while(c[3]&&c[2]) c[3]--,c[2]--,tot++;
     while(c[3]&&c[1]) c[3]--,c[1]--,tot++;
     return tot+c[1]+c[2]+c[3]+c[4];                //带牌+三张 对子 单张 
 }

 void dfs(int now) {
     if(now>=ans) return ;
     int tmp=Qans();
     if(now+tmp<ans)  ans=now+tmp;
     FOR(i,2,13) {                                //三顺子 
         int j=i;
         while(a[j]>=3) j++;
         if(j-i>=2) {
             FOR(j2,i+1,j-1) {
                 FOR(k,i,j2) a[k]-=3;
                 dfs(now+1);
                 FOR(k,i,j2) a[k]+=3;
             }
         }
     }
     FOR(i,2,13) {                                //双顺子 
         int j=i;
         while(a[j]>=2) j++;
         if(j-i>=3) {
             FOR(j2,i+2,j-1) {
                 FOR(k,i,j2) a[k]-=2;
                 dfs(now+1);
                 FOR(k,i,j2) a[k]+=2;
             }
         }
     }
     FOR(i,2,13) {                                //单顺子 
        int j=i;
         while(a[j]>=1) j++;
         if(j-i>=5) {
             FOR(j2,i+4,j-1) {
                 FOR(k,i,j2) a[k]--;
                 dfs(now+1);
                 FOR(k,i,j2) a[k]++;
             }
         }
     }
 }

 int main() {
     //freopen("in.in","r",stdin);
     //freopen("out.out","w",stdout);
     scanf("%d%d",&T,&n);
     while(T--) {
         memset(a,0,sizeof(a));
         int x,y;
         FOR(i,1,n) {
             scanf("%d%d",&x,&y);
             if(x==1) x=13; else if(x) x--;
             a[x]++;
         }
         ans=1e9;
         dfs(0);
         printf("%d\n",ans);
     }
     return 0;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值