Codeforces Round #508 (Div. 2)

A. Equality

给定一个字符串,求最长子字符串,要求所有字符出现次数相同。注意,由于要求使用的字符数k已经给出,但是不一定全部出现在字符串里,所以可能只能构造空字符串。

很简单了,先将所有字符的出现次数统计出来。然后看k个字符出现次数最少的那个,然后将最小出现次数乘以k就行了。

[n,k] = map(int, input().split())
const = input()
counting = {}
for i in range(k):
    counting[chr(ord('A') + i)] = 0
for i in const:
    counting[i] += 1
    
ret = n
for i,v in counting.items():
    ret = min(ret, v)
print(ret*k)


 B. Non-Coprime Partition

给出n,将从1到n这n个数字分到两份,分别求和得到sum1和sum2,求gcd(sum1,sum2) > 1的的分割方法。打印是否可以分割,然后把分割打印出来。

这里有一点小技巧。很明显,对于n为1,2,都是没有结果的,但是对于大于等于3的n,都是有结果的。但是具体要怎么分呢?我们直到n个数字的和为 (1 + n ) * n /2。这里,和分两种。奇数,偶数。

1,对于和为偶数的情况,任何偶数减2都还是偶数。所以最简单的分配方法,就是2一波,剩下的一波。他们的gcd为2。

2,对于和为奇数的情况,需要考虑两种情况,

a,n为奇数,这个时候,中间数middle = (1 + n)/2,必定有middle * n = (n + 1) * n /2 。很明显了,这个时候,middle一波,剩下的数一波。gcd为middle=(n+1)/2

b,n为偶数,这个时候,(1+n)/2 = middle,但是这个middle不是整数。所以我们换一个middle,用n/2。同理,middle一波,剩下的数一波,gcd为middle=n/2

n = int(input())
if n <= 2:
    print("No")
else:
    print("Yes")
    sum = (n + 1) * n / 2
    if(sum % 2 == 0):
        print("1 2")
        anotherLine = "%d 1" % (n - 1)
        for i in range(3,n + 1):
            anotherLine += " %d" % i
        print(anotherLine)
    else:
        if n % 2 == 0:
            middle = n /2
        else:
            middle = (n + 1) / 2
        print("1 %d" % (middle))
        anotherLine = "%d 1" % (n - 1)
        for i in range(2,n + 1):
            if i != middle:
                anotherLine += " %d" % i
        print(anotherLine)
    

C. Gambling

A,B两人分别有一组数字,没人可以从自己的数组取一个数字,拿起来。或者从对手的数组取一个数字,移出,不放入任何人的口袋。两人数组清空之后,游戏结束。游戏结束后,两人手中分别求手上的数字之和。求两人在用最优策略时,两人的数字相差为多少。

这个策略用贪心即可。把两人手头的数字从大到小排序,每一轮,如果自己手头的最大的数字比对手大,就拿起来,否则,移除对手的数字。

嗯。。。就是我这代码晚上写的,不够简洁。。。。

n = int(input())
A = list(map(int, input().split()))
B = list(map(int, input().split()))

A = sorted(A,reverse = True)
B = sorted(B,reverse = True)

sA, sB = 0, 0
i,j = 0,0
turn = 1
while True:
    turn += 1
    turn %= 2
    if i >= n and j >= n:
        break;
    if i == n:
        if turn == 1:
        #turn for b and a is list empty
            sB += B[j]
        j+=1
        continue
    if j == n:
        if turn == 0:
        #turn for a and b is list empty
            sA += A[i]
        i+=1
        continue
    if turn == 0:
        #turn for a
        if A[i] > B[j]:
            sA += A[i]
            i +=1
        elif A[i] < B[j]:
            j +=1
        else:
            j +=1
    else:
        if A[i] > B[j]:
            i += 1
        elif A[i] < B[j]:
            sB += B[j]
            j += 1
        else:
            i += 1
print(sA-sB)

D. Slime

有一排史莱姆,他们可以吃自己左边或者右边的史莱姆。每个史莱姆有一个数值。每次吞噬,都用自己的数字减对方的数字。求所有史莱姆吞噬完,只剩一个史莱姆的时候,能得到的最大数字是多少。

这个策略也简单。

如果输入有正有负,则将所有数字的绝对值加起来,即可(很简单,从负数开始,一直用小的吃大的,不断积累,最后用正数吃负数即可)

如果输入全部为正或者全部为负,则将所有数字的绝对值加起来,然后减 2 乘以最小的绝对值即可。第一步用最小的绝对值吃旁边的史莱姆(或者被吃掉),得到一个有正有负的数列。然后重复上面有正有负的步骤。

import sys
n = int(input())
line = list(map(int, input().split()))

if n == 1:
    print(line[0])
    sys.exit(0)

sum = 0
minNum = 10 ** 9
maxNum = -10**9
for i in line:
    if minNum > i:
        minNum = i
    if maxNum < i:
        maxNum = i
    sum += abs(i)
if minNum * maxNum < 0:
    print(sum)
elif minNum < 0:
    print(sum - 2 * abs(maxNum))
else:
    print(sum - 2 * abs(minNum))

E. Maximum Matching

有很多block,block左右分别有一种颜色,左右的,从所有block里面选一部分出来,同颜色的边可以拼在一起。求拼在一起的block的value的和最大为多少。

