ccpc 2016 合肥站 (5道题)

来源:互联网 发布:星空倒影 知乎 编辑:程序博客网 时间:2024/04/29 18:50

5961.传递 (思维题)

http://acm.hdu.edu.cn/showproblem.php?pid=5961

题目大意:

给你两个有向图,问你这两个图是否都是传递的。

一个有向图是传递的,当且仅当图中任意三点a,b,c,若存在边a->b,b->c则必存在边a->c.

题目分析:

bfs图,若存在一个点的深度>=3,则不是。

为什么呢?道理很简单,如果bfs序列中有c点的深度是3,设父亲是b,爷爷是a,则一定存在a->b,b->c,而不存在a->c,否则c的深度就是2了

#include <bits/stdc++.h>using namespace std;#define RE(x) freopen(x,"r",stdin)#define WR(x) freopen(x,"w",stdout)typedef long long ll;vector<int> g1[2222],g2[2222];bool vis1[2222],vis2[2222];int T,n;char c;bool bfs(int u,vector<int> g[]) {    bool* vis;    if(g==g1)        vis=vis1;    else        vis=vis2;    queue<int> q;    int d[2222];    q.push(u); vis[u]=true; d[u]=1;    while(!q.empty()) {        int v=q.front();        q.pop();        if(d[v]==3) return false;        for(int i=0;i<g[v].size();i++) {            int next=g[v][i];            if(!vis[next]) {                d[next]=d[v]+1;                vis[next]=true;                q.push(next);            }        }    }    return true;}int main() {    // RE("in.txt");    // WR("out.txt");    scanf("%d",&T);    while(T--) {        scanf("%d",&n);        for(int i=1;i<=n;i++)  {            g1[i].clear();            g2[i].clear();            vis1[i]=vis2[i]=false;        }        getchar();        for(int i=1;i<=n;i++) {            for(int j=1;j<=n;j++) {                scanf("%c",&c);                if(c=='P')                    g1[i].push_back(j);                else if(c=='Q')                    g2[i].push_back(j);            }            getchar();        }        bool flag1=true,flag2=true;        for(int i=1;i<=n;i++) {            if(!vis1[i])                flag1=bfs(i,g1);            if(!flag1) break;            if(!vis2[i])                flag2=bfs(i,g2);            if(!flag2) break;        }        if(!flag1 || !flag2)            cout<<"N"<<endl;        else            cout<<"T"<<endl;    }}

5963.朋友(博弈,思维题)

http://acm.hdu.edu.cn/showproblem.php?pid=5963

题目大意:

懒得抄了

题目分析:

观察游戏规则,我们发现了规律,如果根节点到一个孩子的边是1,那么不管后面的边是什么样的,把这条链变成全0需要的操作次数就是奇数次,否则是偶数次,是0反之。

直观上是这么证明的:(有点像数学归纳法,我也不知道是不是)
* 如果这条链上只有1,那么需要1次是奇数。
* 假设对序列 1[01]1 ,操作次数是奇数,那么再添加任意多个0或任意多个1,操作次数不变,若添加序列0+1,则变成全0的次数多了两次也是奇数。

对于从0开始的链亦同理。

那么得出结论:如果根节点下面的孩子节点的边中,1的个数是奇数,则需要奇数次操作变成全0,即女生胜,否则男生胜。

剩下的就是维护树边就可以了,首先以1为根,dfs/bfs确定深度和父节点,然后深度大的往深度小的父亲找,并更新权值直到相遇即可。

至于数据结构,这里用的是前向星,顺便学习了一下这种数据结构。(其实邻接表也同理,需要在深搜的时候记下每个点的父节点在邻接表中的序号,学习了不熟悉的数据结构,就懒得写了)

关于前向星,请阅读这篇文章:http://blog.csdn.net/acdreamers/article/details/16902023 ,我就不重复了。

我记得最大流算法里面的图也是用前向星做的,之前没意识到直接套模板了。。。现在好好学习一下。

关于树中边的信息维护还有一种更加高级的算法叫树链剖分,本人比较菜暂时还不懂这个。。。。
相关博文放在这,有机会看看:

树链剖分原理

