2015ccpc南阳个人题解

 

  1. Secrete Master Plan

签到题,枚举4种情况判断相同即可。

#include<cstdio>

#include<cstring>

using namespace std;

int a[4][2];

void turn()

{

    int te = a[0][0];

    a[0][0] = a[1][0], a[1][0] = a[1][1], a[1][1] = a[0][1], a[0][1] = te;

}

int main()

{

    int t, n, i, j, ca = 1;

    scanf("%d", &t);

    while (t--)

    {

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

            for (j = 0; j < 2; j++)

                scanf("%d", a[i] + j);

        int flag = 0;

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

        {

            turn();

            if (a[0][0] == a[2][0] && a[0][1] == a[2][1] && a[1][0] == a[3][0] && a[1][1] == a[3][1])

                flag = 1;

        }

        printf("Case #%d: %s\n", ca++, flag ? "POSSIBLE" : "IMPOSSIBLE");

    }

    return 0;

}

 

 

L.    Huatuo's Medicine

输入输出签到题,输出2*n-1。

#include<cstdio>

#include<cstring>

using namespace std;

int main()

{

    int t, n, ca = 1;

    scanf("%d", &t);

    while (t--&&scanf("%d", &n))

        printf("Case #%d: %d\n", ca++, 2 * n - 1);

    return 0;

}

 

H. Sudoku

水题,写过搜索专题解9*9数独的再写这个完全没问题,怎么写都行。

#include<cstdio>

#include<cstring>

using namespace std;

char s[7][7];

int ok[5][5][5];

int flag;

void add(int x, int y, int z)

{

    int i, j;

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

        for (j = 0; j < 4; j++)

            if (x == i || j == y || (i / 2 == x / 2) && (j / 2 == y / 2))

                ok[i][j][z]++;

}

void del(int x, int y, int z)

{

    int i, j;

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

        for (j = 0; j < 4; j++)

            if (x == i || j == y || (i / 2 == x / 2) && (j / 2 == y / 2))

                ok[i][j][z]--;

}

void dfs(int x, int y)

{

    int i, j, k;

    if (x == 4)

    {

        flag = 1;

        return;

    }

    for (i = x; i < 4; i++)

        for (j = (i == x) ? y : 0; j < 4; j++)

            if (s[i][j] == '*')

            {

                for (k = 1; k <= 4; k++)

                    if (ok[i][j][k] == 0)

                    {

                        add(i, j, k);

                        s[i][j] = k + '0';

                        if (j == 3)

                            dfs(i + 1, 0);

                        else dfs(i, j + 1);

                        if (flag == 1)

                            return;

                        s[i][j] = '*';

                        del(i, j, k);

                    }

                return;

            }

    if (i == 4)

        flag = 1;

}

int main()

{

    int t, n, i, j, ca = 1;

    scanf("%d", &t);

    while (t--)

    {

        memset(ok, 0, sizeof(ok));

        flag = 0;

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

            scanf("%s", s[i]);

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

            for (j = 0; j < 4; j++)

                if (s[i][j] != '*')

                    add(i, j, s[i][j] - '0');

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

            for (j = 0; j < 4; j++)

                if (s[i][j] == '*')

                {

                    dfs(i, j);

                    break;

                }

        printf("Case #%d:\n", ca++);

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

            puts(s[i]);

    }

    return 0;

}

 

 

G. Ancient Go

简单题,问x走1步可不可能把o吃掉至少一颗。直接的想法是枚举每个空位,看x走这里会不会堵死o,复杂度O(n^4),没问题。

一个优化是直接dfs连通的o,看是否存在只连着一个空格的连通块。复杂度几乎是O(n^2)的。

#include<cstdio>

#include<cstring>

char ss[13][13];

int vis[13][13], v2[13][13], to[][2] = { 1,0,-1,0,0,1,0,-1 };

int dfs(int x, int y)