我一开始以为是动态规划,折腾了很久。后来看了别人的答案才知道,嗯。。。欧拉图。。。

我们把四个颜色看成四个点,一个block表示从一个点到另一个点的一条边。block的value就是边的权重。求一个子图,该子图构成欧拉回路,并且权重之和最大。

进一步,由于只有四个点,又是无向图,所以我们可以有这样的结论,要么刚好所有节点都是偶数度节点,要么有两个节点是奇数度节点,要么四个节点都是奇数度节点。这里其实只有最后一种情况不符合欧拉图的要求,这个时候需要去掉一条边,这样肯定有两个节点分别减少一个度,变成两个偶数度节点。于是问题变成了去掉那条边。

出题者给的答案是,我们只有16条边,整一个bitmap,遍历所有的边的组合。然后看哪个组合得到的和最大。时间复杂度为n * 2^16。嗯。。。。。其实挺好的。但是我觉得太复杂了。看了下答案,有复杂度为n的解。于是我研究了一下。奥,他们直接从一个节点开始dfs,遇到的节点加入进来,计算度数。如果有四个奇数度的节点在这个子图,那么需要去掉一条边,很多人直接拿一条权重最低的边一减就完事了,并且AC了。

感觉哪里不对劲的我把AC代码抄下来丢上去。跑test,一切都很愉悦,直到第117个test case,嗯???WA了?为嘛他能过,我一样的就WA,一看记录,比赛的时候只有116个test case。。。

仔细分析test case117,发现他的权重最小的那条边,是一个割边,去除之后,有一个节点从原来的欧拉图上面割裂开了。。。。看来codeforeces的测试用例也不是很全嘛。。。。

正确的做法是。将任意两个点(包括从自己出发到自己)的边的min值保存下来。先做一次dfs,这个dfs需要做的就是遍历四个点,将连通子图的权重和求出来,如果这个子图的奇数节点为4,那就丢掉这个结果。然后,遍历16条边,如果这个边在图里面出现了,不管是有一条还是几条,尝试将它抹去,然后做一次dfs,更新结果,再把这个边加回来。时间复杂度 4*4*n。很愉悦。

#include <bits/stdc++.h>

using namespace std;

int deg[4];
vector<int> e[4][4];
bool was[4];
unsigned int min_v[4][4];

int sum, min_val, cnt_node, cnt_deg, res;

vector<int> removed;
void dfs(int u) {
	was[u] = true;
	cnt_deg += deg[u];
	cnt_node += 1;
	for (int c : e[u][u]) {
		sum += c;
	}
	for (int v = 0; v < 4; ++v) {
		if (!e[u][v].empty()) {
			if (u < v) {
				for (int c : e[u][v]) {
					sum += c;
				}
			}
			if (!was[v]) dfs(v);
		}
	}
}

void check()
{
    memset(was, 0, sizeof(was));
    for (int i = 0; i < 4; ++i) if (!was[i]) {
        sum = 0;
        cnt_deg = 0;
        dfs(i);
        if (cnt_deg != 4) {
            res = max(res, sum);
        }
    }
//    printf("res:%d\n", res);
}

//void print_all_edeges()
//{
//    for (int i = 0; i < 4; ++i) {
//        for (int j = 0; j <= i; ++j) {
//            if(e[i][j].size() > 0)
//            {
//                printf("i:%d, j:%d, size:%d\n", i + 1, j +1, e[i][j].size());
//            }
//        }
//    }
//    printf("\n");
//}
int main() {
	int n; cin >> n;

    res = 0;
    memset(min_v, 0xff, sizeof(min_v));
	for (int i = 0; i < n; ++i) {
		int u, v, c;
		cin >> u >> c >> v;
		deg[--u] ^= 1;
		deg[--v] ^= 1;
		e[u][v].push_back(c);
        if(c < min_v[u][v])
        {
            min_v[u][v]=c;
            min_v[v][u]=c;
        }
		if (u != v) e[v][u].push_back(c);
	}

    check();
    for (int j = 0; j < 4; j++)
    {
        for(int k = 0; k <= j; k++)
        {
            //delete one edge
            if(e[j][k].size() > 0)
            {
                auto i = e[j][k].begin();
//                printf("min_v[j][k] is :%d %d\n", min_v[j][k], min_v[k][j]);
                for(;i != e[j][k].end(); i++)
                {
                    if(*i == min_v[j][k])
                    {
                        e[j][k].erase(i);
                        break;
                    }
                }
                auto it = e[k][j].begin();
                if(j!=k)
                {
                    for(;it != e[k][j].end(); it++)
                    {
                        if(*it == min_v[k][j])
                        {
                            e[k][j].erase(it);
                            break;
                        }
                    }
                }
//                printf("after remove %d, %d\n", k + 1, j+ 1);
//                print_all_edeges();
                deg[j] ^= 1;
                deg[k] ^= 1;
                check();
                //add back after check
                e[j][k].push_back(min_v[j][k]);
                if(j!=k)e[k][j].push_back(min_v[j][k]);
                deg[j] ^= 1;
                deg[k] ^= 1;
//                printf("after recover\n");
//                print_all_edeges();
            }
        }
    }
	cout << res << endl;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值