比赛地址:http://codeforces.com/contest/812

A. Sagheer and Crossroads

题目的意思是给出4个路口的车辆和人的红路灯状态,判断是否可能车撞人。

关键是题意,然后模拟,注意细节。

#include <bits/stdc++.h>
using namespace std;
int l[5], s[5], r[5], p[5];

int main()
{
    for(int i=1; i<=4; i++){
        scanf("%d %d %d %d", &l[i],&s[i],&r[i],&p[i]);
    }
    if(p[1]==1){
        if(l[1] || s[1] || r[1] || l[2] || s[3] || r[4]){
            puts("YES");
            return 0;
        }
    }
    if(p[2]==1){
        if(l[2] || s[2] || r[2] || r[1] || l[3] || s[4]){
            puts("YES");
            return 0;
        }
    }
    if(p[3]==1){
        if(l[3] || s[3] || r[3] || l[4] || s[1] || r[2]){
            puts("YES");
            return 0;
        }
    }
    if(p[4]==1){
        if(l[4] || s[4] || r[4] || l[1] || r[3] || s[2]){
            puts("YES");
            return 0;
        }
    }
    puts("NO");
    return 0;
}

B. Sagheer, the Hausmeister

题意:n<15,m<=100的矩阵 1为亮,0为灭 每层第一个和最后一个位置为楼梯,每层必须灭完才能到上一层,问全部灯灭完 最小时间?
解法:到上一层只能从左端或者右端走 设dp[i][0/1] : 灭了前i层的灯 &&当前在第i层左/右端时的最小时间。
dp[i][0]=min(dp[i+1][0]+1+(r[i]1)2,dp[i+1][1]+1+m1)
idp[i][0]=dp[i+1][0]:,...

#include <bits/stdc++.h>
using namespace std;
int n, m, ans=0, l[300], r[300], dp[300][2];
char s[300];

int main()
{
 scanf("%d%d",&n,&m);
 m+=2;
 int maxx=n+1;
 for(int i=1; i<=n; i++){
 l[i]=1e5,r[i]=0;
 scanf("%s",s+1);
 for(int j=1; j<=m; j++){
 if(s[j]=='1'){
 l[i]=min(l[i],j);
 r[i]=max(r[i],j);
 maxx=min(i,maxx);
 }
 }
 if(l[i]==1e5){
 l[i]=1,r[i]=m;
 }
 }
 for(int i=n; i>=maxx; i--){
 if(r[i]==m){
 if(i==n) dp[i][0]=0,dp[i][1]=m-1;
 else{
 dp[i][0]=dp[i+1][0]+1;
 dp[i][1]=dp[i+1][1]+1;
 }
 continue;
 }
 if(i==maxx){
 ans=min(dp[i+1][0]+1+r[i]-1,dp[i+1][1]+1+(m-l[i]));
 break;
 }
 if(i==n){
 dp[i][0]=2*(r[i]-1);
 dp[i][1]=m-1;
 continue;
 }
 dp[i][0]=min(dp[i+1][0]+1+2*(r[i]-1),dp[i+1][1]+m-1+1);
 dp[i][1]=min(dp[i+1][1]+(m-l[i])*2+1,dp[i+1][0]+m-1+1);
 }
 if(maxx==n) ans=r[n]-1;
 cout<<ans<<endl;
}

C. Sagheer and Nubian Market

排序+二分水题。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n;
LL S,a[100010], b[100010];
LL check(int mid){
    for(int i=1; i<=n; i++) b[i]=a[i]+1LL*i*mid;
    sort(b+1,b+n+1);
    LL res=0;
    for(int i=1; i<=mid; i++){
        res+=b[i];
    }
    return res;
}
int main(){
    cin>>n>>S;
    for(int i=1; i<=n; i++){
        scanf("%lld",&a[i]);
    }
    int ans=1;
    int l=0,r=n;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)<=S) ans=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d %lld\n", ans,check(ans));
}

