Codeforces Round #558 (Div. 2)题解
Codeforces Round #558 (Div. 2)题解
A. Eating Soup
水题,直接给出代码。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n, m;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
int remain = n - m;
if(remain <= n / 2) {
cout << remain;
return 0;
}
cout << max(n - remain, 1);
return 0;
}
B2. Cat Party (Hard Edition)
问题最后只和每个数出现的次数之间的大小有关,有如下几种情况:
- 假设出现次数最多为\(x\)次,那么一个数的出现次数为\(x\)次,其它数的出现次数为\(x-1\)次。
- 假设出现次数最多为\(x\)次,那么有一个数的出现次数为\(1\),其余数的出现次数都为\(x\)次。
这还是很好想的,只有这两种情况与题意相符合。所以我们用一个数组维护出现次数,另一个数组维护出现次数的次数,再维护一下出现最多的次数为多少,最后扫一遍判断就好了。
代码如下:(写的时候我傻逼离散化了一下,其实可以不用,这样\(O(n)\)就可以解决)
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n;
int sum[N], a[N], b[N], cnt[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
sort(b + 1, b + n + 1);
int D = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + D +1, a[i]) - b;
int ans = 1;
int num = 0;
int mx = 0, num1;
for(int i = 1; i <= n; i++) {
if(sum[a[i]] > 0) cnt[sum[a[i]]]--;
sum[a[i]]++;
cnt[sum[a[i]]]++;
if(sum[a[i]] > mx) {
mx = sum[a[i]];
num1 = 1;
} else if(sum[a[i]] == mx) {
num1++;
}
if(num1 == 1 && cnt[mx - 1] * (mx - 1) == i - mx) ans = i;
if(num1 * mx == i - 1 && cnt[1] == 1) ans = i;
if(mx == 1) ans = i;
}
cout << ans;
return 0;
}
C2. Power Transmission (Hard Edition)
当时比赛的时候被这个题关了,有点可惜唉。
已知两个点\(A(x_1,y_1),B(x_2,y_2)\),那么经过这两个点的直线方程为:\(y=\frac{y_2-y_1}{x_2-x_1}x+y_1-x_1*\frac{y_2-y_1}{x_2-x_1}\),可以化为:\((x_2-x_1)*y-(y_2-y_1)*x=y_1*x_2-y_2*x_1\)。
之后我们把形式统一一下,暴力搞就行了。
代码如下:
Code
#include <bits/stdc++.h>
#define MP make_pair
using namespace std;
typedef long long ll;
const int N = 2505;
int n;
struct node{
int x, y;
bool operator < (const node &A)const{
if(A.x == x) return y < A.y;
return x < A.x;
}
}a[N], b[N];
map <pair<int, int>, set<int> > mp;
map <int, int> c1, c2 ;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].y;
}
ll ans, tot = 0;
ans = 0;
int num = 0;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
int dx = a[j].x - a[i].x ;
int dy = a[j].y - a[i].y ;
int g = __gcd(dx, dy) ;
if(dx == 0 && dy == 0) continue ;
dx /= g ;
dy /= g ;
if(dy < 0 || (dy == 0 && dx < 0)) {
dx = -dx ;
dy = -dy;
}
int c = dy * a[i].x - dx * a[i].y ;
if(!mp[MP(dy, dx)].count(c)) {
++tot;
mp[MP(dy, dx)].insert(c) ;
ans += tot - mp[MP(dy, dx)].size() ;
}
}
}
cout << ans ;
return 0;
}
D. Mysterious Code
比较套路的一个KMP+DP,因为长度很小,所以先暴力处理出\(f(i,j)\)表示当前在第\(i\)个位置,后面一位为\(j(0<j<26)\)时,能匹配到的最大位置。这里的匹配是自己和自己匹配,类似于KMP算法中的next数组。
之后直接由\(dp(i,j,k)\)表示第一个串在\(i\)位置,第二个串在第\(j\)个位置,第三个串在第\(k\)个位置,所求的最大值。
之后直接转移就行了。至于这里的转移为什么是正确的,可以类比于next指针想一下。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1005, M = 55, MAX = 26;
const int INF = 1e9;
int nxts[M][MAX], nxtt[M][MAX] ;
char s[M], t[M], c[N];
int tests[N] ;
int dp[N][M][M] ;
void Get_Nxts(char *s, int Nxt[][MAX]) {
int L = strlen(s + 1) ;
for(int i = 0; i <= L; i++) {
for(int p = 0; p < MAX; p++) {
char nxt = p + 'a' ;
for(int j = min(i + 1, L); j >= 0; j--) {
bool flag = true ;
for(int k = 1; k < j; k++) {
if(s[k] != s[i + 1 - j + k]) flag = false ;
}
if(s[j] != nxt) flag = false;
if(flag) {
Nxt[i][p] = j;
break ;
}
}
}
}
}
void getmx(int &x, int y) {
if(y > x) x = y;
}
int main() {
scanf("%s",c + 1);
scanf("%s%s",s + 1, t + 1);
Get_Nxts(s, nxts) ;
Get_Nxts(t, nxtt) ;
for(int i = 0; i < N; i++)
for(int j = 0; j < M; j++)
for(int k = 0; k < M; k++)
dp[i][j][k] = -INF;
dp[0][0][0] = 0;
int L1 = strlen(c + 1), L2 = strlen(s + 1), L3 = strlen(t + 1);
for(int i = 0; i < L1; i++) {
for(int j = 0; j <= L2; j++) {
for(int k = 0; k <= L3; k++) {
if(dp[i][j][k] == -INF) continue ;
if(c[i + 1] == '*') {
for(int p = 0; p < MAX; p++) {
int d = 0;
if(nxts[j][p] == L2) d++;
if(nxtt[k][p] == L3) d--;
getmx(dp[i + 1][nxts[j][p]][nxtt[k][p]], dp[i][j][k] + d) ;
}
} else {
int d = 0;
int p = c[i + 1] - 'a' ;
if(nxts[j][p] == L2) d++;
if(nxtt[k][p] == L3) d--;
getmx(dp[i + 1][nxts[j][p]][nxtt[k][p]], dp[i][j][k] + d) ;
}
}
}
}
int ans = -INF;
for(int i = 0; i <= L2; i++)
for(int j = 0; j <= L3; j++)
getmx(ans, dp[L1][i][j]) ;
cout << ans;
return 0;
}
E. Magical Permutation
这是一个很巧妙的构造题orz。
先简单说下题意吧:就是会给一个集合\(S\),其中包含\(S_1,S_2,\cdots,S_n\)共\(n\)个数。现在要求你找出最大的\(x\),构造出一个\(0\)到\(2^x-1\)中的所有数的排列。并且相邻两个数的异或值在这个集合\(S\)中。
首先我们可以对这个题进行一些分析:\(0\)左右的数一定在\(S\)中,假设其中一个数为\(x\),那么在\(x\)另一旁的一个数设为\(y\),因为\(x\)^\(y\in S\),可知\(y\)为\(S\)中某些数的异或值。以此类推,就有我们所有构造出来的数都要为\(S\)中某些数的异或值。
到了这里想到了些什么?每个二进制位都存在的线性基!
所以最大的\(x\)我们就可以直接通过线性基来构造。之后解决的问题就是具体的方案了。
这里构造方案需要一点Gray code(格雷码)的知识,但我们先看看具体构造方案:
void gray_code(int x) {
cnt = 1;
cout << x << '\n' ;
for(int i = 1; i <= x; i++)
for(int j = cnt; j > 0; j--) ans[++cnt] = ans[j] ^ now[i] ;
for(int i = 1; i <= cnt; i++) cout << ans[i] << ' ' ;
}
这里就短短两行代码,却十分精妙。看了我好半天
这里一开始有1个数,后面有2个,再后面有4个...以此类推。
每一次都是将前面的数翻转过来异或当前二进制位上面线性基的值。
下面来证明一下这样做的正确性:
当数个数比较少的时候,显然满足条件;
我们假设前面\(2^y\)个数满足条件,很容易知道第\(2^y\)个数一定是集合\(S\)中的某个值,这个很容易知道,因为最后是和0异或的(可以看看之后代码中now数组中存的值)。那么现在来进行构造,可以知道第\(2^y\)个数和第\(2^{y+1}\)异或的值也一定是在\(S\)中的某个数。并且后面\(2^y\)个数两两之间的异或值也一定在集合\(S\)中,因为这是由前面构造过来的,多异或了一个now,两两异或这个多异或的就会被抵消,就相当于前面的两个异或,由我们的假设就得证了。
构造方法和证明说完了,那这和格雷码有什么关系呢。
格雷码的构造方式可以分治来构造,就是将一个二进制串前面加一个0,然后翻转过来在前面加一个1。(详见我上面给出的链接中的镜射排列部分)
注意到这个翻转和我们构造时的翻转很像,那么格雷码有什么性质呢?相邻两个数二进制只差一位!
那么我们将x个线性基的值每一个都看作二进制中的1,那么我们通过类似方法构造出来的也是每个相邻的都差一个二进制1,也不就是一个属于\(S\)集合中的值吗??
这样,格雷码和我们的构造方式就联系了起来!是不是感觉十分巧妙,也不负这个题目中的"Magic"了。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n, cnt;
int a[N], p[22], now[22], ans[N];
void ins(int x) {
int tmp = x;
for(int i = 20; i >= 0; i--) {
if((x >> i) & 1) {
if(!p[i]) {
p[i] = x;
now[++cnt] = tmp;
break ;
} else x ^= p[i] ;
}
}
}
void gray_code(int x) {
cnt = 1;
cout << x << '\n' ;
for(int i = 1; i <= x; i++)
for(int j = cnt; j > 0; j--) ans[++cnt] = ans[j] ^ now[i] ;
for(int i = 1; i <= cnt; i++) cout << ans[i] << ' ' ;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 20; i >= 0; i--) {
memset(p, 0, sizeof(p)); cnt = 0 ;
for(int j = 1; j <= n; j++) {
if(a[j] < 1 << i) ins(a[j]) ;
}
if(i == cnt) break ;
}
gray_code(cnt) ;
return 0 ;
}
重要的是自信,一旦有了自信,人就会赢得一切。