挑战程序设计竞赛(第二章习题总结)

文章目录

        • 搜索
          • Curling 2.0(POJ 3009)
          • Meteor Shower(POJ 3669)
          • Smallest Difference(POJ 2718)
          • Hopscotch(POJ 3050)
        • 贪心
          • Cleaning Shifts(POJ 2376)
          • Radar Installation(POJ 1328)
          • Stall Reservations(POJ 3190)
          • Yogurt factory(POJ 2393)
          • Packets(POJ 1017)
          • Allowance(POJ 3040)
          • Stripies(POJ 1862)
          • Protecting the Flowers(POJ 3262)
        • 动态规划
            • Sumsets(POJ 2229)
          • Milking Time(POJ 3616)
        • 数据结构
          • Sunscreen(POJ 3614)
          • Moo University - Financial Aid(POJ 2010)
        • 并查集
            • Wireless Network
          • Find them, Catch them(POJ 1703)
        • 图论
          • Six Degrees of Cowvin Bacon(POJ 2139)
          • Wormholes(POJ 3259)
          • Silver Cow Party(POJ 3268)
          • Bad Cowtractors(POJ 2377)
          • Out of Hay(POJ 2395)
        • 数论
          • Dead Fraction(POJ 1930)
          • Prime Path(POJ 3126)
          • X-factor Chains(POJ 3421)
          • Semi-prime H-numbers(POJ 3292)
          • Raising Modulo Numbers(POJ 1995)
          • Pseudoprime numbers(POJ 3641)

搜索

Curling 2.0(POJ 3009)

题目链接:Curling 2.0
参考博文:POJ 3009 Curling 2.0(DFS + 模拟)

  • 题目大意:题意比较复杂,详见参考博文。
  • 思路:重要的是理解清楚题意。障碍物可以撞碎,球在遇见障碍物之前一定是直行。DFS回溯方法比较适合。具体思路参考博文。

代码:

#include 
#include 
#include 
using namespace std;

typedef long long LL;

const int MAX = 25;
int maze[MAX][MAX];

int dx[5] = {0, 1, 0, -1};
int dy[5] = {1, 0, -1, 0};
int ans;
int w, h;
int sx, sy;

//判断坐标的状态
int check(int x, int y)
{
    if(maze[x][y]==0 || maze[x][y]==2) return 1;//可以移动
    else if(maze[x][y]==-1 || maze[x][y]==1) return 2;//不能移动
    else return 3;//到达目的地
}

void dfs(int x, int y, int t)
{
    if(maze[x][y]==3 || t>10)//步数超出规定以及到达目的地均结束
    {
        if(ans>t) ans = t;
    }
    else
    {
        //四个方向遍历
        for(int i=0; i<4; i++)
        {
            int tx = x, ty = y;
            if(check(tx+dx[i], ty+dy[i]) != 2)//只进行可以移动的状态操作
            {
                while(check(tx+dx[i], ty+dy[i])==1)//如果前方可以移动,一直向同一个方向移动,步数不更新
                    tx += dx[i], ty += dy[i];

                if(maze[tx+dx[i]][ty+dy[i]]==1)//前方障碍物
                {
                    //当前方是障碍物时,撞碎变成0,更新了地图
                    maze[tx+dx[i]][ty+dy[i]] = 0;
                    t++;
                    dfs(tx, ty, t);//继续从障碍物前一个开始
                    t--;
                    maze[tx+dx[i]][ty+dy[i]] = 1;
                }
                else if(maze[tx+dx[i]][ty+dy[i]]==3)//到达目的地
                {
                    t++;
                    dfs(tx+dx[i], ty+dy[i], t);//直接到达目的地,结束
                    t--;
                }
            }
        }
    }
}

int main()
{
    while(scanf("%d%d", &w, &h)!=EOF && (w+h))
    {
        memset(maze, -1, sizeof(maze));
        ans = 1<<29;
        for(int i=1; i<=h; i++)
        {
            for(int j=1; j<=w; j++)
            {
                scanf("%d", &maze[i][j]);
                if(maze[i][j]==2)
                    sx = i, sy = j;
            }
        }

        dfs(sx, sy, 0);
        if(ans>10)
            printf("-1\n");
        else
            printf("%d\n", ans);
    }
    return 0;
}
Meteor Shower(POJ 3669)

题目链接:Meteor Shower

  • 题目大意:给m个炸弹,炸弹在第t秒钟会在(x,y)处爆炸,爆炸会波及周围的四个点。求离(0,0)最近的没有被爆炸波及的点。
  • 思路:广搜。将安全区标记为-1,将爆炸区标记为该点最早爆炸的时间点,bfs遍历爆炸区,目标到达任意一个安全区。注意一个点可能爆炸多次,需要去最早爆炸的时间。
    代码:
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 500;

struct Node
{
    int x, y, t;
    Node(int x=-1, int y=-1, int t=-1): x(x), y(y), t(t){}
};
queue q;
int M, X, Y, T;
int vis[MAX][MAX];//-1代表安全区,0代表已经遍历的地区,其余正值代表爆炸的时间
int dx[5] = {0, 0, 1, -1};
int dy[5] = {1, -1, 0, 0};

