9th NENU ACM-ICPC Contest Solving Report

来源:互联网 发布:简单的数据库系统 编辑:程序博客网 时间:2024/05/22 09:07

第九届东北师范大学ACM-ICPC校赛于2015年4月12日圆满落幕,五个小时赛程共十道题,现公布题解如下:


--------------------------------------------------------------------------------------

绪→感谢出题组、验题组、翻译组的倾力奉献,人员如下:

    Ok_again                                       Alwayswet

    Cs1131                                         Vencentuse

    Light_starlight                                Dream410

    Yinzm                                           Junkie

    Todayac                                        Cuijy

    Fengcb

--------------------------------------------------------------------------------------


A题:

Author:Light_starlight

Interpreter:Light_starlight

设定为签到题,故事背景为"曹冲称象",给出N个石头的重量,求大象的重量。

N个数求和,目标所有选手可AC

#include<iostream>#include<cstdio>#include<cmath>#include<cstring>using namespace std;int main(){    int n;    while(~scanf("%d",&n)){        int x;        int sum = 0;        while(n--){            scanf("%d",&x);            sum += x;        }        printf("%d\n",sum);    }    return 0;}

B题:

Author:Cuijy

Interpreter:Light_starlight

输出pascal triangle的第N行,N∈(0,100000].answer mod 1e9+7

众人皆知,杨辉三角的通式为:第n行第m个数位C(n-1,m-1)。

那么本题就变成了求C(n,m)显然:

C(n, m) mod p = n!/(m!(n - m)!) mod p

由于N范围达10^5,朴素的递推求解时间代价太高

考虑1e9+7为素数,根据费马小定理: 若p是质数,且gcd(a,p)=1,则a^(p-1)≡ 1 (mod p),即a*ap-2 ≡ 1 (mod p)

就是说,(m!(n-m)!)的逆元为 (m!(n-m)!)p-2

C(n,r)=(n!)*(r!)^(P-2)*(n-r)!^(P-2)%P

接下来的处理就简单明了了。

#include<math.h>#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;typedef long long LL;const LL mod=1e9+7;const int maxn=1111111;LL Inv(LL x, LL mod) {    LL r, y;    for(r = 1, y = mod - 2; y; x = x * x % mod, y >>= 1)        (y & 1) && (r = r * x % mod);    return r;}LL A[maxn],C[maxn];void init(){    A[0]=1,A[1]=1;C[0]=Inv(A[0],mod);C[1]=Inv(A[1],mod);    for(int i=2;i<maxn;i++){        A[i]=A[i-1]*i%mod;        C[i]=Inv(A[i],mod);    }}int main() {    int n;    init();//    freopen("cjy.in","r",stdin);//    freopen("cjy.out","w",stdout);    while(scanf("%d",&n)!=EOF) {        printf("1");        for(int i=1;i<=n;i++){            printf(" %lld",A[n]*C[n-i]%mod*C[i]%mod);        }        puts("");    }}

C题:

Auther:Fengcb

Interpreter:Light_starlight

一个数字最多表示成4个数字的平方和的种数。

DP预处理,定义dp[i][j]表示到数字i时,当前有j个数的平方和。

状态转移方程为:dp[i][j] = ∑{dp[i-k*k][j-1]},时间复杂度为O(4Nsqrt(N)).

三层sqrt暴力+break优化也是可以过的,时间复杂度为O(Nsqrt(N)).

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#define N 40000using namespace std;int dp[N][5];int main() {    int n,i,j,k;    memset( dp,0,sizeof(dp) );    dp[0][0]=1;    for( i=1; i*i<N; i++ )        for( j=1; j<5; j++ )            for( k=i*i; k<N; k++ )                dp[k][j]+=dp[k-i*i][j-1];    int t;    cin>>t;    while(t--) {        cin>>n;        printf( "%d\n",dp[n][1]+dp[n][2]+dp[n][3]+dp[n][4] );    }    return 0;}

D题:

Author:Todayac

Interpreter:Light_starlight

有N个整数构成一个数列A,给出一个整数S。在这个数列中是否存在两个数的和等于S.

直接用Map或者Set就可以过了,当然现场大部分人用的是二分查找。

不管什么姿势,综合复杂度均为O(NlogN)

以下给出一种优雅的写法:

