Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4)
A. Happy Birthday, Polycarp! (CF 1277 A)
题目大意
问\(1\)到\(n\)中有多少个数是由一个数字组成的数
解题思路
很显然只有\(11,22,33,44,55,66\)等诸如此类的数,则一共有(位数-\(1\))*\(9\)+首位-\(1\)+(\(n\)是否大于以首位数字构成,长度为\(n\)的数字).
神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
void Input(void) {
string s;
cin>>s;
int ans=9*(s.size()-1)+s[0]-'0'-1;
string ss(s.size(),s[0]);
if (ss<=s) ++ans;
cout<<ans<<endl;
}
main(void) {
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int kase;
ios::sync_with_stdio(false);
cin>>kase;
for (int i = 1; i <= kase; i++) {
Input();
}
}
B. Make Them Odd (CF 1277 B)
题目大意
给定一个数列,每次操作选择一个偶数\(c\),把所有与\(c\)相同的数都除以\(2\),重复此类操作,直到所有数都变为奇数,问最小的操作次数。
解题思路
奇数我们可以忽略,单看偶数,如果某个偶数一直除以\(2\)得到了一个已有的偶数,则之后的操作可以一并进行,而这类数它们之间就差\(2\)的次方,于是我们把不断除以\(2\)得到的奇数是同一个的偶数看做一类,而把它们变成奇数的最小操作次数就是这类数中的最大的偶数。
于是我们把每个偶数都除以\(2\),直到变成奇数,对于这每个奇数我们把得到它的最大操作次数记到对答案的贡献里就可以了。
\(a_{i}\)很大,但\(n\)不大,用个\(unordered\_set\)来存,\(unordered\_map\)来记录操作次数即可。
神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
unordered_map<int,int> an;
unordered_set<int> sign;
const int N=2e5+8;
int n,cnt,ans;
void Input(void) {
sign.clear();
an.clear();
cnt=0;
read(n);
for(int u,i=1;i<=n;++i){
read(u);
if ((u&1)==0) sign.insert(u);
}
}
void Solve(void) {
ans=0;
for(auto i:sign){
cnt=0;
int x=i;
while(!(x&1)){
x>>=1;
++cnt;
}
an[x]=MAX(an[x],cnt);
}
for(auto i:an) ans+=i.second;
}
void Output(void) {
write(ans,'\n');
}
main(void) {
int kase;
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
read(kase);
for (int i = 1; i <= kase; i++) {
//printf("Case #%d: ", i);
Input();
Solve();
Output();
}
}
C. As Simple as One and Two (CF 1277 C)
题目大意
给定一个字符串,要求去掉若干个字母,使得该字符串不包含\("one"\)和\("two"\),求最小的去掉字符的数量。
解题思路
发现\(two\)和\(one\)有相同字母\(o\),考虑子串\(twone\),最优的策略就是去掉中间字母\(o\),之后剩余单独的\(two\)和\(one\),最优的策略就是去掉中间的字母,因为如果去掉第一位或第三位的话,则这个子串与左边一位或右边一位可能会重新组成\(two\)或\(one\)
至于匹配用不用\(KMP\)都无所谓……毕竟就\(12\)的常数
神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
char one[4]={"one"};
char two[4]={"two"};
char twone[6]={"twone"};
int nxtone[4],nxttwo[4],nxttwone[6];
const int N=2e5+8;
char s[N];
int len,cnt;
int anspos[N];
void makenext(char qaq[],int nxt[],int n){
nxt[0]=0;
int k=0;
for (int i=1;i<n;i++){
while ((k>0)&&(qaq[k]!=qaq[i])) k=nxt[k-1];
if (qaq[k]==qaq[i]) k++;
nxt[i]=k;
}
}
void kmp(char qaq[],int nxt[],char qwq[],int n){
int q=0;
for (int i=0;i<len;i++){
if (qwq[i]=='@') continue;
while ((qwq[i]!=qaq[q])&&(q>0)) q=nxt[q-1];
if (qwq[i]==qaq[q]) q++;
if (q==n) {
if (n==5) {qwq[i-2]='@'; anspos[++cnt]=i-1;}
else {qwq[i-1]='@'; anspos[++cnt]=i;}
}
}
}
void Input(void) {
cnt=0;
scanf("%s",s);
len=strlen(s);
}
void Solve(void) {
kmp(twone,nxttwone,s,5);
kmp(two,nxttwo,s,3);
kmp(one,nxtone,s,3);
}
void Output(void) {
write(cnt,'\n');
for(int i=1;i<=cnt;++i) printf("%d%c",anspos[i],i==cnt?'\n':' ');
if (cnt==0) puts("");
}
main(void) {
int kase;
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
read(kase);
makenext(one,nxtone,3);
makenext(two,nxttwo,3);
makenext(twone,nxttwone,5);
for (int i = 1; i <= kase; i++) {
//printf("Case #%d: ", i);
Input();
Solve();
Output();
}
}
D. Let's Play the Words? (CF 1277 D)
题目大意
给定若干个互不相同的\(01\)子串,现翻转一些子串,使得这些子串可以排成一行,第一个子串任意,然后前一个子串的末数字和后一个子串的首数字一样,且不能有相同的子串。求最小的翻转次数,且输出任一种对应的方案。无解则输出\(-1\)
解题思路
考虑到只涉及子串的首位和末位,我们按照首位和末位的数字将这些子串分为\(4\)类,即\(00,01,10,11\)串。
无解的情况就是有\(00\)串和\(11\)串但无\(10\)和\(01\)串。
由植树原理知只要\(10\)串和\(01\)串的数量差值小于等于\(1\)即可。
因为\(01\)串翻转就是\(10\)串,那么最小的操作次数就是\(10\)和\(01\)串的数量差的一半下取整。但这里可能会出现翻转后的子串与已有的子串相同。
因为\(00\)串和\(11\)串无需翻转,我们就不用考虑它们。
注意到如果一个\(01\)串翻转后与已有的子串相同,这个已有的子串一定是\(10\)串,且可以和翻转的\(01\)串进行匹配,我们就可以不考虑这对子串了。
神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
const int N=2e5+8;
int num[4],s01[N],s10[N];
int n,l,c0,c1;
string s,ss;
unordered_map<string,pair<int,bool>> qwq;
void Input(void) {
qwq.clear();
num[0]=num[1]=num[2]=num[3]=0;
c0=c1=0;
cin>>n;
for(int i=1;i<=n;++i){
cin>>s;
if (s[0]==s[s.size()-1]) {qwq[s]=make_pair(i,1);continue;}
ss=s;
reverse(s.begin(),s.end());
if (qwq[s].second==1) {qwq[s].second=0;++num[2];++num[3];continue;}
qwq[ss]=make_pair(i,1);
}
}
void Solve(void) {
for(auto v:qwq){
if (v.second.second==1){
const char *ss=v.first.c_str();
l=strlen(ss);
if (l==1) ++num[ss[0]-'0'];
else{
if (ss[0]=='0'&&ss[l-1]=='0') ++num[0];
else if (ss[0]=='0'&&ss[l-1]=='1') ++num[2],s01[++c0]=v.second.first;
else if (ss[0]=='1'&&ss[l-1]=='0') ++num[3],s10[++c1]=v.second.first;
else ++num[1];
}
}
}
if (num[2]==0&&num[3]==0&&num[0]!=0&&num[1]!=0) cout<<"-1"<<endl;
else {
int qwq=ABS(num[2]-num[3]);
qwq/=2;
cout<<qwq<<endl;
if (num[2]>num[3]) for(int i=1;i<=qwq;++i) cout<<s01[i]<<(i==qwq?'\n':' ');
else for(int i=1;i<=qwq;++i) cout<<s10[i]<<(i==qwq?'\n':' ');
if (qwq==0) cout<<'\n';
}
}
void Output(void) {}
main(void) {
ios::sync_with_stdio(false);
int kase;
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
cin>>kase;
for (int i = 1; i <= kase; i++) {
//printf("Case #%d: ", i);
Input();
Solve();
Output();
}
}
E. Two Fairs (CF 1277 E)
题目大意
给定一张\(n\)个点\(m\)条边的无向图,有两个特殊城市\(a,b\),求点对数\((x,y)\),使得从城市\(x\)到城市\(y\)的所有路径中都会经过城市\(a\)和\(b\),其中\(x\)与\(y\)均不等于\(a\)和\(b\)。
解题思路
我们考虑城市\(a\)和\(b\),很容易想到一种情形就是\(a\)和\(b\)把它们之间的点形成了孤岛,这样从\(a\)的另外一边的城市到\(b\)的另外一边的城市就必须经过\(a\)和\(b\)了。而\(a\),\(b\)就成了连接它们的割点。
那么我们就对\(a\)进行\(DFS\),记录\(a\)不经过\(b\)所能到达的城市,有\(cnta\)个,然后再从\(b\)进行\(DFS\),不经过\(a\),如果到达了\(a\)所到达的城市,那么这个城市就是孤岛上的城市,记有\(d\)个,否则就是\(a\)所到达不到,即\(b\)的另一边的城市,记有\(cntb\)个,而a的另一边的城市有\(cnta-d\)个。
那么答案就是\((cnta-d)*cntb\).
神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
const int N=2e5+8;
vector<int> edge[N];
int n,m,a,b;
int sign[N];
long long cnta,cntb;
void Input(void) {
read(n);
read(m);
read(a);
read(b);
for(int i=1;i<=n;++i) edge[i].clear();
for(int u,v,i=1;i<=m;++i){
read(u);
read(v);
edge[u].push_back(v);
edge[v].push_back(u);
}
}
void DFSa(int x){
for(auto i:edge[x]){
if (i==b) continue;
if (sign[i]==0){
sign[i]=1;
++cnta;
DFSa(i);
}
}
}
void DFSb(int x){
for(auto i:edge[x]){
if (i==a) continue;
if (sign[i]!=2){
if (sign[i]==1) --cnta;
else ++cntb;
sign[i]=2;
DFSb(i);
continue;
}
}
}
void Solve(void) {
for(int i=1;i<=n;++i) sign[i]=0;
cnta=cntb=0;
sign[a]=1;
DFSa(a);
sign[b]=2;
DFSb(b);
}
void Output(void) {
write(cntb*cnta,'\n');
}
int main(void) {
//ios::sync_with_stdio(false);
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int kase; read(kase);
for (int i = 1; i <= kase; i++) {
//printf("Case #%d: ", i);
Input();
Solve();
Output();
}
return 0;
}
F. Beautiful Rectangle (CF 1277 F)
题目大意
给定\(n\)个数,将它们放在一个\(a*b\)的矩阵格子里,要求放的数最多,且每行和每列中的数都不同。
解题思路
我们先统计每个数出现的次数\(cnt\),并按照出现的次数从大到小排序。
我们尝试去验证是否能构造出\(a*b\)的矩阵,其中\(a\leq b\),对于要填的数,其出现的次数是\(cnt\),有两种情况
- \(cnt\geq a\),这个时候我们只能去\(a\)个去填充矩阵
- \(cnt<a\),这个时候我们可以拿全部去填充矩阵
对于前面\(cnt\geq a\)的每个数都能填充一列,对于后面\(cnt<a\)的数我们用它们的和\(\sum cnt\)除以\(a\)即可得到填充的列的个数
然后判断列数即\(b\)是否满足\(b\geq a\)即可
至于构造,这里采用阶梯型构造,对于\(cnt\geq a\)的数,当构造完底部时就换下一个数,对于\(cnt<a\)的数,用完它再换下一个数。
神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
unordered_map<int,int> cnt;
vector<pair<int,int>> number;
int n,a,b;
const int N=4e5+8;
int sum[N];
void check(int x){
int qwq=0;
for(;qwq<n&&number[qwq].first>=x;++qwq);
qwq+=sum[qwq]/x;
if (qwq>=x)
if (a*b<x*qwq){
a=x;
b=qwq;
}
}
void Input(void) {
read(n);
for(int u,i=1;i<=n;++i) {
read(u);
cnt[u]++;
}
for(auto i:cnt) number.push_back(make_pair(i.second,i.first));
}
void Solve(void) {
sort(number.begin(),number.end(),greater<pair<int,int>>());
int qwq=ceil(sqrt(n));
n=number.size();
sum[n]=0;
for(int i=n-1;i>=0;--i) sum[i]=sum[i+1]+number[i].first;
//for(int i=0;i<n;++i) cout<<sum[i]<<endl;
for(int i=1;i<=qwq;++i) check(i);
}
void Output(void) {
printf("%d\n",a*b);
printf("%d %d\n",a,b);
int qwq=a*b;
int c=0,r=0,qaq=1;
int id=0;
int tmp[a+1][b+1];
int fen=0;
for(;fen<n&&number[fen].first>=a;++fen);
while(qwq--){
tmp[r][c]=number[id].second;
number[id].first--;
c=(c+1)%b;
++r;
if (r>=a) {r=0; c=qaq++; if (id<fen) ++id;}
if (number[id].first==0) ++id;
}
for(int i=0;i<a;++i) for(int j=0;j<b;++j) printf("%d%c",tmp[i][j],(j==(b-1))?'\n':' ');
}
int main(void) {
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
Input();
Solve();
Output();
return 0;
}