Codeforces Round #417 (Div. 2)

A

题目链接:

http://codeforces.com/contest/812/problem/A

题意:

给出十字路口四块部分每块4个方向(左转、直行、右转、人行道)共16个方向的信号灯,问是否会出现汽车可能撞到人的情况

题解:

直接把所有人行道判断一遍即可

#include<bits/stdc++.h>
using namespace std;
#define MAX_N 1005
#define inf 0x7fffffff
#define LL long long
const int mod = 1e9+7;
const LL INF = 9e18;
typedef pair<int, int >P;
const double eps = 1e-6;

int main()
{
    int a[4][4];
    for(int i=0; i<4; i++)
        for(int j=0; j<4; j++)
            cin >> a[i][j];
    bool f = false;
    if(a[0][3] && (a[0][0]||a[0][1]||a[0][2]||a[1][0]||a[2][1]||a[3][2]))
        f = true;
    if(a[1][3] && (a[1][0]||a[1][1]||a[1][2]||a[2][0]||a[3][1]||a[0][2]))
        f = true;
    if(a[2][3] && (a[2][0]||a[2][1]||a[2][2]||a[3][0]||a[0][1]||a[1][2]))
        f = true;
    if(a[3][3] && (a[3][0]||a[3][1]||a[3][2]||a[0][0]||a[1][1]||a[2][2]))
        f = true;
    if(f)
        puts("YES");
    else
        puts("NO");
}


B

题目链接:

http://codeforces.com/contest/812/problem/B

题意:

给出一栋高为n,每层都有m个房间,并且在最左边以及最右边都有一个楼梯的建筑。当房间为0时表示关灯了,1表示没关灯,楼梯恒为0,求从底层关闭建筑所有灯的最小花费时间。从一个房间移动到临近的一个房间需要花费1个时间单位,上楼也需要1个时间单位。

题解:

由于1<=n<=15, 1<=m<=100所以我们可以枚举关闭每层楼后是在左边还是在右边的所有状态再对每个状态进行模拟求所有状态的最小即可,O(n*2^n)

#include<bits/stdc++.h>
using namespace std;
#define MAX_N 1005
#define inf 0x7fffffff
#define LL long long
const int mod = 1e9+7;
const LL INF = 9e18;
typedef pair<int, int >P;
const double eps = 1e-6;

int L[20];
int R[20];
int main()
{
    int n, m;
    cin >> n >> m;
    char s[20][105];
    for(int i=n-1; i>=0; i--) {
        scanf("%s", s[i]);
    }
    for(int i=n-1; i>=0; i--) {//预处理出每层楼的最左和最右边没关灯的房间位置
        L[i] = inf;
        R[i] = -1;
        for(int j=0; j<m+2; j++) {
            if(s[i][j]=='1')
                L[i] = min(L[i], j), R[i] = max(R[i], j);
        }
    }
    int mm = 0;
    for(int i=0; i<n; i++) {//求出最高没关灯的楼层
        if(R[i]!=-1) {
            mm = i;
        }
    }
    n = mm+1;//可将建筑高度缩小为最高没关灯楼层的高度
    int ans = inf;
    for(int i=0; i<(1<<(n-1)); i++) {//模拟
        int sum = 0;
        int tmp = i;
        int sta = 0;//前一层楼的状态
        for(int j=0; j<n-1; j++) {
            if(tmp&1) {
                if(sta) {
                    if(L[j]!=inf)
                        sum += 2*(m+1-L[j]);
                }
                else
                    sum += m+1;
            }
            else {
                if(sta)
                    sum += m+1;
                else {
                    if(R[j]!=-1)
                        sum += 2*R[j];
                }
            }
            sta = tmp&1;
            tmp >>= 1;
        }
        if(sta) {
            if(L[n-1]!=inf)
                sum += (m+1-L[n-1]);
        }
        else {
            if(R[n-1]!=-1)
                sum += R[n-1];
        }
        sum += n-1;//上升n层楼花费n-1时间
        ans = min(ans, sum);
    }
    cout << ans << endl;
}

赛后发现还有用dp的O(n)方法

dp[i][2]表示第i层楼关闭所有灯后在最左边或最右边楼梯的最小时间花费

dp[i][0] = min(dp[i-1][0]+(R[i]==-1?0:2*R[i]), dp[i-1][1]+m+1) + 1