树链剖分(一)(#请配合树链剖分(二)以及线段树一起食用-_-)

树链剖分(二)

树链剖分(三)(除了道馆之战——暂时可以告一段落了)

#include <bits/stdc++.h>using namespace std;typedef long long ll;int T,n,m;struct node{    int to,next,w;}edge[80005];int head[40005],cnt,op;int dep[40005],e[40005],father[40005];//dep表示第i个点的深度,father表示第i个点的父节点,e[i]表示终点为i的边的序号void addedge(int u,int v,int w) {    edge[cnt].to=v;    edge[cnt].w=w;    edge[cnt].next=head[u];    head[u]=cnt++;}void dfs(int u,int f,int d) { //u点,父节点是f,深度是d    dep[u]=d;    father[u]=f;    for(int i=head[u];i!=-1;i=edge[i].next) {        int v=edge[i].to;        if(v!=f) {            e[v]=i;            dfs(v,u,d+1);        }    }}int main(){    scanf("%d",&T);    while(T--) {        scanf("%d %d",&n,&m);        memset(head,-1,sizeof(head));        cnt=0;        for(int i=1;i<n;i++) {            int u,v,w;            scanf("%d %d %d",&u,&v,&w);            addedge(u,v,w);            addedge(v,u,w);        }        dfs(1,0,1);        for(int i=0;i<m;i++) {            scanf("%d",&op);            if(op) {                int x,y,z;                scanf("%d %d %d",&x,&y,&z);                while(x!=y) {                    if(dep[x]<dep[y])                        swap(x,y);                    int id=e[x];                    edge[id].w=edge[id^1].w=z;                    x=father[x];                }            }            else {                int x;                scanf("%d",&x);                int ans=0;                for(int i=head[x];i!=-1;i=edge[i].next) {                    ans+=edge[i].w;                }                if(ans&1)                    printf("Girls win!\n");                else                    printf("Boys win!\n");            }        }    }    return 0;}

5965.扫雷(枚举)

http://acm.hdu.edu.cn/showproblem.php?pid=5965

题目大意:

有一个3*n的扫雷棋盘,中间那行都已经给出了数,问有多少种埋雷方案?

题目分析:

枚举第一列有几个雷,则其他列有几个雷都是确定的,有0个雷或者2个雷方法数均为1,1个雷为2,连乘即可。

#include<bits/stdc++.h>using namespace std;typedef long long ll;const int maxn=11111;const int mod=1e8+7;char s[maxn];int num[maxn],dp[maxn];int main(){    int T;    scanf("%d",&T);    while(T--)    {        scanf("%s",s);        int n=strlen(s);        for(int i=0;i<n;i++)            num[i+1]=s[i]-'0';        memset(dp,0,sizeof(dp));       ll ans=0;       for(int i=0;i<=num[1];i++)       {           dp[1]=i;           if(i>2)break;           int j;           for(j=2;j<=n;j++)           {               int k=num[j-1]-dp[j-1]-dp[j-2];               if(k>2||k<0)                break;               dp[j]=k;           }           if(j<=n)continue;           if(dp[n-1]+dp[n]!=num[n])continue;           ll res=1;           for(int i=1;i<=n;i++)           {               if(dp[i]==0||dp[i]==2)                  res*=1;               else                  res*=2;               res%=mod;           }           ans+=res;           ans%=mod;       }       printf("%d\n",ans);    }    return 0;}

5968.异或密码(水)

http://acm.hdu.edu.cn/showproblem.php?pid=5968

题目大意:

给你一个序列,求出每个子序列异或的结果,找到所有的结果中与xi之差的绝对值最小的一个,并输出相应子序列的长度。如果有多个答案,则输出最长的。

题目分析:

因为只有100个用例,每个用例的长度只有100,所以暴力搞的复杂度也不过是 O(Tmn2)108,可以过去,想优化用二分查找。

#include <bits/stdc++.h>using namespace std;#define RE(x) freopen(x,"r",stdin)#define WR(x) freopen(x,"w",stdout)typedef long long ll;int T,n,m;int x[105][105];int a[105];void pre() {    x[0][0]=a[0];    for(int i=1;i<n;i++)        x[0][i]=x[0][i-1]^a[i];    for(int i=1;i<n;i++) {        for(int j=i;j<n;j++) {            x[i][j]=x[0][j]^x[0][i-1];        }    }}int main() {    scanf("%d",&T);    while (T--) {        scanf("%d",&n);        for(int i=0;i<n;i++) {            scanf("%d",&a[i]);        }        pre();        scanf("%d",&m);        while(m--) {            int q;            scanf("%d",&q);            int b=123456,l=-1;            for(int i=0;i<n;i++) {                for(int j=i;j<n;j++) {                    if(abs(x[i][j]-q)<b) {                        b=abs(x[i][j]-q);                        l=j-i+1;                    }                    else if(abs(x[i][j]-q)==b) {                        if(j-i+1>l)                            l=j-i+1;                    }                }            }            printf("%d\n", l);        }        printf("\n");    }}

5969.最大的位或(位运算)

http://acm.hdu.edu.cn/showproblem.php?pid=5969

题目大意:

给两个数l和r,从[l,r]中取两个数x,y,使得x|y最大,输出这个最大值。

题目分析:

其实这题挺符合leetcode 的画风,应该有可能出现在求职笔试面试中,引起重视!!

很明显r的二进制位大于等于l的,统计r有多少位,然后开始下手。

从高位往低位看,如果l和r对应位是相等的,则这一位是什么答案就加上什么,例:

1 0 1 0 ….

1 0 1 0 ….

很明显任意[l,r]之间的数的最高位都是这些。
那么如果遇到不同的位,例:

… 0 …

… 1 …

则x中可以取到..011111...,y中必可以取到...100000..,因为这两个数一定存在~~

所以只要遇到l和r出现不同数位,则答案加上...11111..即可。

#include <bits/stdc++.h>using namespace std;#define RE(x) freopen(x,"r",stdin)#define WR(x) freopen(x,"w",stdout)typedef long long ll;ll l,r;int T;int get(ll r) {    return (r<2)?1:get(r/2)+1;}int main(){    scanf("%d",&T);    while(T--)  {        scanf("%I64d %I64d",&l,&r);        ll ans=0;        int i=get(r)-1;        while(i>=0) {            if((r&(1LL<<i))==(l&(1LL<<i)))                ans+=r&(1LL<<i);            else                break;            i--;        }        ans+=(1LL<<(1+i))-1;        cout<<ans<<endl;    }}

感觉我在赛场上最多只能做出3题。。。据说4题才有牌,但我感觉公司笔试题难度不输给这些题的前半部分~,是不是我也要失业了。。。【柯南思考状~】

0 0
原创粉丝点击