{

    vis[x][y] = 1;

    int i, xx, yy, sum = 0;

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

    {

        xx = x + to[i][0], yy = y + to[i][1];

        if (ss[xx][yy] == '.'&&v2[xx][yy] == 0)

            sum++, v2[xx][yy] = 1;

        if (ss[xx][yy] == 'o'&&vis[xx][yy] == 0)

            sum += dfs(xx, yy);

    }

    return sum;

}

int main()

{

    int i, j, t, ca = 1;

    scanf("%d", &t);

    while (t--)

    {

        memset(vis, 0, sizeof(vis));

        int flag = 0;

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

            scanf("%s", ss[i] + 1);

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

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

                if (ss[i][j] == 'o'&&vis[i][j] == 0)

                {

                    memset(v2, 0, sizeof(v2));

                    if (dfs(i, j) < 2)

                        flag = 1;

                }

        printf("Case #%d: %s\n", ca++, flag ? "Can kill in one move!!!" : "Can not kill in one move!!!");

    }

    return 0;

}

 

4个简单题,接下来是铜牌题。(这场的铜牌线还是很低..比icpc简单)

 

C. The Battle of Chibi

一长度为n的数组,其中选m个数,求这m个数是严格单增序列的种数。

公式不难想,设选y个数且结尾是第x个数且为严格单增序列的方案种数是dp[x][y]。

则dp[x][1]=1,dp[x][y]=∑dp[k][y-1],其中k<x且a[k]<a[x]。

可以这么写:

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

            dp[i][1] = 1;

        for (j = 2; j <= m; j++)

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

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

                    if (a[k] < a[i])

                        dp[i][j] = (dp[i][j] + dp[k][j - 1]);

然而O(n^3)一定会超时,而dp[n][m]下共有n*m个数这2个循环不能删,考虑优化求和的操作。

K<x且a[k]<a[x],想到数组求和,用树状数组更新求和(复杂度O(log(n)),二维线段树不好写且时间常数很大,不推荐)。注意因为用到了树状数组,改变了dp[x][y]的含义。代码中sum(x,y)是以小于等于第x小的元素为结尾长度为y的单增序列的数目,dp[x][y]成了辅助数组,其具体含义参见树状数组专题。

但a[i]的范围是10^9,还需要hash一下。

这题用到动态规划+树状数组+hash,复杂度O(n*m*log(n))。(还是有点难度的..)

#include<cstdio>

#include<cstring>

#include<map>

#include<algorithm>

using namespace std;

#define mm 1000000007

int n, m, a[1005], b[1005], dp[1005][1005];

void add(int i,int j, int val)//单点增加val

{

    for (; i <=n; i += i & -i)

        dp[i][j] = (dp[i][j] + val) % mm;

}

int sum(int i,int j)//1~i的元素和

{

    int s = 0;

    for (; i > 0; i -= i & -i)

        s = (s + dp[i][j]) % mm;

    return s;

}

int main()

{

    int t, i, j, z, ca = 1;

    scanf("%d", &t);

    while (t--&&scanf("%d%d", &n, &m))

    {

        map<int, int>mp;

        memset(dp, 0, sizeof(dp));

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

            scanf("%d", a + i), b[i] = a[i];

        sort(b + 1, b + n + 1);

        z = unique(b + 1, b + n + 1) - (b + 1);

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

            mp[b[i]] = i;

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

            a[i] = mp[a[i]];

        //此时a[i]=k表示a的第i个数为a数组中第k小的数 hash思想

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

            for (j = 1; j <= min(i, m); j++)

                if (j == 1)

                    add(a[i], 1, 1);

                else

                    add(a[i], j, sum(a[i] - 1, j - 1));

        printf("Case #%d: %d\n", ca++, sum(n, m));

    }

    return 0;

}

 

D. Pick The Sticks

题意理解是一大难点系列..

就是说给你一个一维的长条容器,再给一些长条,每个长条有固定的长度和价值。把这些长条放在容器上,只要每个长条的重心不超过容器且长条不重叠即可。问可获得的最大价值。

根据贪心思想,显然如果让一条不完全落在容器里,最优解一定是让它的重心落在容器边界。相当于可以取2件商品半价的01背包(注意只取1件的时候无视价格)。

