ccpc 2016 合肥站 (5道题)

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题才有牌,但我感觉公司笔试题难度不输给这些题的前半部分~,是不是我也要失业了。。。【柯南思考状~】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值