#include<algorithm>#include<iostream>#include<cstring>#include<cstdlib>#include<cstdio>#include<cmath>using namespace std;#ifdef __int64typedef __int64 LL;#elsetypedef long long LL;#endif#define M 100005int a[M];int main() {    int T, N, S;    while(~scanf("%d", &N)) {        for(int i = 0; i < N; i ++) {            scanf("%d", &a[i]);        }        scanf("%d", &S);        sort(a, a+N);        int flg = 0;        for(int i = 0, j = N-1 ; i < j; ) {            if(a[i] + a[j] == S) {                flg = 1;                break;            } else if(a[i] + a[j] < S) i++;            else j--;        }        //printf("%d: ",T);        if(flg == 0) printf("No\n");        else printf("Yes\n");    }    return 0;}

E题:

Author:Alwayswet

Interpreter:Light_starlight

N个点,求出每对点之间的距离的平方和。

N∈[1,100000],坐标X,Y∈[-10000,10000].

非常有意思的推导题,乍一看无从下手,拿三四个点算一算,发现很容易O(N)解决

推导结果不多说,看代码就悟了~

#include<stdio.h>#include<math.h>#include<algorithm>using namespace std;typedef __int64 ll;struct point{    ll x,y,sum,sum2;    void input(){        scanf("%I64d%I64d",&x,&y);        sum=x+y;        sum2=x*x+y*y;    }}p[100100];int main(){    int n;    while(scanf("%d",&n)!=EOF){        ll _sumx=0,_sumy=0,_sum2=0;        for(int i=0;i<n;i++){            p[i].input();            _sumx+=p[i].x;            _sumy+=p[i].y;            _sum2+=p[i].sum2;        }        ll ans=0;        for(int i=0;i<n;i++){            ans+=_sum2+p[i].sum2*n;            ans-=(_sumx)*2*p[i].x;            ans-=(_sumy)*2*p[i].y;        }        printf("%I64d\n",ans/2);    }}

F题:

Author:Alwayswet

Interpreter:Yinzm

加农炮前有M堵墙,以恒定速度打出N发炮弹,倾斜角度0°到45°之间,求每个炮弹的落点。

炮弹打到地上就在地上,打到墙上就黏在墙上,N,M∈[1,10000],g=9.8.

暴力O(N*M)是不科学的

由于速度恒定,那么炮弹在0°到45°之间打出的距离是存在单调性的

将炮弹按照角度排序,将墙按距离原点递增排序

然后离线操作,单调队列O(N+M)不断将炮弹与墙不断往后算即可

最后再按照原来的顺序输出落点

#include<math.h>#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;const double eps=1e-8;const double pi=acos(-1.0);const double g=9.8;int sgn(double x){    if(x<-eps) return -1;    if(x>eps) return 1;    return 0;}struct point{    double x,y;    point(double _x=0,double _y=0){        x=_x,y=_y;    }    bool friend operator<(const point &a,const point &b){        return a.x<b.x;    }    void input(){        scanf("%lf%lf",&x,&y);    }}p[120000],ans[120000];struct cannon{    double angle;    int id;    bool friend operator<(const cannon &a,const cannon &b){        return a.angle<b.angle;    }    void input(int _id){        scanf("%lf",&angle);        id=_id;    }}q[120000];int n,m;double v;int main(){//    freopen("E.in","r",stdin);//    freopen("E.out","w",stdout);    while(scanf("%d%lf",&n,&v)!=EOF){        for(int i=0;i<n;i++) q[i].input(i);        scanf("%d",&m);        for(int i=0;i<m;i++) p[i].input();        sort(p,p+m);sort(q,q+n);        int j=0;        for(int i=0;i<n;i++){            double k=tan(q[i].angle);            double b=(1+k*k)*g/2/v/v;            bool f=0;            while(j<m){                double ty=k*p[j].x-b*p[j].x*p[j].x;                if(sgn(ty)<=0){                    int id=q[i].id;                    ans[id].y=0;                    ans[id].x=k/b;                    f=1;                    break;                }                if(sgn(p[j].y-ty)>=0){                    int id=q[i].id;                    ans[id].y=ty;                    ans[id].x=p[j].x;                    f=1;                    break;                }                j++;            }            if(!f){                int id=q[i].id;                ans[id].y=0;                ans[id].x=k/b;            }        }        for(int i=0;i<n;i++){            printf("%.6f %.6f\n",ans[i].x,ans[i].y);        }    }}