细节是为了不让半价后的价格为小数,事先把所有长度乘2。

#include<cstring>

#include<cstdio>

#include<algorithm>

using namespace std;

int a[2005], v[2005], n, m;

long long dp[4005][4], ans;

int main()

{

    int t, i, j, k, ca = 1;

    scanf("%d", &t);

    while (t--&&scanf("%d%d", &n, &m))

    {

        ans = 0, m *= 2;

        memset(dp, 0, sizeof(dp));

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

        {

            scanf("%d%d", a + i, v + i);

            a[i] *= 2;

            ans = max(ans, 1LL * v[i]);

        }

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

            for (j = m; j >= a[i] / 2; j--)

                for (k = 0; k < 3; k++)

                {

                    if (j >= a[i])

                        dp[j][k] = max(dp[j][k], dp[j - a[i]][k] + v[i]);

                    if (k != 0)

                        dp[j][k] = max(dp[j][k], dp[j - a[i] / 2][k - 1] + v[i]);

                }

        printf("Case #%d: %lld\n", ca++, max(ans, dp[m][2]));

    }

    return 0;

}

 

 

6题铜首银尾,接下来是银牌题目..顺便一提,一般acm现场赛第一名在60分钟左右的成绩就是最终铜牌前列的成绩。但这场第一名很强,加上铜牌难度不如icpc,58分钟时第一名就7题(最终榜的银牌中上)了...

 

F. The Battle of Guandu

题意比较麻烦:给n个城镇m个战场,每个城镇有如下3个属性:向x战场派正方1人同时向y战场派反方 1人的费用是c。每个战场有一个重要程度:0则无所谓双方人数,1则该战场的正方人数大于等于反方人数,2则该战场的正方人数大于反方人数。

问每个战场都达成条件,至少需要多少费用(或不能达成条件)。

还是根据贪心原则,因为费用一次支付一人的,所以最少费用时,2级战场必然是正方只多一人(否则把这个人去掉一定会更省钱),而大于等于必然是等于(多派一个人必然会多花钱)。由于双方人数守恒,2级战场正方多人则0级战场正方一定少人。

从图论角度考虑,每个城镇相当于一个边,战场相当于点,有:

对于每个城镇,相当于y到x的的长度为c的边。起点为所有0级战场(相当于从0级战场借人到2级战场),跑一遍多起点最短路,最后如果所有2级战场都可达,则所有2级战场到源的最短路和即答案,否则不可达成。

多起点最短路用迪杰斯特拉算法,堆优化后复杂度为O(nlogn)。(用优先队列实现就行,最短路常用模板)。

注意答案可能会超int。

很棒的题目..比较看思维,想到之后很快能写出来代码。

#include<cstdio>

#include<cstring>

#include<queue>

#include<algorithm>

using namespace std;

#define mm 100005

#define LL long long

int x[mm], y[mm], w[mm], c[mm], h[mm], vi[mm], n, m, k;

LL d[mm];

struct { int v, w, ne; }a[mm];

void add(int u, int v, int w)//初始k=1

{

    a[k].v = v, a[k].w = w, a[k].ne = h[u], h[u] = k++;

}

struct node

{

    int v;

    LL c;

    node(int _v = 0, LL _c = 0) :v(_v), c(_c) {}

    bool operator <(const node &r)const

    {

        return c > r.c;

    }

};

LL dij()

{

    int i, v, u;

    LL ww, ans = 0;

    priority_queue<node>q;

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

        vi[i] = 0, d[i] = 233333333333;

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

        if (w[i] == 0)

            d[i] = 0, q.push(node(i, 0));

    node tmp;

    while (!q.empty())

    {

        tmp = q.top(), q.pop(), u = tmp.v;

        if (vi[u])

            continue;

        vi[u] = 1;

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

        {

            v = a[i].v, ww = a[i].w;

            if (!vi[v] && d[v] > d[u] + ww)

                d[v] = d[u] + ww, q.push(node(v, d[v]));

        }

    }

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

        if (w[i] == 2)

            if (d[i] == 233333333333)

                return -1;

            else ans += d[i];

    return ans;

}