dp[i][1] = min(dp[i-1][0]+m+1, dp[i-1][1]+(L[i]==inf?0:2*(m+1-L[i]))) + 1

R[i]==-1或L[i]==inf表示改层楼没有开灯房间

再注意下最后一层楼关闭灯后不需要到最左边楼梯或最右边楼梯,最后答案就为 min(dp[n-2][0]+R[n-1], dp[n-2][1]+m+1-L[n-1]) + 1

时间复杂度O(n)

#include<bits/stdc++.h>
using namespace std;
#define MAX_N 100005
#define inf 0x7fffffff
#define LL long long
const int mod = 1e9+7;
const LL INF = 9e18;
typedef pair<int, int >P;
const double eps = 1e-6;

int main()
{
    char s[20][105];
    int L[20];
    int R[25];
    int n, m;
    cin >> n >> m;
    for(int i=n-1; i>=0; i--) {
        scanf("%s", s[i]);
        L[i] = inf;
        R[i] = -1;
        for(int j=0; j<m+2; j++) {
            if(s[i][j]=='1')
                L[i] = min(L[i], j), R[i] = max(R[i], j);
        }
    }
    int mm = 1;
    for(int i=n-1; i>=0; i--) {
        if(R[i]!=-1) {
            mm = i + 1;
            break;
        }
    }
    n = mm;
    if(n==1) {
        printf("%d\n", R[0]==-1?0:R[0]);
        return 0;
    }
    int dp[20][2];
    if(R[0]!=-1)
        dp[0][0] = 2*R[0];
    else
        dp[0][0] = 0;
    dp[0][1] = m + 1;
    for(int i=1; i<n-1; i++) {
        dp[i][0] = min(dp[i-1][0]+(R[i]==-1?0:2*R[i]), dp[i-1][1]+m+1) + 1;
        dp[i][1] = min(dp[i-1][0]+m+1, dp[i-1][1]+(L[i]==inf?0:2*(m+1-L[i]))) + 1;
    }
    int ans = min(dp[n-2][0]+R[n-1], dp[n-2][1]+m+1-L[n-1]) + 1;
    printf("%d\n", ans);
}

C

题目链接:

http://codeforces.com/contest/812/problem/C

题意:

有n件物品,问如何选取尽量多的物品使物品的花费不超过S,求最多选取多少件物品以及最少花费,当选取k件物品时物品的价格变为a[i]+i*k,i从一开始

直接二分k,然后得到没件物品的新花费在sort一下取前k个,就是取k个物品的最少花费。

时间复杂度O(n*log(n)*log(n))

#include<bits/stdc++.h>
using namespace std;
#define MAX_N 100005
#define inf 0x7fffffff
#define LL long long
const int mod = 1e9+7;
const LL INF = 9e18;
typedef pair<int, int >P;
const double eps = 1e-6;

LL n, S;
LL a[MAX_N];
bool check(LL k)
{
    LL b[MAX_N];
    for(LL i=1; i<=n; i++) {
        b[i] = a[i] + i*k;
    }
    sort(b+1, b+n+1);
    LL sum = 0;
    for(LL i=1; i<=k; i++) {
        sum += (LL)b[i];
    }
    if(sum>S)
        return false;
    return true;
}
int main()
{
    cin >> n >> S;
    for(int i=1; i<=n; i++) {
        scanf("%lld", &a[i]);
    }
    LL low = 1;
    LL up = n;
    LL ans = 0;
    while(low<=up) {
        LL mid = (low+up)/2;
        if(check(mid)) {
            ans = max(ans, mid);
            low = mid + 1;
        }
        else {
            up = mid - 1;
        }
    }
    if(!ans) {
        printf("0 0\n");
    }
    else {
        LL tmp = 0;
        for(LL i=1; i<=n; i++) {
            a[i] = a[i] + i*ans;
        }
        sort(a+1, a+n+1);
        for(LL i=1; i<=ans; i++) {
            tmp += a[i];
        }
        printf("%lld %lld\n", ans, tmp);
    }
}



E

推荐看Jaihk662的博客:http://blog.youkuaiyun.com/jaihk662/article/details/72842070

我就是看了他的题解才理解的

题目链接:

http://codeforces.com/contest/812/problem/E

题意:

存在一个有n个结点的苹果树,每个结点上都有ai个苹果,现在两个人在这个苹果树上玩一种无聊的游戏:

