Codeforces Round #599 (Div. 2)
https://codeforces.com/contest/1243
A - Maximum Square
题意:给 \(n\) 块宽度为 \(1\) 长度为 \(a_i\) 的木板,把这些木板拼在一起,求最大形成的正方形的边长。
题解:贪心,从大到小排序,然后找第一个满足 \(a_i<i\) 的位置break掉。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, a[1005];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int k;
scanf("%d", &k);
while(k--) {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n, greater<int>());
int ans = 0;
for(int i = 1; i <= n; ++i) {
if(a[i] >= i)
ans = i;
else
break;
}
printf("%d\n", ans);
}
return 0;
}
B1 - Character Swap (Easy Version)
题意:给两个互异的字符串 \(s\) 和 \(t\) ,必须选择某个 \(s[i]\) 和 \(t[j]\) 交换恰好1次,问能否使得两个字符串相等。
题解:交换恰好1次应该是最多减少两个不等的位置,找出不等的位置之后判断是不是恰好只有两个,若是,则直接交换然后判断相等,否则直接No。
注:由于是互异的所有不用特判0,否则0是一定合法的(交换(1,1))。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
char s[10005], t[10005];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int k;
scanf("%d", &k);
while(k--) {
scanf("%d%s%s", &n, s + 1, t + 1);
vector<int> dif;
for(int i = 1; i <= n; ++i) {
if(s[i] != t[i]) {
dif.push_back(i);
if(dif.size() >= 3)
break;
}
}
if(dif.size() != 2) {
puts("No");
} else {
swap(s[dif[0]], t[dif[1]]);
if(strcmp(s + 1, t + 1) == 0)
puts("Yes");
else
puts("No");
}
}
return 0;
}
B2 - Character Swap (Hard Version)
题意:给两个互异的字符串 \(s\) 和 \(t\) ,选择某个 \(s[i]\) 和 \(t[j]\) 交换不超过 \(2n\) 次,问能否使得两个字符串相等。
题解:很显然有解的必要条件就是所有的字母出现都是偶数次,先判断这个。其次满足上面的必要条件之后必定有解,因为使用最多2次交换就可以消除至少1个位置上的不同。具体的做法是:固定已经相等的前半部分,然后假如 \(s[i] \neq t[i]\),如果有某个后面的 \(s[id]=s[i]\) ,那么就把 \(s[id]\) 和 \(t[i]\) 交换。否则,必定存在某个后面的 \(t[id]=s[i]\) ,但是 \(t[id]\) 不能直接与 \(t[i]\) 交换,所以让它与 \(s[n]\) 交换(这样比较好写),然后把 \(s[n]\) 和 \(t[i]\) 交换。
注:输出要先输出 \(i\) 再输出 \(j\) ,这个WA不值得。用set实现的这个算法可以应对5e4级别的,可惜数据量太小了,各路神仙直接暴力。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
char ss[55], tt[55];
int s[55], t[55];
set<int> accs[128], acct[128];
vector<pair<int, int> > op;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int k;
scanf("%d", &k);
while(k--) {
scanf("%d%s%s", &n, ss + 1, tt + 1);
for(int c = 'a'; c <= 'z'; ++c) {
accs[c].clear();
acct[c].clear();
}
for(int i = 1; i <= n; ++i) {
s[i] = (int)ss[i];
t[i] = (int)tt[i];
accs[s[i]].insert(i);
acct[t[i]].insert(i);
}
bool suc = 1;
for(int c = 'a'; c <= 'z'; ++c) {
if((accs[c].size() + acct[c].size()) & 1) {
suc = 0;
break;
}
}
if(suc) {
op.clear();
for(int i = 1; i <= n; ++i) {
accs[s[i]].erase(i);
if(s[i] != t[i]) {
if(accs[s[i]].size()) {
int id = *accs[s[i]].begin();
accs[s[id]].erase(id);
accs[t[i]].insert(id);
acct[t[i]].erase(i);
acct[s[id]].insert(i);
op.push_back({id, i});
swap(s[id], t[i]);
} else {
int id = *acct[s[i]].begin();
accs[s[n]].erase(n);
accs[t[id]].insert(n);
acct[t[id]].erase(id);
acct[s[n]].insert(id);
op.push_back({n, id});
swap(t[id], s[n]);
id = *accs[s[i]].begin();
accs[s[id]].erase(id);
accs[t[i]].insert(id);
acct[t[i]].erase(i);
acct[s[id]].insert(i);
op.push_back({id, i});
swap(s[id], t[i]);
}
}
acct[t[i]].erase(i);
}
puts("Yes");
printf("%d\n", (int)op.size());
for(auto i : op)
printf("%d %d\n", i.first, i.second);
} else
puts("No");
}
return 0;
}
C - Tile Painting
题意:给排成一行的 \(n\) 个格子,要求所有的 \(i,j\) 满足 \(|i-j|\) 是 \(n\) 的因子的,都要同色,求最大的涂色数。
题解:非常显然就是所有质因子的gcd,当然因为都是质因子,所以直接判断有没有多种质因子,若有多种质因子直接输出 \(1\) 就可以。为什么要放在C题坑人,放在B题不好吗。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
while(~scanf("%lld", &n)) {
if(n == 1) {
puts("1");
continue;
}
ll cn = n;
vector<ll> fac;
for(ll i = 2; i * i <= cn; ++i) {
if(cn % i == 0) {
fac.push_back(i);
while(cn % i == 0)
cn /= i;
}
}
if(cn != 1)
fac.push_back(cn);
if(fac.size() >= 2)
puts("1");
else
printf("%lld\n", fac[0]);
}
return 0;
}
*D - 0-1 MST
题意:给一个 \(n\) 个节点的完全图,有 \(m\) 条权为1的边,其他的边权为0,求最小生成树的权值和。
题解:改写Prim算法,放三个容器,一个权为无穷(未知),一个权为0,一个权为1,每次优先取出0容器里面的扩展,不消耗任何东西,否则取出1容器的扩展,cost++。扩展的时候1容器可以从无穷容器夺取节点,0容器可以从1容器夺取节点,所以1容器和无穷容器应该是set。复杂度想不明白。
注:换了好几次做法,最后应该是时间不够,浪费太多在前面换来换去了,应该是类似0-1BFS的思路,改写Prim算法。???这样魔改之后不就是0-1BFS了吗?就直接用0-1BFS就可以了。
补题细节:弄一个链表/并查集存放不在0容器里面的节点,遍历有序的边表时可以顺便知道哪些节点会进入0容器,哪些节点会变成1然后暂时放进1容器,吐出一个最小度点之后会删除这个链表/并查集中的大部分节点,最差情况是最小度点也是满的,也就是1e5的平方根的完全图,这种情况退化到2次方带log?但是数据小没问题(或者另外写一个算法解决这种情况),吐出点的顺序按度数来启发式吐,这样复杂度更玄学。
实验证明这样是对的。只有0-1边权的Prim实际上就和0-1BFS没有什么不同。主要是要避免遍历0边,解决的方法是记录还没有变成0边且有机会变成0边的节点,然后启发式吐出来一次尽可能把最多的0节点确定下来,复杂度玄学。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 100005;
vector<int> G[MAXN];
int d[MAXN];
int dis[MAXN], vis[MAXN];
priority_queue<pair<int, int> > one, zero;
int pre[MAXN], nxt[MAXN];
int Prim(int s, int n) {
int cnt = 0, cost = 0;
dis[s] = 0;
zero.push({-d[s], s});
nxt[pre[s]] = nxt[s];
pre[nxt[s]] = pre[s];
while(cnt < n) {
int u = -1;
if(zero.size()) {
u = zero.top().second;
zero.pop();
} else {
u = one.top().second;
one.pop();
if(!vis[u])
++cost;
}
if(vis[u])
continue;
vis[u] = 1;
++cnt;
int cur = nxt[0];
for(int j = 0; j <= d[u]; ++j) {
int v = G[u][j];
while(v > cur) {
if(dis[cur] != 0 && vis[cur] == 0) {
dis[cur] = 0;
zero.push({-d[cur], cur});
nxt[pre[cur]] = nxt[cur];
pre[nxt[cur]] = pre[cur];
}
cur = nxt[cur];
}
if(v == cur) {
if(cur <= n && dis[cur] == INF) {
dis[cur] = 1;
one.push({-d[cur], cur});
}
cur = nxt[cur];
}
}
}
return cost;
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int n, m;
while(~scanf("%d%d", &n, &m)) {
while(one.size())
one.pop();
while(zero.size())
zero.pop();
for(int i = 1; i <= n; ++i) {
G[i].clear();
dis[i] = INF;
vis[i] = 0;
}
for(int i = 0; i <= n; ++i) {
pre[i] = i - 1;
nxt[i] = i + 1;
}
while(m--) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
int s = 1;
for(int i = 1; i <= n; ++i) {
d[i] = G[i].size();
sort(G[i].begin(), G[i].end());
G[i].push_back(n + 1);
if(d[i] < d[s])
s = i;
}
printf("%d\n", Prim(s, n));
}
return 0;
}
*E - Sum Balance
目前水平太差了,代码和题解都看不懂,等橙了再说吧。
update:还真的橙了,回来补题。
题意:给 \(k leq 15\) 个盒子,每个盒子有若干个数字,每个盒子的数不超过 \(5000\) 个。所有的数字两两互异。要求一种方案,使得从每个盒子拿出一个数字然后放到一个盒子(可以放回去)里,并且所有的盒子移动前后数字的数量不能改变,且要使得所有盒子的数字的和相等。
题解:首先,显然每个数字的和必须是相等的,意味着和必须是 \(k\) 的倍数,并且每个盒子要变成的和已经确定。这种移动方案会使得每个盒子有一个出度和一个入度,那么这个就是若干个不相交的环(每个盒子看作一个点,都有一个出度和一个入度,就是若干个环)。从元素最少(或者任意一个)的节点中,枚举出边的权值,那么入边的权值也会随之确定,由于数字的唯一的,那么要么找到这个数字没有被作为出度使用过且在环中的节点里,要么无解。若有解则可以继续往下递归,直到形成一个环。处理这个情况,每个点需要5000的复杂度。在这里之后,每个点取出某个数字作为出边,是否合法,以及合法的话使用的方案(点数)是什么已经确定。搞定了这个问题之后,使用子集dp来合并若干个环,只需要记录是否可行以及由哪个状态转移得到,最后验证所有节点的方案是否可行并输出答案。
参考博客:https://blog.csdn.net/Scar_Halo/article/details/102964709