int main()

{

    int t, i, ca = 1;

    scanf("%d", &t);

    while (t--&&scanf("%d%d", &n, &m))

    {

        k = 1;

        memset(h, 0, sizeof(h));

        memset(a, 0, sizeof(a));

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

            scanf("%d", x + i);

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

            scanf("%d", y + i);

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

            scanf("%d", c + i);

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

            scanf("%d", w + i);

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

            add(y[i], x[i], c[i]);

        printf("Case #%d: %lld\n", ca++, dij());

    }

    return 0;

}

 

K.Game Rooms

题意很简单,相当于n个楼层,每个楼层男女各x,y人,每层只能建一个厕所,为了让所有人上厕所的距离尽量少。求最优时所有人上一次厕所的距离和。

还是挺不好想的...看了题解。

dp[i][k]相当于在i层建了第k种厕所(k=0或1)则有:

dp[i][k] = min(dp[i][k], dp[j][1 - k] + fx(j + 1, i, k));

意思是:枚举所有前一个另一种厕所的位置j(即[j+1,i]都是第k种厕所),并把 [j+1,i]这个区间的k种人分散到2边。

fx(i,j,k)表示[i,j]这个区间的所有k种人上一次厕所的距离和。(预处理前缀和就可以O(1)算出)

状态涉及是连续一段区间的dp挺常见的,公式也非常简单。

但感觉这题的思想还是很玄妙..可能是动态规划做的少。

细节是最终答案可能很大,用longlong且初始化最大值时尽量大..

#include<cstdio>

#include<map>

#include<algorithm>

using namespace std;

#define mm 4005

#define LL long long

int n;

LL dp[mm][2], s1[mm][2], s2[mm][2], ti[mm][2];

LL left(int i, int j, int k)

{//[i,j]的k种人到i-1的代价和

    LL ans1 = s1[j][k] - s1[i - 1][k], ans2 = s2[j][k] - s2[i - 1][k];

    return ans2 - ans1 * (i - 1);

}

LL right(int i, int j, int k)

{//[i,j]的k种人到j+1的代价和

    LL ans1 = s1[j][k] - s1[i - 1][k], ans2 = s2[j][k] - s2[i - 1][k];

    return ans1 * (j + 1) - ans2;

}

LL fx(int i, int j, int k)

{//[i,j]的k种人到2边的代价和

    if (i == 1)

        return right(i, j, k);

    if (j == n)

        return left(i, j, k);

    int mid = (i + j) / 2;

    return left(i, mid, k) + right(mid + 1, j, k);

}

int main()

{

    int t, i, j, k, ca = 1;

    scanf("%d", &t);

    while (t--&&scanf("%d", &n))

    {

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

            for (j = 0; j < 2; j++)

            {

                scanf("%lld", ti[i] + j);

                s1[i][j] = s1[i - 1][j] + ti[i][j];

                s2[i][j] = s2[i - 1][j] + i * ti[i][j];

                //注意如果ti是int型,i*ti会溢出

            }

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

            for (j = 0; j < 2; j++)

                dp[i][j] = fx(1, i, j);

        dp[n][0] = dp[n][1] = 2333333333333333333;

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

            for (k = 0; k < 2; k++)

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

                    dp[i][k] = min(dp[i][k], dp[j][1 - k] + fx(j + 1, i, k));

        printf("Case #%d: %lld\n", ca++, min(dp[n][0], dp[n][1]));

    }

    return 0;

}

 

再后面题就明显困难起来了,看文字题解仍然写不出代码(dfs树上的所有独立回路再高斯消元解异或方程组,dp套dp,奇怪的计算几何..)..不勉强了..(现场赛再多ac一题兴许就有金牌,这场金牌难度感觉和icpc差不多)

 

 

 

 

猜你喜欢

转载自blog.csdn.net/weixin_40191952/article/details/89087665
今日推荐