甲先走,乙后走

轮到每个人走的时候可以选择

1.将一个非叶子结点上的若干苹果移到该结点的孩子上,不允许移0个

2.将一个叶子结点上的苹果拿走若干,不允许拿0个

最后不能行动的人输

开局乙可以选择将两个结点上的苹果数交换,问有多少种交换方法使乙必胜。

甲乙行动都使用最佳移动方案

苹果树叶子结点的深度都为奇数或偶数

题解:

可以将结点分为两种:

1.该结点到子树叶子结点的距离为偶

2.该结点到子树叶子结点的距离为奇

(因为叶子结点的深度都为偶或都为奇,所以非叶子结点到所有子树叶子结点的距离奇偶性是固定的)

对于第二种结点:

当甲从该种结点上移动x个苹果到孩子时,乙可以模仿甲再将这x个苹果移动到下一层孩子,一直到当甲移动苹果到叶子结点时乙就可以拿走该叶子结点上的所有苹果。(因为到子树叶子结点距离为奇所以一定为甲最后移动苹果到叶子结点)所以该种结点对局面不起影响。

对于第一种结点:

因为到子树叶子结点的距离为偶,所以乙可以模仿甲的移动,这样就变成了尼姆博弈了,当距离为偶的异或值为0时为必败局势。


所以获胜可以先考虑起始局势为必胜还是必败

当起始局势为必败时,可以将距离为偶上的结点进行任意交换,也可以将距离为奇的结点进行任意交换,都不影响局势,次数为C(2,a)+C(2,b) a、b为距离为偶和奇的结点个数,

并且当将苹果数目相同的两种结点进行交换也不改变局势,所以次数再加 sum(even[i]*odd[i]),even[i]是距离为偶的结点中有几个结点的苹果数目为i,同理odd[i]为距离为奇的。

所以必败局势的答案为 C(2,a)+C(2,b)+sum(even[i]*odd[i])


当起始局势为必胜时,我们可以将异或值变为0使乙获胜。设异或值为tmp,则当从距离为偶的结点i上拿走所有a[i]个苹果时异或值变为tmp^a[i](这里参考异或的性质a^b^b=a),所以再和距离为奇的苹果数为tmp^a[i]的结点交换即可最终答案为sum(even[i]*odd[tmp^i])


#include<bits/stdc++.h>
using namespace std;
#define MAX_N 100005
#define inf 0x3f3f3f3f
#define LL long long
#define uLL unsigned long long
const LL mod = 1e9+7;
const LL INF = 1e18;
typedef pair<int, int>P;
const double eps = 1e-6;

int a[MAX_N];
vector<int> G[MAX_N];
int dep[MAX_N];
int maxdep = 0;
LL odd[10000005];
LL even[10000005];
void dfs(int u, int pre)
{
    maxdep = max(maxdep, dep[u]);
    for(int i=0; i<G[u].size(); i++) {
        int v = G[u][i];
        if(v==pre)
            continue;
        dep[v] = dep[u] + 1;
        dfs(v, u);
    }
}
int main()
{
    int up = 0;
    int n;
    cin >> n;
    for(int i=1; i<=n; i++) {
        scanf("%d", &a[i]);
        up = max(up, a[i]);
    }
    for(int i=2; i<=n; i++) {
        int x;
        scanf("%d", &x);
        G[i].push_back(x);
        G[x].push_back(i);
    }
    dep[1] = 0;
    dfs(1, 0);
    memset(odd, 0, sizeof(odd));
    memset(even, 0, sizeof(even));
    int tmp = 0;
    LL sumeven = 0;
    LL sumodd = 0;
    for(int i=1; i<=n; i++) {
        if(maxdep%2==dep[i]%2) {
            tmp ^= a[i];
            even[a[i]]++;
            sumeven++;
        }
        else {
            odd[a[i]]++;
            sumodd++;
        }
    }
    LL ans = 0;
    if(tmp==0) {
        ans = (sumeven*(sumeven-1))/2;
        ans += (sumodd*(sumodd-1))/2;
        for(int i=1; i<=up; i++) {
            ans += even[i]*odd[i];
        }
    }
    else {
        for(int i=1; i<=up; i++) {
            int t = tmp^i;
            if(t < 1e7+5)//避免溢出
                ans += even[i]*odd[tmp^i];
        }
    }
    cout << ans << endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值