2017_SDNU_ACM-ICPC_Provincial_Team_Selection_Round_2【--完结--】

来源:互联网 发布:windows 程序返回值 编辑:程序博客网 时间:2024/05/17 09:29

山东省第八届acm大学程序设计竞赛山师选拔赛第二场

(声明:标题是自己取的,如果有语法错误的话与他人无关)


-----------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------


Problem_A(最近公共祖先)

题意:SDNU的ACM集训队陷入了混乱中,如果你加入,你要成为一个人的小跟班,我们把你跟着的那个人称为“dalao”(/doge)。例如有两个菜鸟C和D加入了,他们的dalao是A,我们就称A是C和D的最近公共dalao。再比如C是E和F的dalao,那么E和C的最近公共dalao就是A。一共n个人,给你n-1组关系,问输入的两个人的最近公共dalao是谁,有的话输出他的名字,没有输出“I am so bad.”(注意一下Hint里的提示:A也是A的daolao
思路:一开始考虑用map来记录这个点的根节点,关系记录完后,不断向上查询,直至找到它的祖先,然后必然的TLE。其实这个题就是个经过封装的求LCA的题目,套上模板就能A了(主要目的应该是让我们学一下LCA吧。
AC代码
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>#include <queue>using namespace std;const int MAXN = 10010;const int DEG = 20;struct Edge{    int to,next;} edge[MAXN*2];int head[MAXN],tot;void addedge(int u,int v){    edge[tot].to = v;    edge[tot].next = head[u];    head[u] = tot++;}void init(){    tot = 0;    memset(head,-1,sizeof(head));}int fa[MAXN][DEG];//fa[i][j]表示结点i的第2^j个祖先int deg[MAXN];//深度数组void BFS(int root){    queue<int>que;    deg[root] = 0;    fa[root][0] = root;    que.push(root);    while(!que.empty())    {        int tmp = que.front();        que.pop();        for(int i = 1; i < DEG; i++)    fa[tmp][i] = fa[fa[tmp][i-1]][i-1];        for(int i = head[tmp]; i != -1; i = edge[i].next)        {            int v = edge[i].to;            if(v == fa[tmp][0])continue;            deg[v] = deg[tmp] + 1;            fa[v][0] = tmp;            que.push(v);        }    }}int LCA(int u,int v){    if(deg[u] > deg[v])swap(u,v);    int hu = deg[u], hv = deg[v];    int tu = u, tv = v;    for(int det = hv-hu, i = 0; det ; det>>=1, i++)   if(det&1)    tv = fa[tv][i];    if(tu == tv)return tu;    for(int i = DEG-1; i >= 0; i--)    {        if(fa[tu][i] == fa[tv][i])    continue;        tu = fa[tu][i];        tv = fa[tv][i];    }    return fa[tu][0];}bool flag[MAXN];int main(){    int T;    int n;    int u,v;    while(~scanf("%d",&n))    {        init();        memset(fa,0,sizeof(fa));        memset(deg,0,sizeof(deg));        memset(flag,false,sizeof(flag));        for(int i = 1; i < n; i++)        {            scanf("%d%d",&u,&v);            addedge(u,v);            addedge(v,u);            flag[v] = true;        }        int root;        for(int i = 1; i <= n; i++)    if(!flag[i])            {                root = i;                break;            }        BFS(root);        scanf("%d%d",&u,&v);        if(LCA(u,v))            printf("%d\n",LCA(u,v));        else            puts("I am so bad.");    }    return 0;}


Problem_B(矩阵快速幂)

题意:对任意的(1+sqrt(2)) ^n我们是否能找到对应的m使得上式化简为sqrt(m) +sqrt(m-1),如果有,请输出m%(1e9+7)的值,没有输出"I want to talk a joke."

思路:先写几个数字看看什么情况。化简后会看出(整数部分)系数为1、3、7、17……带有sqrt(2)的部分的系数为1、2、5、12……后者数值等于它上一个数的这两部分的系数之和(2=1+1,5=3+2,12=7+5……),前者的数值等于和他一个数的sqrt(2)的部分的系数+它上一个数的sqrt(2)的部分的系数(3=2+1,7=5+2,17=12+5……)。根据这个规律构造一个四阶矩阵,然后套上模板就可以了。注意每次乘完都要%MOD一下,防止溢出。

关于输出:如果n是奇数的话,就输出(整数部分)系数的^2,否则输出它的^2+1(自己推一推便知道了。

讲的很有道理的一篇博客(还没看懂---看OJ上交的状态,师哥们用了三阶、二阶的矩阵就求出来了..   I good vegetables a!
AC代码
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int MAX = 10010;const int MOD = 1e9 + 7;const int ch = 4;struct matrix{    long long mat[ch][ch];};matrix multiply(matrix a, matrix b) //构造矩阵乘法{    int i, j, k;    matrix t;    memset(t.mat, 0, sizeof(t. mat));    for(i = 0; i < ch; ++i)        for(j = 0; j < ch; ++j)            for(k = 0; k < ch; ++k)                t.mat[i][j] = (t.mat[i][j] + a.mat[i][k]*b.mat[k][j]%MOD) % MOD;    return t;}long long fibonacci(long long n){    long long tem = n;    n--;    matrix base, ans;    memset(base.mat, 0, sizeof(base.mat));    memset(ans.mat, 0, sizeof(ans.mat));    base.mat[2][0] = base.mat[2][2] = base.mat[3][1] = base.mat[3][2] = base.mat[3][3] = 1;    base.mat[2][3] = 2;    ans.mat[0][1] = ans.mat[0][0] = 1;    ans.mat[0][2] = 2;    ans.mat[0][3] = 3;    while(n)    {        if(n & 1) ans = multiply(ans, base);        base = multiply(base, base);        n >>= 1;    }    //cout << tem << endl;    if(!(tem&1)) return ans.mat[0][1]*ans.mat[0][1]%MOD;    return (ans.mat[0][1]*ans.mat[0][1]+1)%MOD;}int main(){    long long n;    while(cin >> n)        cout << fibonacci(n) << endl;    return 0;}

Problem_C(签到题)

题意:给定1-n个城市,你处在x位置,求出满足|x-i|<=r的正整数i的可能取到的数。

思路:因为给定了n的范围,所以一共会有4种情况,即左越界,左不越界,右越界,右不越界。4个if,轻松搞定。(我这个人比较懒,用一个for遍历了一遍,符合条件就让ans++,否则不作操作。比较而言当然是前者时间复杂度更低了,比赛时当然是要选择最优解法。

AC代码:

#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;int main(){    int t;    double n, r, x;  //用int就行    scanf("%d",&t);    while(t--)    {        int ans = 0;        scanf("%lf%lf%lf",&n,&r,&x);        for(int i = 1; i <= n; ++i)        {            if(fabs(x-i) <= r) ans++;        }        cout << ans << endl;    }    return 0;}


Problem_D(线段树单点更新求和+期望公式)

题意:在一个长度为n的数组里,分别执行两种操作,1是把位置为x的元素改为y;2是求出区间l到r的方差。
思路:线段树的单点更新及求和,直接套模板就可以。
期望公式:D(x) = E(x^2) - E(x)^2。而求区间的方差此公式经过化简可得,D(x) = num * sum2 - sum * sum,其中num是区间的元素总个数,sum2是区间内每个元素的平方求和,sum是区间内的每个元素的和。
AC代码:
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int MAX = 1<<16;struct node{    int l, r;    long long sum, sum2;} tree[MAX<<2];long long a[MAX];long long sum, sum2;void pushup(int rt){    tree[rt].sum = tree[rt<<1].sum + tree[rt<<1|1].sum;    tree[rt].sum2 = tree[rt<<1].sum2 + tree[rt<<1|1].sum2;}void build(int l, int r, int rt){    tree[rt].l = l;    tree[rt].r = r;    tree[rt].sum = 0;    if(l == r)    {        tree[rt].sum = a[l];        tree[rt].sum2 = a[l]*a[l];        return ;    }    int mid = (tree[rt].l + tree[rt].r) >> 1;    //递归建树    build(l, mid, rt<<1);    build(mid+1, r, rt<<1|1);    pushup(rt);}void update(int x, int y, int rt){    //更新这个区间的值    if(tree[rt].l == tree[rt].r)    {        tree[rt].sum = y;        tree[rt].sum2 = y*y;        return ;    }    int mid = (tree[rt].l + tree[rt].r) >> 1;    if(x <= mid) update(x, y, rt<<1);    else update(x, y, rt<<1|1);    pushup(rt);}void query(int x, int y, int rt){    if(tree[rt].l == x && tree[rt].r == y)    {        sum += tree[rt].sum;        sum2 += tree[rt].sum2;        return ;    }    int mid = (tree[rt].l + tree[rt].r) >> 1;    if(y <= mid)  query(x, y, rt<<1);    else if(x > mid)  query(x, y, rt<<1|1);    else    {        query(x, mid, rt<<1);        query(mid+1, y, rt<<1|1);    }}int main(){    int n, m;    while(~scanf("%d%d",&n,&m))    {        for(int i = 1; i <= n; ++i)            scanf("%lld",&a[i]);        build(1, n, 1);        while(m--)        {            int q;            scanf("%d",&q);            if(q == 1)            {                int pos, num;                scanf("%d%d",&pos,&num);                update(pos, num, 1);            }            else            {                int l, r;                sum = sum2 = 0;                scanf("%d%d",&l,&r);                query(l, r, 1);                long long ans = (r-l+1)*sum2 - sum*sum;                cout << ans << endl;            }        }    }    return 0;}


Problem_E(最短路)

题意:告诉你一系列的公交线路,问能否从1号站到m号站,如果能,输出最少的换乘次数,不能输出-1。
思路:建图。把任一公交线路中任意两点能到达的位置距离设置为1,这样条件都结束后不能到达的位置距离就是INF。然后转化为最短路问题解决就行了。
对输入的处理:每行结束的标志是'\n',可以用gets读入一行后把数字当作字符挨个处理;其他人也有用scanf("%d%c",&d,&ch)这样读入的,当ch=='\n'时break;也可以像我一样用stringstream。
AC代码:
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>#include <sstream>using namespace std;const int INF = 0x3f3f3f3f;const int MAX = 510;int n;int adj[MAX][MAX];int a[MAX], dis[MAX];bool vis[MAX];void init(){    for(int i = 1; i <= 500; ++i)    {        for(int j = 1; j <= 500; ++j)        {            if(i == j) adj[i][j] = 0;            else adj[i][j] = INF;        }    }}void Dij(){    int tem, minx;    memset(vis, false, sizeof(vis));    for(int i = 0; i <= n; ++i)        dis[i] = adj[1][i];    dis[1] = 0;    vis[1] = true;    for(int i = 1; i < n; ++i)    {        minx = INF;        for(int j = 1; j <= n; ++j)        {            if(!vis[j] && dis[j] < minx)            {                minx = dis[j];                tem = j;            }        }        if(minx == INF) break;        vis[tem] = 1;        for(int j = 1; j <= n; ++j)        {            if(!vis[j] && adj[tem][j] != INF && dis[tem] + adj[tem][j] <dis[j])            {                dis[j] = adj[tem][j] + dis[tem];            }        }    }}int main(){    int t;    int item;    string stem, s;    while(~scanf("%d",&t))    {        init();        scanf("%d",&n);        getchar();        while(t--)        {            int cou = 0;            getline(cin, stem);            stringstream ss (stem);            while(ss >> item)            {                a[cou++] = item;            }            for(int i = 0; i < cou-1; ++i)                for(int j = i+1; j < cou; ++j)            {                adj[a[i]][a[j]] = 1;            }        }        Dij();        if(dis[n] == INF) cout << "-1" << endl;        else  cout << dis[n]-1 << endl;    }    return 0;}

Problem_F(思维)

题意:一群美女排成一排,各有各的颜值。现在GOD超要从中选取一些连续排列的美女,要求他们的颜值满足先增大后减小。如果有,依次输出起点美女和重点美女的坐标;多组的话输出长度最长的那组的两个下标;没有符合题意的输出-1 -1。
思路:之前做过一个合唱队列的题目(要求求出最长先严格上升后严格下降的子序列的长度,我从头到尾求了每个点作为终点的最长上升子序列的长度,从尾往前求出了每个点作为终点的最长上升子序列的长度,然后遍历一遍求出最大的两者之和-1的值),开始以为是类似的,觉得有点麻烦,就略过了(而队友一直在调E,以至于到了最后也没做)。赛后看了看发现一个for循环就能解决。具体做法是从前往后遍历,让起点定位在第一个位置,开始下降后把终点定位。碰到再上升,就把之前的符合条件的与当前已有的答案比较,保留最优解。重复此过程直至遍历结束。详见代码。
AC代码:
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int N = 5e6 + 5;int a[N];int main(){    int n;    bool flag;    int ans1, ans2, tem1, tem2;    while(~scanf("%d",&n))    {        flag = true;        ans1 = ans2 = -1;        tem1 = 0;        tem2 = -1;        for(int i = 0; i < n; ++i)            scanf("%d",&a[i]);        for(int i = 1; i < n; ++i)        {            if(a[i-1] == a[i])            {                if(tem2 - tem1 > ans2 - ans1)//更新答案                {                    ans2 = tem2;                    ans1 = tem1;                }                tem1 = tem2 = -1;                continue;            }            if(a[i-1] < a[i]) //上升            {                if(!flag)                {                    if(tem2 - tem1 > ans2 - ans1)//更新答案                    {                        ans2 = tem2;                        ans1 = tem1;                    }                    flag = 1;                    tem1 = tem2 = -1;                }                if(tem1 == -1) tem1 = i-1;                //cout << i << "--" << tem1 << endl;            }            else            {                flag = 0;                if(tem1 != -1) tem2 = i;            }            if(tem2 - tem1 > ans2 - ans1)//更新答案            {                ans2 = tem2;                ans1 = tem1;            }        }        if(ans1 != -1 && ans2 != -1)            cout << ans1 << " " << ans2 << endl;        else            cout << "-1 -1" << endl;    }    return 0;}


Problem_G(并查集)

题意:在一块1000*1000的土地上施工,每周选择一块土地使这块土地由海洋变为陆地。问经过n周后,一共有多少块岛屿(上下左右相连接的属于一块),岛屿面积是多少以及岛屿的周长。
思路:施工n次,假设每次开辟的新岛屿周围都没有与之相连接的岛屿,所以岛屿个数+1,面积+1,周长+4。然后再判断它周围是否有可以与之相连的岛屿,上下左右四个方向逐个判断,再将相连接的个数相减就好了。具体减的方法很简单,此处不再赘述,如有疑问,欢迎私戳。
并查集:用来快速判断两者是否属于同一个集合的工具,因为所给的数据范围不是很大,所以可以用f[x*MAX+y]来表示点(x,y)所属的集合,这里转化完后就与普通的并查集没啥区别了。
AC代码:
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>#include <queue>using namespace std;const int MAX = 1000;int tot, area, peri, cou;int f[MAX*MAX+5];        //记录是否联通char mapa[MAX+5][MAX+5]; //存图int dir[][2] = {0,1,0,-1,1,0,-1,0};void init(){    for(int i = 0; i <= MAX*MAX; ++i)    {        f[i] = i;    }}int getf(int v){    if(f[v] != v)        f[v] = getf(f[v]);    return f[v];}int merg(int u, int v){    int t1 = getf(u);    int t2 = getf(v);    /*cout << u << "--" << v << endl;    cout << t1 << "----" << t2 << endl << endl;*/    if(t1 != t2)    {        f[t2] = t1;        return 1;  //与周围的岛屿之前不连通    }    return 0;}void solve(int x, int y){    int xx, yy;    for(int i = 0; i < 4; ++i)    {        xx = x + dir[i][0];        yy = y + dir[i][1];        if(xx < 0 || xx >= MAX || yy < 0 || yy > MAX) continue;        if(mapa[xx][yy] != '#') continue;        //cout << xx << "---" << yy << endl;        if(merg(x*MAX+y, xx*MAX+yy)) //联通成功,总岛屿数-1        {            tot--;        }        peri -= 2;                   //周围有一个岛屿的话,周长-2    }}int main(){    int t;    int x, y;    while(~scanf("%d",&t))    {        init();        tot = area = peri = 0;        memset(mapa, 0, sizeof(mapa));        while(t--)        {            scanf("%d%d",&x,&y);            if(mapa[x][y] == '#')            {                cout << tot << " " << area << " " << peri << endl;                continue;            }            //先假设新加入的岛屿与其他都不连通            tot++;            area++;            peri += 4;            mapa[x][y] = '#';            //开始做减法            solve(x, y);            cout << tot << " " << area << " " << peri << endl;        }    }    return 0;}


Problem_H(博弈+大数)

题意:理解后可以改为两个人取石子,规则是每次可以取1-4个,谁先取到最后一块谁就能赢,A先取。
思路:很显然,当n∈[1,4]时,A必赢;n=5时,A必输。因为两人都采取最优策略,所以当n∈[6,9]时,A可以取到使石子数目剩余为5,这样他就必赢,而当n=10时,B就必赢了……总结一下得出结论,所给的石子总数为5的倍数时,B必赢,否则A必赢。另外,数据有点大,考虑到了用JAVA。不过鉴于这个数据的特殊性,是5的倍数,用C++的话只要判断最后一位数是否是5或0就好了,想到了会更加轻松。(这里C++的就不贴了
AC代码(JAVA):
import java.math.BigInteger;import java.math.BigDecimal;import java.util.*;public class Main {public static void main(String[] args){Scanner scanner = new Scanner(System.in);int T;BigInteger n;BigInteger MOD = new BigInteger("5");T = scanner.nextInt();while((T--) > 0){n = scanner.nextBigInteger();if(n.mod(MOD).compareTo(BigInteger.ZERO) == 0)System.out.println("chaochao");else System.out.println("huahua");}}}


Problem_I(素数打表)

题意:给你一个偶数x,让你判断这个偶数能否用两个素数(a、b)之差表示出来(a-b=x),能的话根据b的大小输出最小的一组,不能的话输出FAIL
思路:任一大于2的偶数都可写成两个质数之和(哥德巴赫猜想),这道题自己想一下就会知道不会出现FAIL的情况。因为数据比较大,所以打两个表:第一个vis判断是否是素数,第二个a记录可能用到的素数。然后对于给定的x,依次将a[i]从第一个往后遍历,如果x+a[i]是素数那就输出他们,结束遍历。
(这里不得不单独吐槽一下出题人→_→,题面的数据范围给小了,比赛时改了数据。赛后改了题面,做了这两天,让我WA到怀疑人生。看别人过了,更忍不了了,下载测试数据对拍后才发现,出题人标程没改,那个A了的是错的。难受。。后来那个lazy的出题人把数据范围缩小了╮(╯_╰)╭rejudge后轻松AC)
AC代码:
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int MAX = 1500005;int a[MAX], tot;bool vis[MAX];void init()//筛法求a数组{    memset(vis, false, sizeof(vis));    //vis[0] = vis[1] = true;    tot = 0;    int M = sqrt(MAX+0.5);    for(int i = 2; i <= M; ++i)    {        if(!vis[i])        {            a[tot++] = i;            for(int j = i*i; j <= MAX; j += i)            {                vis[j] = true;            }        }    }}int main(){    init();    int t, n;    scanf("%d",&t);    while(t--)    {        scanf("%d",&n);        for(int i = 0; i < tot; ++i)        {            if(!vis[n+a[i]])            {                cout << n+a[i] << " " << a[i] << endl;                break;            }        }    }    return 0;}

Problem_J(贪心)

题意:有一块肉重为n,每切一次消耗n的体力,要求切成所给的t块重为a[i]的肉,问消耗的最小体力是多少。
思路:倒着想,根据所给的每块小肉,将它们合并为一块大肉,每次合并消耗所要合并的两块肉的重量之和。因为求最小的消耗体力,而最小的两块合并后不一定是最小的了,所以每次取完后都得重新排一次序。因之前后面的序列都已有序,直接用sort会造成大量的时间浪费,所以我可以用依次往后交换直到后面的数大于等于要交换的这个数为止。当然能用上优先队列的话就不用每次都重新排序了。
相似(?:石子合并简化版,每次取最大的合并,这样合并完后不用再排序,直接继续取就OK。
AC代码:
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int MAX = 20005;long long a[MAX];int main(){    int n;    long long ans;    while(~scanf("%d",&n))    {        ans = 0;        for(int i = 0; i < n; ++i)        {            scanf("%lld",&a[i]);        }        sort(a, a+n);        int tot = 1;        while(tot < n)        {            a[tot] = a[tot] + a[tot-1];            ans += a[tot];            for(int i = tot; i < n-1; ++i)            {                if(a[i] <= a[i+1]) break;                swap(a[i], a[i+1]);            }            tot++;            /*cout << "   tot=" << tot << endl;            for(int i = 0; i < n; ++i)                cout << a[i] << " ";            cout << endl;*/        }        //cout << endl;        cout << ans << endl;    }    return 0;}

AC代码(priority_queue):
#include <cstdio>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>#include <queue>#include <vector>using namespace std;struct cmp  //自定义优先级{    bool operator () (long long &a, long long &b) const    {        //最小值优先        return a > b;    }};int main(){    int n;    long long ans, tem;    priority_queue<long long, vector<long long>, cmp >pq;    //因为priority_queue中有已经定义好的越小的整数优先级越大,所以可以直接调用    //priority_queue<long long, vector<long long>, greater<long long> >pq;    while(~scanf("%d",&n))    {        ans = 0;        while(!pq.empty()) pq.pop();        while(n--)        {            scanf("%lld",&tem);            pq.push(tem);        }        while(!pq.empty())        {            tem = pq.top();            pq.pop();            if(pq.empty()) break;            //cout << "tem=" << tem << endl;            tem += pq.top();            pq.pop();            //cout << "tem2=" << tem << endl;            ans += tem;            pq.push(tem);        }        cout << ans << endl;    }    return 0;}


-----------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------

     选拔赛结束了,虽然以排名比较靠前的成绩进了正式队,但是心里很是不甘,特别是这一次,很多能做的题都没做出来,还有一开始I的数据范围看错+打表打错,浪费了太多时间也影响了整体士气(我的锅。

     还剩25天了,多学知识的同时也要修炼自己沉稳的性格,不管结果如何,不留遗憾就好。

2 0
原创粉丝点击