NOIP2014复赛提高组day1(A:生活大爆炸版石头剪刀布 B:联合权值 C:飞扬的小鸟)

来源:互联网 发布:c语言中float与double 编辑:程序博客网 时间:2024/05/22 07:49

A题:
正宗水题——RPS
随便搞搞就好了,
我也想不出来这题还能有什么思路。。。

#include<stdio.h>#define M 205int A[M],B[M]; int mark[15][15];int gcd(int a,int b){    if(b==0)return a;    return gcd(b,a%b);}int main(){    mark[0][0]=0;mark[0][1]=-1;mark[0][2]=1;mark[0][3]=1;mark[0][4]=-1;//纯模拟    mark[1][0]=1;mark[1][1]=0;mark[1][2]=-1;mark[1][3]=1;mark[1][4]=-1;    mark[2][0]=-1;mark[2][1]=1;mark[2][2]=0;mark[2][3]=-1;mark[2][4]=1;    mark[3][0]=-1;mark[3][1]=-1;mark[3][2]=1;mark[3][3]=0;mark[3][4]=1;    mark[4][0]=1;mark[4][1]=1;mark[4][2]=-1;mark[4][3]=-1;mark[4][4]=0;    int n,a,b;    scanf("%d %d %d",&n,&a,&b);    for(int i=1;i<=a;i++)scanf("%d",&A[i]);    for(int i=1;i<=b;i++)scanf("%d",&B[i]);    int m=a*b/gcd(a,b);    if(n<=m){        int ans1=0,ans2=0;        for(int i=1;i<=n;i++){            int x=i%a,y=i%b;            if(!x)x+=a;            if(!y)y+=b;            if(mark[A[x]][B[y]]==1)ans1++;            else if(mark[A[x]][B[y]]==-1)ans2++;        }printf("%d %d\n",ans1,ans2);    }else{        int tmp1=0,tmp2=0;        for(int i=1;i<=m;i++){            int x=i%a,y=i%b;            if(!x)x+=a;            if(!y)y+=b;            if(mark[A[x]][B[y]]==1)tmp1++;            else if(mark[A[x]][B[y]]==-1)tmp2++;        }int ans1=n/m*tmp1,ans2=n/m*tmp2;        n%=m;        for(int i=1;i<=n;i++){            int x=i%a,y=i%b;            if(!x)x+=a;            if(!y)y+=b;            if(mark[A[x]][B[y]]==1)ans1++;            else if(mark[A[x]][B[y]]==-1)ans2++;        }printf("%d %d\n",ans1,ans2);    }return 0;}