G题:

Author:Yinzm

Interpreter:Junkie

        这个题目是一道特别好的思维题,考点是高斯消元,建议大家先去切几道基本的高斯消元题目,再来研究这个题目。这个题目必须得真正理解高斯消元的思想才能快速的切掉。因为数据量比较大,所以用其他方法不太适合。如果只挑选两个数字的话,就可以考虑用字典树(如去年的校赛中的某题),如果数据量比较小(<=30)的,还能用状态压缩+字典树解决,强烈推荐大家去亲手切两道这种题目。在比赛中异或运算应该算出现频率比较高的了,基于异或运算的性质,所以我们一般都要往二进制上面想。

       因为要在N个数中挑选若干个数字一起异或,所以可以先考虑将所有的数字都用二进制表示,根据题目中的数据范围,将所有的二进制数都表示为32位,如果不足32位的高位补零,得到N个长度为32位的二进制数。现在需要考虑的是怎么将最后的结果最大,最后的结果如果也用二进制表示的话,我们的期望当然是高位的1越多越好。高斯消元的思想现在就能用上了,建立方程组,然后判断最后结果高位能不能取1。

       1、建立方程组矩阵。我们可以将这N个数从高位到低位建立方程,假设低i个数的二进制表示为[ a(i,1) a(i,2) ... a(i,32) ],由于期望最后结果的每一个位都能取1,所以将最后结果的那一列全部置为1。方程的n个变量x1...xn代表该位取或者不取,即xi=0或xi=1

a(1,1)*x1 ^ a(2,1)*x2 ^ a(3,1)*x3  ^...^ a(n,1)*xn = 1
a(1,2)*x1 ^ a(2,2)*x2 ^ a(3,2)*x3  ^...^ a(n,2)*xn = 1
  .......
a(1,32)*x1 ^ a(2,32)*x2 ^ a(3,32)*x3 ^...^ a(n,32)*xn = 1

        2、从第一个方程开始判断能够成立,能够成立就代表最后结果中的该位能够取1,否则取0。判断方法,从方程左边开始查找,若a(i,j)=1而且方程右端为1,那么这个方程肯定可以成立(只需要让xi变量取1,其他变量都取0便可),然后利用该方程向下消元(因为该变量的值已经确定了,便消去它)。若没有a(i,j)=1,但是此时方程右端为0,那么此时这个方程也能成立(所有的变量都取0便可)。最后将结果的二进制表示转化为十进制,就可以输出了。

#include<cstdio>#define LL long longusing namespace std;int n;LL x;int a[110][110];void Guass(){    LL ans=0;    for(int i=0;i<=33;i++){        int id=-1;        for(int j=0;j<n;j++){            if(a[i][j]){                id=j;                break;            }        }        if(id==-1 && a[i][n]==0){            ans|=(1ll<<(33-i));        }else if(id!=-1){            ans|=(1ll<<(33-i));            for(int j=i+1;j<=33;j++){                if(a[j][id]){                    for(int k=0;k<=n;k++){                        a[j][k]^=a[i][k];                    }                }            }        }    }    printf("%lld\n",ans);}int main(){//    freopen("input.txt","r",stdin);//    freopen("output.txt","w",stdout);    while(~scanf("%d",&n)){        for(int i=0;i<n;i++){            scanf("%lld",&x);            for(int j=33;j>=0;j--){                a[(33-j)][i]=(x>>j)&1;            }        }        for(int i=0;i<=33;i++)a[i][n]=1;        Guass();    }    return 0;}

H题:

Author:ok_again

Interpreter:Junkie

十分有趣的小学追击类问题,三个条件列出一个方程组,然后不断消去未知数,得到:

L = 2*v*T^2/t

一行printf结束,就是这么easy!

#include<algorithm>#include<cstdio>int main() {//    freopen("in.in", "r", stdin);//    freopen("out.out", "w", stdout);    double T, V, t;    while(scanf("%lf%lf%lf", &T, &V, &t) != EOF) {        printf("%.5f\n", 2 * T * T * V / t);    }    return 0;}


I题:

Author:ok_again

Interpreter:Junkie

模拟题,题目怎么说,代码就怎么写

//#pragma comment(linker, "/STACK:1024000000,1024000000")#include<algorithm>#include<iostream>#include<cstring>#include<cstdio>#include<vector>#include<string>#include<queue>#include<cmath>#include<stack>#include<set>#include<map>#define FIR first#define SEC second#define MP make_pair#define INF 0x3f3f3f3f#define LL long long#define CLR(a, b) memset(a, b, sizeof(a))using namespace std;const int maxn = 3333;int L[maxn], R[maxn], bank[maxn], head[7], cnt[7];void init() {    for(int i = 1; i <= 5; i ++) {        head[i] = 3000 + i;        bank[i + 3000] = i;        cnt[i] = 0;    }    for(int i = 0; i < maxn; i ++) {        L[i] = R[i] = i;    }}void ins(int x, int y) {    L[y] = x;    R[y] = R[x];    L[R[x]] = y;    R[x] = y;    bank[y] = bank[x];    cnt[bank[x]] ++;}void del(int x) {    cnt[bank[x]] --;    R[L[x]] = R[x];    L[R[x]] = L[x];}int getnum(int x) {    int ret = 0, Lx = L[x];    while(Lx != head[bank[x]]) {//        printf("%d == %d\n", Lx, head[bank[x]]);        Lx = L[Lx];        ret ++;    }    return ret;}int main() {//    freopen("in2.txt", "r", stdin);//    freopen("out2.txt", "w", stdout);    int m;    while(scanf("%d", &m) != EOF) {        init();        while(m --) {            char op[10];            int x, b;            scanf("%s", op);//            printf("%s == ", op);            if(op[0] == 'O') {                if(op[2] == '1') {                    scanf("%d", &x);                    int hd = -1, nm = maxn;                    for(int i = 1; i <= 5; i ++) {                        if(nm > cnt[i]) {                            nm = cnt[i];                            hd = i;                        }                    }                    ins(L[head[hd]], x);                    printf("%d\n", cnt[bank[x]] - 1);                } else if(op[2] == '2') {                    scanf("%d%d", &x, &b);                    if(bank[x] == b) {                        puts("Something Wrong!!!");                    } else {                        del(x);                        ins(L[head[b]], x);                        printf("%d\n", cnt[bank[x]] - 1);                    }                } else if(op[2] == '3') {                    scanf("%d", &b);                    if(cnt[b] == 0) {                        puts("Something Wrong!!!");                    } else {                        del(R[head[b]]);                        printf("%d\n", cnt[b]);                    }                } else {                    scanf("%d", &x);                    del(x);                    printf("%d\n", cnt[bank[x]]);//                    printf("%d\n", getnum(x));                }            } else {                if(op[1] == '1') {                    scanf("%d", &b);                    printf("%d\n", cnt[b]);                } else {                    scanf("%d", &x);                    printf("%d\n", getnum(x));                }            }        }    }}

J题:

Auther:dream410

Interpreter:Light_starlight

思路非常好的博弈论题。

运用群论中置换的思想,可用LCM(最小公倍数)求出置换回最初状态需要的置换步数,然后题目就转换为了巴什博弈。

#include<cstdio>#include<cstring>#include<algorithm>#define maxn 1005using namespace std;int p[maxn];bool used[maxn];int main() {    int n, m;//    freopen("in.txt","r",stdin);//    freopen("out.txt","w",stdout);    while(~scanf("%d %d",&n,&m)) {        for(int i=1; i<=n; i++) {            scanf("%d",&p[i]);        }        memset(used,false,sizeof(used));        int ans=1;        for(int i = 1; i <= n; i ++) {            int tmp = 0;            while(!used[i]) {                used[i] = true;                tmp ++;                int q = p[i];                while(!used[q]) {                    used[q] = true;                    tmp ++;                    q = p[q];                }                ans = ans / __gcd(ans, tmp) * tmp;            }        }//        printf("%d\n",ans);        if((ans - 1) % (m + 1) ==0)printf("Bob\n");        else printf("Alice\n");    }}/*5 34 1 5 2 3*/


本次校赛题目质量还是比较高的,没有出模板题,希望各位可以多学习学习~

0 0
原创粉丝点击