D. Sagheer and Kindergarten

题意: 一共n个人,m个玩具,然后读入是x和y,表示x要玩y。然后在玩y的人玩的时间不知道(当他得到了所有想要的玩具之后就开始玩,玩完之后将玩具还回),如果x要玩y……..这样形成了一个环,这个环上所有人都会哭。比如1要玩2在玩的玩具,2要玩3的,3要玩1的,就形成了个环,就全都哭了。然后前面k个操作,保证没人哭,后面q个操作,问如果有这个操作,有多少个人哭了

解法:每个人最多只会等1个人玩某个玩具,从某个人出发连一条指向等的人的有向边。最终形成了一个有许多有根树的森林。每个结点最多1个出度,用欧拉路的方式给每个结点的出入标id,就方便判断某两点是否之前在一棵树上,以及在树的路径中经过的结点个数。这样每次询问,如果最后一个玩y的人在以x为根节点的子树上,连接这两个人就形成了一个环,输出这棵子树上结点个数即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n, m, k, q, dfsclk, fa[maxn], pre[maxn], in[maxn], out[maxn];
vector <int> G[maxn];
void dfs(int u){
    in[u] = ++dfsclk;
    for(int i=0; i<G[u].size(); i++){
        dfs(G[u][i]);
    }
    out[u] = dfsclk;
}
int main()
{
    while(~scanf("%d %d %d %d" , &n,&m,&k,&q))
    {
        for(int i=0; i<maxn; i++) G[i].clear();
        memset(fa, 0, sizeof(fa));
        memset(pre, 0, sizeof(pre));
        memset(in, 0, sizeof(in));
        memset(out, 0, sizeof(out));
        dfsclk = 0;
        for(int i=1; i<=k; i++){
            int x,y;
            scanf("%d%d",&x,&y);
            if(pre[y]) fa[x] = pre[y];//如果有人在x前一个玩y,x得等到前一个玩y的结束
            pre[y] = x;
        }
        for(int i=1; i<=n; i++) G[fa[i]].push_back(i);
        for(int i=1; i<=n; i++){
            if(!in[i]) dfs(i);
        }
        while(q--){
            int x, y;
            scanf("%d %d", &x, &y);
            y = pre[y]; //最后一个玩y的人
            if(in[x]<=in[y]&&out[x]>=in[y]){
                printf("%d\n", out[x]-in[x]+1);
            }
            else{
                printf("0\n");
            }
        }
    }
    return 0;
}

E. Sagheer and Apple Tree

题意:有一颗很奇怪的苹果树,这个苹果树所有叶子节点的深度要不全是奇数,要不全是偶数,并且包括根在内的所有节点上都有若干个苹果,现有两个极其聪明的人又来无聊的游戏了,每个人可以吃掉某个叶子节点上的部分苹果(不能不吃),或者将某个非叶子结点上的部分苹果移向它的孩子(当然也不能不移),吃掉树上最后一个苹果的人获胜,问先手是否必胜?不,不是问这个,因为后手可以作弊,他可以交换任意两个节点的苹果数量,问有多少种不同的作弊(交换)方式可以使得后手必胜(不能不交换)?

解法:书上的Nim游戏。

中文版是这位同学写的,尊重版权从我做起。点这里

不如考虑什么时候先手必胜,在尼姆博弈中,将所有的苹果个数依次异或,如果最后答案为0就先手必败,否则必胜,那么树上的呢?其实一样,不妨把节点按照深度分成两种:①想要移动到任意叶子结点都需要偶数步数(深度和最深的叶子结点深度奇偶性相同);②想要移动到任意叶子结点都需要奇数步数

对于①,玩家只要移动x个苹果到它孩子节点上,那么这x个苹果就一定会变成状态②之下
对于②,玩家只要移动x个苹果到它孩子节点上,那么这x个苹果就一定会变成状态①之下