void bfs(Node s)
{
    while(!q.empty()) q.pop();
    q.push(s);
    if(vis[s.x][s.y]<0)
    {
        printf("%d\n", &s.t);
        return;
    }
    if(vis[s.x][s.y]>0 && s.t=0 && y>=0)
            {
                if(vis[x][y]>0 && tT)
            vis[X][Y] = T;
        for(int j=0; j<4; j++)
        {
            int x = X+dx[j], y = Y+dy[j];
            if(x>=0 && y>=0)
            {
                if(vis[x][y]==-1)
                    vis[x][y] = T;
                else if(vis[x][y]>T)
                {
                    vis[x][y] = T;
                }
            }
        }
    }
    bfs(Node(0, 0, 0));
    return 0;
}
Smallest Difference(POJ 2718)

题目链接:Smallest Difference

  • 题目大意:将k个数字分成两份,组成两个整数,求其最小的差值。
  • 思路:全排列的求解搜索。因为最多是0-9,所以如果全排列可知有428800中情况,不超时。注意nex_permutation(a, a+k)函数的使用(使用do循环)。

代码:

#include 
#include 
#include 
#include 
using namespace std;


int main()
{
    int T;
    string s;
    int a[15];
    int b, c;//b是大的那一方
    cin >> T;
    getchar();

    while(T--)
    {
        getline(cin, s);
        int k = 0, cnt;

        for(int i=0; i2)) continue;//舍去这个排列

            for(int i=0; i
Hopscotch(POJ 3050)

题目链接:Hopscotch

  • 题目大意:牛可以在一个矩阵上任意一点上向前后左右四个方向跳格子,跳五次后得到含有六个数字的数字串,计算其组成的整数,求所得整数的可能的个数。
  • 思路:穷竭搜索。注意使用set结构体来存储枚举得到的结果,这样可以省去很多代码。遍历每一个点起始得到的整数,存入Set,最后得到set的大小。
    代码:
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 10;

struct Node
{
    int x, y;
    Node(int x=-1, int y=-1): x(x), y(y){}
};

int G[MAX][MAX];
int r[100000][MAX], n;
set a;
Node b[MAX];
//右、左、下、上
int dx[5] = {0, 0, 1, -1};
int dy[5] = {1, -1, 0, 0};

int cnt = 0;

void dfs(Node s, int step, int num)
{
    if(step==5)
    {
        a.insert(num);
        return;
    }

    for(int i=0; i<4; i++)
    {
        int x = dx[i]+s.x, y = dy[i]+s.y, t = step+1;
        if(x>=0 && x<5 && y>=0 && y<5)
        {
            dfs(Node(x, y), t, num*10+G[x][y]);
        }
    }
    return;
}

int main()
{
    n = 0;
    for(int i=0; i<5; i++)
    {
        for(int j=0; j<5; j++)
        {
            scanf("%d", &G[i][j]);
        }
    }

    for(int i=0; i<5; i++)
    {
        for(int j=0; j<5; j++)
        {
            dfs(Node(i, j), 0, G[i][j]);
        }
    }
    printf("%d\n", a.size());

    return 0;
}

贪心

Cleaning Shifts(POJ 2376)

题目链接:Cleaning Shifts

  • 题目大意:要求使用最少的区间覆盖一个大区间。
  • 思路:贪心。需要在可以首尾相接的条件下,保证得到的区间覆盖最远的距离。要注意首尾相接的条件可以不重合,但需要接上。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 25005;
struct Node
{
    int s, e;
    bool operator < (const Node &A) const
    {
        return scnt)//起始点可以接上,终点取满足起始点条件的最大值
        {
            cnt = c[i].e;
        }else if(c[i].s>end+1 && c[i].s<=cnt+1 && c[i].e>cnt)//有新的更新目标(即起始点接不上,但满足更新条件)
        {
            end = cnt;
            ans++;
            i--;//注意这种情况需要重来一次
        }
        if(cnt==T)//满足终止条件
        {
            flag = 1;
            break;
        }
    }

    if(flag)
        printf("%d\n", ans);
    else
        printf("-1\n");
    return 0;
}
Radar Installation(POJ 1328)

题目链接:Radar Installation
参考博文:Radar Installation

  • 题目大意:在坐标系中,x轴上方为海洋(海洋中有多个岛屿,以点标记),x轴下方为陆地。需要在陆地上建立多个雷达站,使得雷达探测范围可以将所有岛屿覆盖,求需要建立的雷达站的最少数目。
  • 思路:比较好的贪心算法题目。将思想转换一下,计算探测到每一个岛屿的雷达站建设区域,在重合区域内建立雷达站即可探测到多个岛屿。需要记录区域的起始和终止,按起始点从小到大排序,然后不断更新重叠区域的终止点(可能变近),如果没有重叠区域则加一(起始点比重叠区域的终止点还大)。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1005;
struct Node
{
    double s, e;

    bool operator < (const Node &A) const
    {
        return send)//没有交集
            {
                ans++;
                //start = a[i].s;
                end = a[i].e;
            }else if(a[i].e<=end)//交集缩小
            {
                //start = a[i].s;
                end = a[i].e;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
Stall Reservations(POJ 3190)

题目链接:Stall Reservations

  • 题目大意:每个牛在某个区间工作,需要占用一个牛棚,问至少需要准备多少牛棚,并给出一个分配方案。
  • 思路:贪心加上优先级队列的题。按照牛区间起始点从小到大排序并遍历,如果已经使用的牛棚的使用区间终止点比遍历的牛的区间起始点低,则该牛可以进入该牛棚工作,否则,新开一个牛棚。

代码:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 50005;
int N, s, t, ans[MAX];
struct Node
{
    int s, e, id;//起始点,终止点,编号(用来当下标)
    Node(int s = -1, int e = -1, int id = -1): s(s), e(e), id(id){}
};
bool cmp1(Node A, Node B)
{
    return A.sB.e;
    }
};

Node cows[MAX];
Node stall[MAX];
priority_queue, cmp2> q;//用来筛选stall(选择终止点最小的)

int main()
{
    while(scanf("%d", &N)!=EOF)
    {
        for(int i=0; i
Yogurt factory(POJ 2393)

题目链接:Yogurt factory

  • 题目大意:有一个奶酪工厂,给出这个工厂每天加工每个奶酪需要的价格,以及每天的需求量,另外,奶酪也可以存放在仓库里(竟然放不坏!),给出每个奶酪存放一天需要的成本,问,这些生产任务全部完成,最少的花费是多少。
  • 思路:这道题的贪心策略比较巧妙。可以直接从前向后更新每一天的奶酪生产价格(包括存储成本)。即:更新当前周的价格:min(当前周价格,上一周的价格+S),更新后则直接计算即可。

代码:

#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 10005;
int C[MAX], Y[MAX];
int N, S;
LL cost;

int main()
{
    scanf("%d%d", &N, &S);
    for(int i=0; i
Packets(POJ 1017)

题目链接:Packets
参考博文:POJ1017 Packets(贪心算法训练)

  • 题目大意: 公司共有底面面积为11、22、33、44、55、66,高度同为H的六种产品,现在需要用最少的箱子打包,箱子的底面面积为6*6,高度为H。
  • 思路:先装大的后装小的,空隙之间可能装小的,尽量填满空隙。因此需要从大到小以此模拟装入,具体参考博文。

代码:

#include 
#include 
using namespace std;

int a1, a2, a3, a4, a5, a6, s3, s2;

int main()
{
    while(scanf("%d%d%d%d%d%d", &a1, &a2, &a3, &a4, &a5, &a6)!=EOF)
    {
        if(a1+a2+a3+a4+a5+a6==0) break;
        int p = 0;
        p += a6/1;
        p += a5/1;
        a1 = max(a1-a5*11, 0);
        p += a4/1;
        if(a2<5*a4) a1 = max(a1-(5*a4-a2)*4, 0);
        a2 = max(a2-5*a4, 0);
        p += a3/4;
        s3 = a3%4;
        if(s3) p++;
        if(s3==1)
        {
            if(a2<5) a1 = max(a1-(5-a2)*4-7, 0);
            else     a1 = max(a1-7, 0);
            a2 = max(a2-5, 0);
        }
        else if(s3==2)
        {
            if(a2<3) a1 = max(a1-(3-a2)*4-6, 0);
            else     a1 = max(a1-6, 0);
            a2 = max(a2-3, 0);
        }
        else if(s3==3)
        {
            if(a2<1) a1 = max(a1-(1-a2)*4-5, 0);
            else     a1 = max(a1-5, 0);
            a2 = max(a2-1, 0);
        }
        p += a2/9;
        s2 = a2%9;
        if(s2!=0) p++;
        if(s2) a1 = max(a1-(36-4*s2), 0);
        p += (a1+35)/36;

        printf("%d\n", p);
    }
    return 0;
}
Allowance(POJ 3040)

题目链接:Allowance
参考博文:Allowance(POJ-3040)

  • 题目大意:每周至少给牛 c 元,有 n 种硬币,已知每种硬币币值和数量,求最多坚持的周数。
  • 思路:将硬币从小到大排序,大于等于 c 元的硬币直接支付,然后将不能直接支付的先从大到小凑到接近 c ,再从小到大向上加,如果能达到就记录并开始下一轮,如果不能就退出。要注意的是,硬币无法拆分,因此只能多给不能少给。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1005;
const int INF = 1<<29;
struct Node
{
    int num, value;
    bool operator < (const Node &A) const
    {
        return value < A.value;
    }
};
int use[MAX];//记录硬币一周使用的个数
int n, c, cnt;

int main()
{
    cnt = 0;
    scanf("%d%d", &n, &c);

    for(int i=1; i<=n; i++)
        scanf("%d%d", &coin[i].value, &coin[i].num);

    sort(coin+1, coin+1+n);
    //找出硬币币值大于c元的数目
    for(int i=1; i=c)
        {
            cnt += coin[i].num;//直接支付
            coin[i].num = 0;//清零
        }
    }

    while(true)
    {
        bool flag = false;
        int sum = c;
        memset(use, 0, sizeof(use));

        //从大到小逼近c
        for(int i=n; i>0; i--)
        {
            if(coin[i].num>0)
            {
                use[i] = min(sum/coin[i].value, coin[i].num);
                sum -= use[i]*coin[i].value;
                if(sum==0)
                {
                    flag = true;
                    break;
                }
            }
        }

        //从小到大抵达或略微超过c
        if(sum>0)
        {
            for(int i=1; i<=n; i++)
            {
                if(coin[i].num-use[i]>0)
                {
                    while(use[i]0)
                minn = min(minn, coin[i].num/use[i]);//记录所有硬币中最小的使用周数
        }

        for(int i=1; i<=n; i++)
        {
            if(use[i]>0)//按照求出的使用周数,更新硬币的数目
                coin[i].num -= minn*use[i];
        }
        cnt += minn;
    }
    printf("%d\n", cnt);

    return 0;
}
Stripies(POJ 1862)

题目链接:Stripies

  • 题目大意:从N个数任取两个数按2 × \times ×sqrt(a*b)合成新数放回,求最后那个数的最小值。
  • 思路:贪心策略是使尽量使大的数多参与开方运算。每次取出最大和次大的数字运算,直至剩下一个数字。因为运算公式的原因,所以运算之后的结果一定是最大的数字,所以可以简化计算,直接排序之后遍历计算。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 200;
bool cmp(int a, int b)
{
    return a>b;
}
int main()
{
    int n, m[MAX];
    cin >> n;
    for(int i=0; i> m[i];
    sort(m, m+n, cmp);//从大到小排序

    double b = m[0];
    for(int i=1; i
Protecting the Flowers(POJ 3262)

题目链接:Protecting the Flowers

  • 题目大意:n头牛在吃花,每头牛牵走(避免吃花)要花费2*t秒(来回),每头牛吃花的速率是d,要你自己设计一个牵走牛的顺序,使被牛吃的花最少。
  • 思路:贪心算法。其实就是按d/t从大到小排序,然后依次牵走。重点是可能超时,需要在代码优化上下点功夫。

代码:

#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 100005;
struct Node
{
    int t, d;
};
bool cmp(Node a, Node b)
{
    double ap = a.d/(double)a.t;
    double bp = b.d/(double)b.t;
    return ap>bp;
}
Node C[MAX];
LL ans = 0, s = 0;

int main()
{
    int N, T, D;
    while(scanf("%d", &N)!=EOF)
    {
        for(int i=0; i

动态规划

Sumsets(POJ 2229)

题目链接:Sumsets
参考博文:动态规划之划分数
DP:Sumsets(POJ 2229)

  • 题目大意:给一个数,将其进行分解,只能分解为2的幂次,最多能分解多少种。

代码:

#include 
#include 
#include 
using namespace std;

const int MAX = 1000001;
int dp[MAX];
int c;

int main()
{
    int N;
    while(cin >> N)
    {
        dp[0] = 1;
        c = 1;
        for(int i=1; i<=20 && c<=N; i++)
        {
            for(int j=c; j<=N; j++)
            {
                dp[j] = (dp[j]+dp[j-c])%1000000000;
            }
            c = c*2;//累乘,节省了时间
        }
        cout << dp[N] << endl;
    }
    return 0;
}
Milking Time(POJ 3616)

题目链接:Milking Time
参考博文:poj 3616 Milking Time —DP(带权重的区间动态规划)

  • 题目大意:题意就是有m头牛,每一只牛有一个产奶的时间段和一个奶量,Bessie可以去给每一头奶牛挤奶,但是每次给一个奶牛挤奶之后必须休息R分钟,问Bessie选择怎么挤奶可以使挤出的奶最多。
  • 思路:
    • 状态构造:dp[i]代表在前i个区间可以获得的最大奶量。
    • 终态:dp[M],M是牛的数量。
    • 状态转移方程:dp[i] = max(dp[i-1], dp[p[i]]+w[i](其中p[i]表示在第i个区间之前最近的一个与第i个区间不冲突的区间的下标,w[i]表示第i个区间的奶量。
    • 初始化:dp[0] = 0;

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1000001;
struct Node
{
    int s, e, v;
    bool operator < (const Node &A) const
    {
        return e=b.e+R || b.s>=a.e+R) return 1;
    else return 0;
}
void Calculate_P(int M, int R)
{
    p[1] = 0;
    for(int i=M; i>1; i--)
    {
        int k=i-1;
        while(k>0 && !check(C[k], C[i]))
            k--;
        p[i] = k;
    }
}
int main()
{
    while(scanf("%d%d%d", &N, &M, &R)!=EOF)
    {
        for(int i=1; i<=M; i++)
        {
            scanf("%d%d%d", &C[i].s, &C[i].e, &C[i].v);
        }
        sort(C+1, C+M+1);//按结束点排序
        Calculate_P(M, R);//计算每一个区间前面可以接着的最近区间下标
        memset(dp, 0, sizeof(dp));
        for(int i=1; i<=M; i++)
        {
            dp[i] = max(dp[i-1], dp[p[i]]+C[i].v);//第i个区间是否使用
        }
        printf("%d\n", dp[M]);
    }
    return 0;
}

数据结构

Sunscreen(POJ 3614)

题目链接:Sunscreen
参考博文:Sunscreen (poj 3614 贪心+优先队列)

  • 题目大意:有c头牛晒太阳,每头牛都有一个能承受辐射的范围(min~max),现在有 l 种防晒霜,每种防晒霜都能将辐射值固定在spf,每种防晒霜都有一定的数量num。每头牛用最多一种防晒霜,问最多能满足多少头牛。
  • 思路:先将防晒霜按照spf从小到大排序,再将牛按照min从小到大排序,最后按序取出防晒霜,如果牛的min小于等于spf则入队列,将所有满足条件的牛入队列,然后找出其max最小的牛且max>=spf。循环以上步骤直到防晒霜用完或牛遍历完。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 2505;
struct Sun
{
    int spf, num;
};
struct Cow
{
    int min, max;
};
struct cmp
{
    bool operator ()(Cow a, Cow b)
    {
        return a.max>b.max;
    }
};
bool cmp1(Cow a, Cow b)
{
    return a.min, cmp> Q;
Cow Cow[MAX], p;
Sun S[MAX];

int main()
{
    int C, L;
    cin >> C >> L;
    for(int i=0; i> Cow[i].min >> Cow[i].max;
    for(int i=0; i> S[i].spf >> S[i].num;
    sort(Cow, Cow+C, cmp1);
    Cow[C].min = 1<<29, Cow[C].max = 1<<29;//哨点
    sort(S, S+L, cmp2);
   
    int k = 0, ans = 0;
    for(int i=0; i<=C; i++)
    {
        if(k==L) break;
        if(Cow[i].min<=S[k].spf)
            Q.push(Cow[i]);
        else if(!Q.empty())//符合该防晒霜的牛已经全部进队列
        {
            p = Q.top();
            Q.pop();
            while(p.max=S[k].spf)
            {
                S[k].num--;
                if(S[k].num==0)
                    k++;
                ans++;
            }
            i--;
        }
        else if(Q.empty())//该防晒霜不能用,换下一个
        {
            k++, i--;
        }
    }
    cout << ans << endl;
    return 0;
}
Moo University - Financial Aid(POJ 2010)

题目链接:Moo University - Financial Aid
参考博文:POJ 2010 Moo University – Financial Aid 题解 《挑战程序设计竞赛》

  • 题目大意:从C头奶牛中招收N头。它们分别得分score_i,需要资助学费aid_i。希望新生所需资助不超过F,同时得分中位数最高。求此中位数。
  • 思路:先将奶牛排序,考虑每个奶牛作为中位数时,比它分数低(前面的)的那群牛的学费总和lower_i(选取N/2个最少学费之和),后面的总和upper_i(选取N/2个最少学费之和)。然后从分数高往分数低扫描,满足aid_i + lower_i + upper_i <= F的第一个解就是最优解。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 100005;
struct Cow
{
    int s, m;
};
bool cmp(Cow a, Cow b)
{
    return a.s>b.s;
}
struct cmp1
{
    bool operator ()(Cow a, Cow b)
    {
        return a.m, cmp1>q, p;//取最大的学费

int main()
{
    while(scanf("%d%d%d", &N, &C, &F)!=EOF)
    {
        for(int i=0; i=n)
            {
                lower[i] = total;
            }else
            {
                lower[i] = 0;
            }
            q.push(c[i]);
            total += c[i].m;
            if(q.size()>n)
            {
                total -= q.top().m;
                q.pop();
            }
        }
        //求upper的值
        total = 0;
        for(int i=C-1; i>=0; i--)
        {
            if(p.size()>=n)
            {
                upper[i] = total;
            }else
            {
                upper[i] = 0;
            }
            p.push(c[i]);
            total += c[i].m;
            if(p.size()>n)
            {
                total -= p.top().m;
                p.pop();
            }
        }
        //筛选出最终结果
        int ans = -1;
        for(int i=C; i>=0; i--)
        {
            if(lower[i]+c[i].m+upper[i]<=F)//判断条件
            {
                ans = c[i].s;
                break;
            }
        }
        if(ans==-1)
            printf("-1\n");
        else
            printf("%d\n", ans);
    }
    return 0;
}

并查集

Wireless Network

题目链接:Wireless Network

  • 题目大意:一些结点需要维修,并检查一些结点是否连通。给出结点数目,结点之间的连通半径,并给出所有结点的坐标,最后给出相应操作,维修和检验联通,每一次检验均需要给出检验结果。
  • 思路:并查集题。在维修时,判断该结点是否可以与之间维修的结点并入一个集合(即连通),检验时直接用并查集检验。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1005;
struct Node
{
    int x, y;
};
Node C[MAX];
int a[MAX], d, N, id, id2;
int pre[MAX], Rank[MAX];
char order;
bool check(int x1, int y1, int x2, int y2)
{
    if(sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))<=(double)d)
    {
        return true;
    }
    return false;
}

void Init()
{
    for(int i=0; iRank[fy])
    {
        pre[fy] = fx;
    }else
    {
        pre[fx] = fy;
        if(Rank[fx]==Rank[fy]) Rank[fy]++;
    }
}
bool same(int x, int y)
{
    return findRoot(x)==findRoot(y);
}

int main()
{
    scanf("%d%d", &N, &d);
    Init();
    for(int i=1; i<=N; i++)
    {
        scanf("%d%d", &C[i].x, &C[i].y);
    }
    int k = 0;
    while(scanf("%c%d", &order, &id)!=EOF)
    {
        if(order=='O')
        {
            a[k++] = id;
            for(int i=0; i
Find them, Catch them(POJ 1703)

题目链接:Find them, Catch them

  • 题目大意:在一个城市里有两种不同的犯罪团伙。首先输入T表示有T组测试,然后输入N和M,表示有N个罪犯(编号从1到N)而且接下来有M个操作。操作分为两种:
    1.D a b,表示编号为a和b的两个罪犯属于不同的犯罪团伙;
    2.A a b,表示询问编号为a和b的两个罪犯是否是同一个犯罪团伙或者不确定。
    对于每一个A操作,根据题意都要有相应的回答(输出)。
  • 思路:这道题目类似食物链的题目食物链(相关讲解),属于种类并查集,即无法准确确定一个类具体属于那一个种类,只能知道不同类之间的关系,这样就需要维护比原始数目大种类个数倍的数组,同时更新每一个每一个阶段的数组,维护其关系而不是维护其种类。
    本题目的并查集表示A和B分别属于两个集团的关系同时存在。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 100005;
int pre[MAX*2], Rank[MAX*2];
int N = MAX-1;

//并查集表示关系同时存在
void Init()
{
    for(int i=0; i<=2*MAX; i++)
    {
        pre[i] = i;
        Rank[i] = 1;
    }
}
int findRoot(int x)
{
    int r = x;
    while(r!=pre[r])
    {
        r = pre[r];
    }
    int j=x, i;
    while(j!=r)
    {
        i = pre[j];
        pre[j] = r;
        j = i;
    }
    return r;
}
void join(int x, int y)
{
    int fx = findRoot(x), fy = findRoot(y);
    if(Rank[fx]>Rank[fy])
    {
        pre[fy] = fx;
    }else
    {
        pre[fx] = fy;
        if(Rank[fx]==Rank[fy]) Rank[fy]++;
    }
}
bool same(int x, int y)
{
    return findRoot(x)==findRoot(y);
}

int main()
{
    int T, N, M, a, b;
    char order;
    scanf("%d", &T);
    while(T--)
    {
        Init();
        scanf("%d%d", &N, &M);
        for(int i=0; i

图论

Six Degrees of Cowvin Bacon(POJ 2139)

题目链接:Six Degrees of Cowvin Bacon

  • 题目大意:先根据题意建立图,然后求出每个点到其他每个点的最短距离的均值(即总和/可以到达的点的个数)
  • 思路:使用的多源最短路径中的Floyd算法。得到了所有结点之间的最短距离后,遍历每一个点的均值,然后得到最小的值就是结果。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 500;
const int INF = 1<<29;
int G[MAX][MAX], a[MAX];
int cnt = 0, Min = INF, ans;
int N, M, n;

int main()
{
    scanf("%d%d", &N, &M);
    for(int i=0; i<=N; i++)
    {
        for(int j=0; j<=N; j++)
            G[i][j] = INF;
    }
  
    while(M--)
    {
        scanf("%d", &n);
        for(int i=0; i=0; j--)
            {
                G[a[j]][a[i]] = 1;
                G[a[i]][a[j]] = 1;
            }
        }
    }
    
    for(int k=1; k<=N; k++)
    {
        for(int i=1; i<=N; i++)
        {
            if(G[i][k]==INF) continue;
            for(int j=1; j<=N; j++)
            {
                if(G[k][j]==INF) continue;
                G[i][j] = min(G[i][j], G[i][k]+G[k][j]);
            }
        }
    }
    Min = INF;
    double r;
    for(int i=1; i<=N; i++)
    {
        cnt = 0, ans = 0;
        for(int j=1; j<=N; j++)
        {
            if(i==j) continue;
            if(G[i][j]!=INF)
            {
                ans++;
                cnt += G[i][j];
            }
        }
        Min = min(Min, cnt);
        //cout << cnt << endl;
        r = Min/(1.0*ans)*100;
    }
    Min = r;
    printf("%d\n", Min);
    return 0;
}
Wormholes(POJ 3259)

题目链接:Wormholes

  • 题目大意:农夫john发现了一些虫洞,虫洞是一种在你到达虫洞之前把你送回目的地的一种方式,FJ的每个农场,由n块土地(编号为1-n),M条路,和W个 虫洞组成,FJ想从一块土地开始,经过若干条路和虫洞,返回到他最初开始走的地方并且时间要在他离开之前,或者恰好等于他离开的时间。
  • 思路:明显的使用带负权的单源最短路径算法,所有选择Bellman_Ford算法,最后判断一下是否有负环而已。裸题。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 505;
const int INF = 1<<29;
int G[MAX][MAX], d[MAX];
int F, N, M, W, En, S, E, T;
struct Edge
{
    int from, to, w;
}e[5005];
void Bellman_Ford(int s)
{
    fill(d, d+MAX, INF);
    d[s] = 0;
    int k = 0;
    while(1)
    {
        int flag = 0;
        for(int i=0; id[e[i].from]+e[i].w)
            {
                d[e[i].to] = d[e[i].from]+e[i].w;
                flag = 1;
            }
        }
        if(!flag) break;
        k++;
        if(k>=N) break;
    }
    if(k>=N)
        printf("YES\n");
    else
        printf("NO\n");
}
int main()
{
    scanf("%d", &F);
    while(F--)
    {
        En = 0;
 
        scanf("%d%d%d", &N, &M, &W);
        for(int i=0; i
Silver Cow Party(POJ 3268)

题目链接:Silver Cow Party

  • 题目大意:给定一些路线和花费,然后N头牛,每头牛要求按照最短路走到X处,并用最短路在返回,问这些牛中所有路线里最大值是多少。(注意是单向图)
  • 思路:可以先从X用Dijkstra,再将图的边的方向反过来,再用dijstra求一下X的单源最短路径,求二者和最大即可

代码:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1005;
const int INF = 1<<29;
int G[MAX][MAX];
int d[MAX], d2[MAX], vis[MAX];
int N, M, X, A, B, T;

void dijstra(int s, int (&d)[MAX])
{
    fill(d, d+N+2, INF);
    memset(vis, 0, sizeof(vis));
    d[s] = 0;

    while(1)
    {
        int minv = INF, u;
        for(int i=1; i<=N; i++)
        {
            if(vis[i]==0 && d[i]
Bad Cowtractors(POJ 2377)

题目链接:Bad Cowtractors

  • 题目大意:给定每条路线间需要的费用,建造一个最贵的网络线路(任意两节点都可以互相达到,但是不存在回路),求最多需要花费多少钱。
  • 思路:求最大生成树。则将边的权重取负,然后按照最小生成树计算即可。注意:使用邻接表可以方便处理结点之间有重边的情况,并且效率还高,推荐使用邻接表实现。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

/*
使用了邻接表,不需要考虑重边,因为所以的边均保存了;
如果是邻接矩阵,需要保存最大或最小的边
*/
const int MAX = 1005;
const int INF = 1<<29;
int N, M;
int A, B, C;
int vis[MAX], d[MAX];
struct Node
{
    int to, w;
    Node(int to=-1, int w=-1): to(to), w(w){}
};
vectorG[MAX];

void Prim()
{
    fill(d, d+MAX, INF);
    memset(vis, 0, sizeof(vis));
    d[1] = 0;
    while(1)
    {
        int Minv = INF, u;
        for(int i=1; i<=N; i++)
        {
            if(vis[i]==0 && d[i]v.w)
            {
                d[v.to] = v.w;
            }
        }
    }
    int sum = 0, flag = 0;
    //cout << endl;
    for(int i=1; i<=N; i++)
    {
        if(d[i]!=INF)
            sum += d[i];
        else
            flag = 1;
    }
    if(!flag)
        printf("%d\n", -sum);
    else
        printf("-1\n");
}
int main()
{
    cin >> N >> M;
    for(int i=0; i> A >> B >> C;
        G[A].push_back(Node(B, -C));
        G[B].push_back(Node(A, -C));
    }
    Prim();
    return 0;
}
Out of Hay(POJ 2395)

题目链接:Out of Hay

  • 题目大意:有n个农场,贝西在1号农场,要访问其他n-1个农场,给出m条路,a b c表示a农场到b农场路程为c(两个农场间可能有多条路)。贝西会挑选最短的路径来访问完这n-1个农场。 问在此过程中贝西会经过的最大边是多大?
  • 思路:求最小生成树上的最大边,注意有重边,使用邻接表。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

/*
使用了邻接表,不需要考虑重边,因为所以的边均保存了;
如果是邻接矩阵,需要保存最大或最小的边
*/
const int MAX = 2005;
const int INF = 1<<29;
int N, M;
int A, B, C;
int vis[MAX], d[MAX];
struct Node
{
    int to, w;
    Node(int to=-1, int w=-1): to(to), w(w){}
};
vectorG[MAX];

void Prim()
{
    fill(d, d+MAX, INF);
    memset(vis, 0, sizeof(vis));
    d[1] = 0;
    while(1)
    {
        int Minv = INF, u;
        for(int i=1; i<=N; i++)
        {
            if(vis[i]==0 && d[i]v.w)
            {
                d[v.to] = v.w;
            }
        }
    }
    int Max = 0;
    //cout << endl;
    for(int i=1; i<=N; i++)
    {
        Max = max(Max, d[i]);
    }
    printf("%d\n", Max);
}
int main()
{
    cin >> N >> M;
    for(int i=0; i> A >> B >> C;
        G[A].push_back(Node(B, C));
        G[B].push_back(Node(A, C));
    }
    Prim();
    return 0;
}

数论

Dead Fraction(POJ 1930)

题目链接:Dead Fraction
参考博文:poj 1930 Dead Fraction
参考博文:POJ - 1930 Dead Fraction(简单数学推理)

Prime Path(POJ 3126)

题目链接:Prime Path

  • 题目大意:给你两个四位数m和n,求m变到n至少需要几步;每次只能从个十百千上改变一位数字,并且改变后的数要是素数。
  • 思路:使用埃氏筛法来筛选出给定范围内的所有素数,然后使用BFS搜索到达n的最短路径,使用素数数组来作为判定减枝条件。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 10000;
int isPrime[MAX];
int vis[MAX];
int T, from, to;
struct Node
{
    int n, t;//n是数字,t是步数
    Node(int n=-1, int t=-1): n(n), t(t){}
};
queue q;
void getPrime()
{
    fill(isPrime, isPrime+MAX, 1);
    isPrime[1] = 0;
    for(int i=2; 2*i
X-factor Chains(POJ 3421)

题目链接:X-factor Chains
参考博文:POJ 3421 X-factor Chains (因式分解+排列组合)

  • 题目大意:一条整数链,要求相邻两数前一个整除后一个。给出链尾的数,求链的最大长度以及满足最大长度的不同链的数量。
  • 思路:因式分解的素因子个数即为链长。因为链中后一个数等于前一个数乘以某素因子(相当于1乘以一些素数然后得到X),所以链的数量即为这些素因子不相异的全排列数:A!/(a1!a2!a3!..)

代码:

#include 
#include 
#include 
using namespace std;

const int MAX = 1<<20+1;
typedef long long LL;
LL X, p[MAX], a[MAX];


int main()
{
    while(scanf("%lld", &X)!=EOF)
    {
        int cnt = 0;
        //素因子分解(因为从小到大分解,得到的一定是素数)
        for(LL i=2; i*i<=X; i++)
        {
            if(X%i==0)
            {
                p[cnt] = i;
                a[cnt++] = 1;
                X /= i;
            }
            while(X%i==0)
            {
                a[cnt-1]++;
                X /= i;
            }
        }
        if(X>1)//这一步容易遗忘
        {
            p[cnt] = X;
            a[cnt++] = 1;
        }
        //相关计算
        LL A = 0, ans = 1;
        for(int i=0; i
Semi-prime H-numbers(POJ 3292)

题目链接:Semi-prime H-numbers
参考博文:Semi-prime H-numbers POJ - 3292(简单打表)

  • 题目大意:定义一种数叫H-numbers,它是所有能除以四余一的数。
    在H-numbers中分三种数:
    1、H-primes,这种数只能被1和它本身整除,不能被其他的H-number整除,例如9是一个H-number,能被1,3,9整除,但3不是H-number,所以他是H-primes。
    2、H-semi-primes是由两个H-primes相乘得出的,H-semi-prime只能分解成两个H-prime数相乘。
    3、剩下的是H-composite。
    问给一个数,求1到这个数之间有多少个H-semi-primes。
  • 思路:打表得出所有H-semi-primes,然后使用数组累积得到输出结果。我的方法有点麻烦,虽然思想是一样的,还是代码能力不行。

先看别人的代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define up(i, x, y) for(int i = x; i <= y; i++)
#define down(i, x, y) for(int i = x; i >= y; i--)
#define MAXN ((int)1e6 + 10)
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int vis[MAXN];
int ans[MAXN];
void prime_()
{
    for(int i = 5; i <= MAXN; i += 4)
    {
        for(int j = 5; j <= MAXN; j += 4)
        {
            if(i * j > MAXN) break;//超出范围
         	//i 与 j 都不可以被分解,换句话说就是之前没有被构造出来过
            //i*j一定符合4*n+1,这时的i*j定是H-semi—prime,以后再以i * j作为因子的数就不可以构成了,仔细体会下
            if(!vis[i] && !vis[j]) vis[i * j] = 1;//得到H-semi—prime
    		//之前被构造出来过 所以 i * j 可以被分解成至少三个H-prime,所以不行
            else vis[i * j] = -1; 
        }
    }
 
    int len = 0;//累积
    for(int i = 1; i <= MAXN; i++){ //打表,计算出1-i之间的多少个H-semi—prime数
        if(vis[i] == 1) len++;
        vis[i] = len; //有点像前缀和
    }
}
int main()
{
    int n;
    prime_();
    while(~scanf("%d", &n) && n)
    {
        printf("%d %d\n", n, vis[n]);
    }
}

自己的代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 1000002;
int h, flag[MAX], ans[MAX];
vector v;
int r[MAX];

//打表+筛选
void isHPrime()
{
    memset(flag, 1, sizeof(flag));
    //得到H素数,存入数组,并将H-composite标记为0
    for(int i=1; i<=MAX/4; i++)
    {
        int j = 4*i+1;
        if(j>=MAX) break;
        if(flag[j])
        {
            for(int k=2; k=MAX) break;
                flag[4*a+1] = 0;
            }
            v.push_back(j);
        }
    }

    //标记所有的H_s_p
    int k = 0;
    r[k++] = 0;//作为一个初始数据方便下面的程序
    for(int i=0; i
Raising Modulo Numbers(POJ 1995)

题目链接:Raising Modulo Numbers

  • 题目大意:(A1^B1 +A2^B2+ … +AH^BH)mod M。
  • 思路:快速幂乘,只不过每乘一次或加一次都需要模M一次。

代码:

#include 
#include 
#include 
using namespace std;

typedef long long LL;
LL Z, H, M, A, B;

LL Pow(LL a, LL p)
{
    LL ans = 1, t = p;
    while(t)
    {
        if(t&1) ans = (a*ans)%M;
        a = (a*a)%M;
        t >>= 1;
    }
    return ans;
}

int main()
{
    scanf("%lld", &Z);
    while(Z--)
    {
        scanf("%lld%lld", &M, &H);
        LL cnt = 0;
        for(LL i=0; i
Pseudoprime numbers(POJ 3641)

题目链接:Pseudoprime numbers

  • 题目大意:给出 p 和 a,若 a^p 和a对 p 同余且 p 不是素数,则输出 yes,否则输出 no。
  • 思路:快速幂乘,且判断素数。

代码:

#include 
#include 
#include 
using namespace std;

typedef long long LL;
LL p, a;

int IsPrime(LL p)
{
    for(LL i=2; i*i<=p; i++)
    {
        if(p%i==0)
            return 0;
    }
    return 1;
}
LL Pow(LL a, LL p)
{
    LL ans = 1, t = p;
    while(t)
    {
        if(t&1) ans = (a*ans)%p;
        a = (a*a)%p;
        t >>= 1;
    }
    return ans;
}
void solve(LL a, LL p)
{
    if(IsPrime(p)||Pow(a, p)!=a%p)
        printf("no\n");
    else
        printf("yes\n");
}

int main()
{
    while(scanf("%lld%lld", &p, &a)!=EOF && (a+p))
    {
        solve(a, p);
    }
    return 0;
}

你可能感兴趣的:(挑战程序设计竞赛——经验篇)