Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4)
A - Happy Birthday, Polycarp!
题意:给一个n<=1e9,求[1,n]里面有多少个数字是有单个数字重复多次构成的。
题解:这种数字本身不对,一个一个暴力验证就可以。假如数据量继续扩大,那么把所有的数字生成出来(至多200个,当1e18时),排个序,然后把n在里面二分,应该速度会大概提升10倍。
void test_case() {
int n, ans = 0;
scanf("%d", &n);
for(int x = 1; x <= 9; ++x) {
ll tmp = x;
while(tmp <= n) {
++ans;
tmp = 10 * tmp + x;
}
}
printf("%d\n", ans);
}
B - Make Them Odd
题意:给一个序列,你每次可以选一个偶数,然后把所有等于这个偶数的数变成原来的一半,求最小的操作步数。
题解:每次取最大的出来贪心,每个数最多被贪30次,再加上优先队列的log就不太美妙了,但是总的复杂度依然是nlogn+nlogai。经过观察发现能够连在一起的都是非2的部分一样的数,也就是每种除去2的幂之后一样的数贡献这一种的最高2的幂,还是要用map去统计,复杂度没变。
附带一个更快的分解2的幂次的办法:__builtin_ctz(x),返回后跟0的数量。来源:https://www.cnblogs.com/liuzhanshan/p/6861596.html
int a[200005];
map<int, int> m;
void test_case() {
int n, ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
int tmp = __builtin_ctz(a[i]);
m[a[i] >> tmp] = max(m[a[i] >> tmp], tmp);
}
for(auto &i : m)
ans += i.second;
m.clear();
printf("%d\n", ans);
}
C - As Simple as One and Two
题意:给一个字符串,移除最小数量的字符使得里面既没有子串"one"也没有子串"two"。
题解:一开始在考虑一个dp[04][j]表示前j个字符经过最少dp[04][j]次移除操作之后停留目前停留在状态0~4的做法,下次把这个补上。事实上想到一种更好的思路,首先重叠的字符串形如"xtwoney",这时候拿走"o"就一箭双雕,其他的情况形如"xtwoy"和"xoney"都是拿走中间的那个字符,因为拿走两边的可能x和y也会连上来。易知这就是最小构造。
char s[2000005];
vector<int>ans;
void test_case() {
scanf("%s", s + 1);
int n = strlen(s + 1);
ans.clear();
for(int i = 3; i <= n; ++i) {
if(s[i - 2] == 't' && s[i - 1] == 'w' && s[i] == 'o') {
if(i + 1 <= n && s[i + 1] == 'o') {
s[i - 1] = '*';
ans.push_back(i - 1);
} else {
s[i] = '*';
ans.push_back(i);
}
}
}
for(int i = 3; i <= n; ++i) {
if(s[i - 2] == 'o' && s[i - 1] == 'n' && s[i] == 'e') {
s[i - 1] = '*';
ans.push_back(i - 1);
}
}
printf("%d\n", (int)ans.size());
for(auto &v : ans)
printf("%d ", v);
printf("\n");
}
D - Let's Play the Words?
题意:给一堆01串玩成语接龙,要求翻转最少的串使得把所有的串都用在一起。
题解:和上次那个奇偶填数的类似,应该就是01串和10串的数量差<=1就可以,而且假如00和11同时存在则至少要有一个01或者10作为黏着剂。这里要求翻转串之后也没有相同的串,所以有反串的就直接不允许翻转,用正数表示翻转此串会导致01->10,用负数表示翻转此串会导致10->01,0表示它的反串存在。若反串检测到0那么就把正串也标为0。注意这种hash的时候要随着串的移动添加一个常数在后面区别"1","01"和"001"。
用hash+map的实现,390ms。
用hash+unorder_map的实现,243ms。
不知道从哪里掏出来的一个图:来源
由上图可知,假如选的数字随机分布,那么中奖的概率比买彩票中奖的概率还要小。
unordered_map<ull, int> m;
vector<int> v;
int l;
char s[4000005];
ull toKey1() {
ull key1 = 0;
for(int i = 1; i <= l; ++i)
key1 = key1 * 23333 + (s[i] - '0' + 23);
return key1;
}
ull toKey2() {
ull key2 = 0;
for(int i = l; i >= 1; --i)
key2 = key2 * 23333 + (s[i] - '0' + 23);
return key2;
}
void test_case() {
int n;
scanf("%d", &n);
m.clear();
int cnt01 = 0, cnt10 = 0;
bool cnt11 = 0, cnt00 = 0;
for(int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
l = strlen(s + 1);
if(s[1] == s[l]) {
if(s[1] == '0')
cnt00 = 1;
else
cnt11 = 1;
continue;
}
ull k1 = toKey1();
ull k2 = toKey2();
auto it = m.find(k1);
if(it == m.end()) {
if(s[1] == '0') {
m[k1] = i;
m[k2] = 0;
++cnt01;
} else {
m[k1] = -i;
m[k2] = 0;
++cnt10;
}
} else {
m[k1] = 0;
m[k2] = 0;
if(s[1] == '0')
++cnt01;
else
++cnt10;
}
}
if(cnt00 && cnt11) {
if(cnt01 + cnt10 == 0) {
puts("-1");
return;
}
}
if(abs(cnt01 - cnt10) <= 1) {
puts("0");
puts("");
return;
}
v.clear();
if(cnt01 > cnt10) {
for(auto &i : m) {
if(i.second > 0)
v.push_back(i.second);
}
int rest = (cnt01 - cnt10) / 2;
if(rest > v.size()) {
puts("-1");
return;
}
printf("%d\n", rest);
for(int i = 0; i < rest; ++i)
printf("%d ", v[i]);
puts("");
return;
} else {
for(auto &i : m) {
if(i.second < 0)
v.push_back(-i.second);
}
int rest = (cnt10 - cnt01) / 2;
if(rest > v.size()) {
puts("-1");
return;
}
printf("%d\n", rest);
for(int i = 0; i < rest; ++i)
printf("%d ", v[i]);
puts("");
return;
}
}
假如害怕冲突,可以使用复杂度更低的Trie的实现。
vector<int> v;
struct TrieNode {
int data;
int nxt[2];
void Init() {
data = 0;
memset(nxt, 0, sizeof(nxt));
}
};
struct Trie {
static const int MAXN = 4000000;
TrieNode tn[MAXN + 5];
int root, top;
int NewNode() {
tn[++top].Init();
return top;
}
void Init() {
top = 0;
root = NewNode();
}
void Insert(char *a, int len, int data) {
int cur = root;
for(int i = 1; i <= len; ++i) {
int &nxt = tn[cur].nxt[a[i] - '0'];
if(!nxt)
nxt = NewNode();
cur = nxt;
}
tn[cur].data = data;
}
int Query(char *a, int len) {
int cur = root;
for(int i = 1; i <= len; ++i) {
int &nxt = tn[cur].nxt[a[i] - '0'];
if(!nxt)
return 0;
cur = nxt;
}
return tn[cur].data;
}
void _dfs(int cur, int val) {
if(tn[cur].data && tn[cur].data != INF) {
if(val == 1 && tn[cur].data >= 1)
v.push_back(tn[cur].data);
if(val == -1 && tn[cur].data <= -1)
v.push_back(-tn[cur].data);
}
if(tn[cur].nxt[0])
_dfs(tn[cur].nxt[0], val);
if(tn[cur].nxt[1])
_dfs(tn[cur].nxt[1], val);
}
void dfs(int val) {
_dfs(root, val);
}
} trie;
int l;
char s[4000005];
void test_case() {
int n;
scanf("%d", &n);
trie.Init();
int cnt01 = 0, cnt10 = 0;
bool cnt11 = 0, cnt00 = 0;
for(int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
l = strlen(s + 1);
if(s[1] == s[l]) {
if(s[1] == '0')
cnt00 = 1;
else
cnt11 = 1;
continue;
}
int res = trie.Query(s, l);
if(res == 0) {
if(s[1] == '0') {
++cnt01;
trie.Insert(s, l, i);
} else {
++cnt10;
trie.Insert(s, l, -i);
}
} else {
if(s[1] == '0')
++cnt01;
else
++cnt10;
}
reverse(s + 1, s + 1 + l);
trie.Insert(s, l, INF);
}
if(cnt00 && cnt11) {
if(cnt01 + cnt10 == 0) {
puts("-1");
return;
}
}
if(abs(cnt01 - cnt10) <= 1) {
puts("0");
puts("");
return;
}
v.clear();
if(cnt01 > cnt10) {
trie.dfs(1);
int rest = (cnt01 - cnt10) / 2;
if(rest > v.size()) {
puts("-1");
return;
}
printf("%d\n", rest);
for(int i = 0; i < rest; ++i)
printf("%d ", v[i]);
puts("");
return;
} else {
trie.dfs(-1);
int rest = (cnt10 - cnt01) / 2;
if(rest > v.size()) {
puts("-1");
return;
}
printf("%d\n", rest);
for(int i = 0; i < rest; ++i)
printf("%d ", v[i]);
puts("");
return;
}
}
使用Trie的时候注意区分找不到字符串(返回0),和反串被使用(返回INF)。
E - Two Fairs
题意:给一个无向连通图,给定x和y两个点,求有多少个不含x也不含y的无序对(u,v),满足u到v的任何一条路都经过x且经过y。
题解:假如改成最短路又变成了考Dijkstra松弛然后找最短路径生成树再加上最短路径生成树外的附加边?不过这里我使用的是进行一次求割点,当x和y都是割点时,他们之间夹的点以及他们本身去掉,剩下的两个部分相乘就是答案。求割点使用Tarjan算法。一开始还在搞什么双连通分量缩点,其实真的没必要而且也没关系。注意其实求割点的算法是为了一次求出一群割点,假如只是验证某个点是不是割点的话,就把这个点堵住,从任意一个点出发开始dfs,假如有点不能被遍历到则堵住了割点(连通图)。然后往返搞两次,被两次都经过的点就是两边割点中间公共的那部分,直接减去就可以了。所以正解是两次堵点搜索。
还是总结一下我的算法,虽然蠢但它是对的。验证两个都是割点之后,堵住y,从x开始搜索vis2,记录所有能够到y的点的点集V(注意是点集!WA了几次了!),然后堵住x和y从这些点集搜索vis3,得到的就是中间部分。剩下的也是堵住x,然后从x开始走vis3,走到的就是集合X,同理整出Y。
struct edge {
int nxt, mark;
} pre[4000010];
int n, m, idx, cnt, tot;
int head[2000010], DFN[2000010], LOW[2000010];
bool cut[2000010];
void add(int x, int y) {
pre[++cnt].nxt = y;
pre[cnt].mark = head[x];
head[x] = cnt;
}
void tarjan(int u, int fa) {
DFN[u] = LOW[u] = ++idx;
int child = 0;
for(int i = head[u]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(!DFN[nx]) {
tarjan(nx, fa);
LOW[u] = min(LOW[u], LOW[nx]);
if(LOW[nx] >= DFN[u] && u != fa)
cut[u] = 1;
if(u == fa)
child++;
}
LOW[u] = min(LOW[u], DFN[nx]);
}
if(child >= 2 && u == fa)
cut[u] = 1;
}
bool vis2[2000010];
bool vis3[2000010];
vector<int> fi;
int x, y;
void dfs2(int u) {
if(vis2[u])
return;
vis2[u] = 1;
for(int i = head[u]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(vis2[nx])
continue;
if(nx == y) {
fi.push_back(u);
continue;
}
dfs2(nx);
}
}
int cnta = 0;
void dfs3(int u) {
vis3[u] = 1;
++cnta;
for(int i = head[u]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(vis3[nx])
continue;
dfs3(nx);
}
}
int cntb = 0;
void dfs4(int u) {
vis3[u] = 1;
++cntb;
for(int i = head[u]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(vis3[nx])
continue;
dfs4(nx);
}
}
void dfs5(int u) {
vis3[u] = 1;
for(int i = head[u]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(vis3[nx])
continue;
dfs5(nx);
}
}
void test_case() {
scanf("%d%d%d%d", &n, &m, &x, &y);
memset(DFN, 0, sizeof(DFN[0]) * (n + 1));
memset(LOW, 0, sizeof(LOW[0]) * (n + 1));
memset(head, 0, sizeof(head[0]) * (n + 1));
memset(cut, 0, sizeof(cut[0]) * (n + 1));
memset(vis2, 0, sizeof(vis2[0]) * (n + 1));
memset(vis3, 0, sizeof(vis3[0]) * (n + 1));
idx = 0, cnt = 0, tot = 0;
for(int i = 0; i <= 2 * m + 10; ++i) {
pre[i].nxt = pre[i].mark = 0;
}
cnt = 0;
for(int i = 1; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
for(int i = 1; i <= n; i++) {
if(DFN[i] == 0)
tarjan(i, i);
//cout << "cut[" << i << "]=" << cut[i] << endl;
}
if(!cut[x] || !cut[y]) {
puts("0");
return;
} else {
fi.clear();
dfs2(x);
//找y向x的第一个点。
//printf("%d\n", fi);
vis3[x] = 1;
vis3[y] = 1;
for(auto &v : fi) {
if(!vis3[v])
dfs5(v);
}
/*for(int i = 1; i <= n; i++)
cout << "vis[" << i << "]=" << vis3[i] << endl;*/
cnta = 0;
for(int i = head[x]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(vis3[nx])
continue;
dfs3(nx);
}
cntb = 0;
for(int i = head[y]; i != 0; i = pre[i].mark) {
int nx = pre[i].nxt;
if(vis3[nx])
continue;
dfs4(nx);
}
printf("%lld\n", 1ll * cnta * cntb);
return;
}
}