B题:
其实这题还不错
听对面的Kanosword大神和Songty大神说还有树形dp的写法,
然而我并搞不懂怎么dp。。。
在这里讲解一下我的思路
我们知道距离为二的两点要么是是这个数与他的祖父,要么由他的各个儿子组成
然而经过思考我们会发现,
这个数和他的祖父这个情况可以说没有任何意义
因为当我们递归到某一个数时,可以把他的父亲和他的儿子放在一起进行组合,
于是乎就变成了这样一个问题
对于树上的每一个点x
用他的父亲节点和子节点所构成的集合中的数两两联合,
而后求出这样配对所得的权值的最大值和总和【%10007】//PS:最大值无须mod
于是乎当我们直接暴力的时候
会发现复杂度最坏情况下是O(n2)的
若某一组数据这棵树只有两层,显然会TLE
但显然求最大值是不会TLE的
于是我们来思考怎么优化求总和
我们把之前说到的那个集合中的数表示为ai
我们用sumi来表示从点i出发的所有路劲的联合权值之和
为了表示方便我们把与点x距离为1的点的集合的大小表示为m,下标分别为1~m
于是乎
sumi
=ai(a1+a2++ai1++ai+1++am1++am)
=ai(mj=1ajai
=aimj=1aja2i
于是乎Sum=mi=1sumi=mi=1aimj=1ajmi=1a2i
所以这个值即为这个点集中的所有数和的平方减去这个点集中所有数的平方和
于是新鲜的代码就出炉了,
然而发现众大神们没有一个的写法和我一样0.0

#include<bits/stdc++.h>using namespace std;#define M 200005#define P 10007int A[M],B[M];void Rd(int &res){    char c;res=0;    while(c=getchar(),!isdigit(c));    do res=(res<<3)+(res<<1)+(c^48);    while(c=getchar(),isdigit(c));}vector<int>edge[M];int val[M];int ans1,ans2;void dfs(int x,int pre){    int tmp=0,sum=0,mx1=0,mx2=0;//mx1表示最大数,mx2表示最小数【与点x距离为1的点】    for(int i=0;i<edge[x].size();i++){        int y=edge[x][i];        if(val[y]>mx1){            mx2=mx1;            mx1=val[y];        }else if(val[y]>mx2){            mx2=val[y];        }tmp=(tmp+val[y])%P;//求和        sum=(1ll*sum+val[y]*val[y])%P;//求平方和        if(y==pre)continue;        dfs(y,x);    }ans1=max(ans1,mx1*mx2);    ans2=(1ll*ans2+tmp*tmp-sum)%P;//值为和的平方减去平方和}int main(){    int n;    scanf("%d",&n);    for(int i=1;i<n;i++){        int a,b;        Rd(a);Rd(b);        edge[a].push_back(b);        edge[b].push_back(a);    }for(int i=1;i<=n;i++)Rd(val[i]);    dfs(1,0);    printf("%d %d\n",ans1,(ans2+P)%P);    return 0;}

C题:
这题明显是dp题
但是博主并没有想到。。。
然而这无伤大雅
博主的最短路【Dijkstra】代码仍旧Water了70分,最后情况太多MLE了。。。
敲完Dijkstra的时候博主自信的认为不会MLE,这是个AC代码
【然而事实上是只考虑了时间复杂度,直接忘记了还有空间这种东东】
附上Dijkstra70分代码:

//PS:最短路70分代码。。。#include<bits/stdc++.h>using namespace std;#define M 10005#define N 1005int x[M],y[M],l[M],r[M];int n,m,k;bool mark[M][N];struct node{    int c,x,h,cnt;    bool operator <(const node &A)const{        return c>A.c;    }};priority_queue<node>Q;int ans;void Dijkstra(){//没事么好分析的,Dijkstra肯定会吧。。    node tmp;    tmp.c=0;    tmp.x=0;    tmp.cnt=0;    for(int i=1;i<=m;i++){        tmp.h=i;        Q.push(tmp);    }while(!Q.empty()){        tmp=Q.top();Q.pop();        if(mark[tmp.x][tmp.h])continue;        mark[tmp.x][tmp.h]=1;        if(tmp.cnt>ans)ans=tmp.cnt;//可以飞过的管道数         if(tmp.x==n){            printf("1\n%d\n",tmp.c);            exit(0);        }node nxt;        nxt.x=tmp.x+1;        nxt.h=tmp.h-y[tmp.x];        if(nxt.h>0){            if(!l[nxt.x]&&!r[nxt.x]){                nxt.c=tmp.c;nxt.cnt=tmp.cnt;                if(!mark[nxt.x][nxt.h])Q.push(nxt);            }else if(nxt.h>l[nxt.x]&&nxt.h<r[nxt.x]){                nxt.c=tmp.c;nxt.cnt=tmp.cnt+1;                if(!mark[nxt.x][nxt.h])Q.push(nxt);            }        }for(int i=1;;i++){            nxt.h=tmp.h+i*x[tmp.x];            if(nxt.h>m)nxt.h=m;            if(!l[nxt.x]&&!r[nxt.x]){                nxt.c=tmp.c+i;nxt.cnt=tmp.cnt;                if(!mark[nxt.x][nxt.h])Q.push(nxt);            }else if(nxt.h>l[nxt.x]&&nxt.h<r[nxt.x]){                nxt.c=tmp.c+i;nxt.cnt=tmp.cnt+1;                if(!mark[nxt.x][nxt.h])Q.push(nxt);            }if(nxt.h==m)break;        }    }}int main(){    scanf("%d %d %d",&n,&m,&k);    for(int i=0;i<n;i++)scanf("%d %d",&x[i],&y[i]);    for(int i=1;i<=k;i++){        int p,L,R;        scanf("%d %d %d",&p,&L,&R);        l[p]=L;r[p]=R;    }Dijkstra();    printf("0\n%d\n",ans);    return 0;}

而后这题Dijkstra是不可能敲到AC
所以还是老老实实的敲dp吧。。。
每一对idhigh的值都可以从id1highkxiid1,high+yi转移过来
于是很容易写出dp代码,复杂度为O(nm2)
会TLE。。。
然而我们再仔细观察一下会发现【这是个完全背包问题。。。所以采用完全背包的优化方法就行了】
其实如果我们保证在id这个位置小于high的高度上的dp值已经最优,
其实id,high的dp值只会从idhighxiid1highxiid1high+yi转移过来
但是要注意的在id,highxi这个位置小鸟是可以碰到管道的【否则75分】
(这只是一个dp转移的中介,最终当id位置上的dp值全部计算完毕的时候需要将碰到管道的情况清空)
时间复杂度为O(nm),空间复杂度为O(nm)
然而这一题某一横坐标id上的dp值显然只会从id1上转移过来
所以我们可以用滚动数组优化空间,将空间复杂度降为O(n+m)
于是代码出炉:

#include<bits/stdc++.h>using namespace std;#define M 10005#define N 1005#define oo 2000000000int x[M],y[M],l[M],r[M];//x[id]表示在横坐标为id的点上点击一次屏幕能升高的距离//y[id]表示在横坐标为id的点上不点击屏幕会下降的距离//l[id]表示在横坐标为id的点上管道的下表面//r[id]表示在横坐标为id的点上管道的上表面//PS:若该处没有管道,下表面为0,上表面为m+1int n,m,k;int dp[2][N];void Rd(int &res){    char c;res=0;    while(c=getchar(),!isdigit(c));    do res=(res<<3)+(res<<1)+(c^48);    while(c=getchar(),isdigit(c));}int main() {    scanf("%d %d %d",&n,&m,&k);    for(int i=0;i<n;++i)Rd(x[i]),Rd(y[i]);    for(int i=0;i<=n;++i)l[i]=0,r[i]=m+1;    for(int i=1;i<=k;++i){        int p,L,R;        Rd(p);Rd(L);Rd(R);        l[p]=L;        r[p]=R;    }for(int j=1;j<=m;++j)dp[1][j]=oo,dp[0][j]=0;    int cur=0,cnt=0;    for(int i=0;i<n;++i){        cur=!cur;        for(int j=1;j<=m;++j){//dp转移            if(j-y[i]>l[i+1]&&j-y[i]<r[i+1]&&dp[cur][j-y[i]]>dp[!cur][j])dp[cur][j-y[i]]=dp[!cur][j];            int H=j+x[i];            if(H>m)H=m;            int len=dp[!cur][j];            if(dp[cur][j]<len)len=dp[cur][j];            ++len;            if(dp[cur][H]>len)dp[cur][H]=len;        }bool f=0;        for(int j=1;j<=m;++j){            if(j<=l[i+1]||j>=r[i+1])dp[cur][j]=oo;            else if(dp[cur][j]!=oo&&r[i+1]!=m+1&&!f)f=1,cnt++;//飞过的管道数            dp[!cur][j]=oo;        }    }int ans=oo;    for(int i=1;i<=m;++i)ans=min(ans,dp[cur][i]);    if(ans!=oo)printf("1\n%d\n",ans);    else printf("0\n%d\n",cnt);    return 0;}

270。。。。。

3 0