而孩子结点属于状态①,所以当前玩家只要移动状态②下的x个苹果,那么另一个玩家只要照搬你的移动,将这x个苹果再次移动到当前的孩子,就又变成状态②了,若当前已经到叶子节点无法移动了,另一个玩家就可以直接将这x个苹果吃掉(你就GG,这几个苹果就和没有一样!)

这样就很明显了,先手对于状态②下的苹果,无论怎么移动,最后一定都会被聪明的后手玩家用上述方法吃掉,所以状态②的苹果毫无意义,那么状态①的呢,只要移动一部就会变得状态②从而毫无意义(和没有一样),那这不就转化成了裸的尼姆博弈了么?

结论:只要将所有状态①下的苹果数量异或,最后结果ans若为0则先手必败,否则必胜!

那么怎么解决这道题,其实已经好办了,先判断一波先手必胜还必败。

如果已经必败了,那么后手作弊交换节点时就要尽量避免改变战局,要知道a^b==b^a,异或是满***换律的,所以B可以将所以状态①中任意两个节点互换,或者将状态②中任意两个节点互换,或者将分别属于状态①和状态②中但数量相同的两堆互换,三种情况个数加在一起即是答案。

如果先手必胜,那么后手的交换一定要改变战局,这个时候必须将状态①中的某个节点和状态②中的某个节点互换,那么怎么换呢?亦或性质a^b^b==a,假设当前异或结果是ans,那么只要遍历一遍状态①中的所有节点,对于当前节点的苹果个数temp,在状态②中找到苹果个数为ans^temp的节点,交换即可!最后统计一遍个数便是答案

这题关键是思维,代码就简单了。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
typedef long long LL;
struct edge{
    int to, next;
}E[maxn*2];
int head[maxn], edgecnt;
void init(){
    edgecnt = 0;
    memset(head,-1, sizeof(head));
}
void add(int u, int v){
    E[edgecnt].to = v, E[edgecnt].next = head[u], head[u] = edgecnt++;
}
map <LL, LL> mp1,mp2;
int dep[maxn];
LL a[maxn], mxdep;
vector <LL> v;
void dfs(int u, int fa, int d){
    dep[u] = d;
    mxdep = max(mxdep, 1LL*d);
    for(int i=head[u]; ~i; i=E[i].next){
        int v = E[i].to;
        if(v!=fa){
            dfs(v,u,d+1);
        }
    }
}
int main()
{
    int n;
    while(~scanf("%d", &n)){
        v.clear();
        mp1.clear();
        mp2.clear();
        for(int i=1; i<=n; i++) scanf("%lld", &a[i]), v.push_back(a[i]);
        sort(v.begin(), v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        init();
        for(int i=2; i<=n; i++){
            int x;
            scanf("%d", &x);
            add(x, i);
            add(i, x);
        }
        mxdep = -1;
        dfs(1,-1,1);
        LL ans = 0, cnt1 = 0, cnt2 = 0;
        for(int i=1; i<=n; i++){
            if(mxdep%2==dep[i]%2){
                mp1[a[i]]++;
                ans ^= a[i];
                cnt1++;
            }
            else{
                mp2[a[i]]++;
                cnt2++;
            }
        }
        if(ans == 0){
            ans = cnt1*(cnt1-1)/2+cnt2*(cnt2-1)/2;
            for(int i=0; i<v.size(); i++){
                ans += mp1[v[i]]*mp2[v[i]];
            }
            printf("%lld\n", ans);
        }
        else{
            LL temp = ans;
            ans = 0;
            for(int i=1; i<=n; i++){
                if(mxdep%2==dep[i]%2){
                    ans += mp2[temp^a[i]];
                }
            }
            printf("%lld\n", ans);
        }
    }
    return 0;
}

总结:这次比赛的思维能力要求的很高,但是代码写起来很短。所以思维是首先要考虑的。