Codeforces Round #739 (Div. 3)
A - Dislike of Threes
有\(t(1\leq t \leq 100)\)组数据,每组数据给出一个\(k(1\leq k \leq 1000)\),
求出第\(k\)小的正整数满足不被\(3\)整除,数的结尾不是\(3\)。
可以发现,按照上述方式构造的数,大致是线性分布,因此
考虑\(O(k)\)预处理,\(O(1)\)查询。
# include<bits/stdc++.h>
using namespace std;
bool check(int x){
if (x%3==0) return 0;
if (x%10==3) return 0;
return 1;
}
int main()
{
int now=1;
vector<int>a;
while (1) {
if (check(now)) a.push_back(now);
now++;
if (a.size()>1000) break;
}
int t; scanf("%d",&t);
while (t--) {
int x; scanf("%d",&x);
printf("%d\n",a[x-1]);
}
return 0;
}
B - Who‘s Opposite?
有\(t(1\leq t \leq 10^4)\)组数据,每组数据给出\(a,b,c (1\leq a,b,c \leq 10^8)\),
考虑在一个由\(1-n\)顶点按照顶点编号顺时针排列的环上,\(n\)为偶数。
顶点\(a\)和顶点\(b\)构成的直线恰好是对称轴,若顶点\(c\)和顶点\(d\)构成的直线也恰好是对称轴。
求出顶点\(d\)的值,若输入数据错误,不存在\(d\),输出\(-1\)
考虑将所有顶点编号都减去\(1\),所以顶点编号是\(0-(n-1)\)这样可以更方便的使用整除的性质。
即\(a‘ = a-1,b‘=b-1,c‘=c-1,d‘=d-1\)
显然,利用\(a‘,b‘\)可以求出\(n = 2\times |a‘-b‘|\).
而$c‘-d‘ ≡ a‘-b‘ \pmod{n} $
因此\(d‘ = c‘-a‘+b‘ \pmod{n}\) 所以 \(d = d‘+1\)
不符合的条件输出\(-1\),就是 \(a‘,b‘,c‘\)中存在一个,大于等于\(n\),说明输入错误。
# include<bits/stdc++.h>
using namespace std;
int solve(int a,int b,int c) {
int n=abs(a-b)*2;
if (a>=n||b>=n||c>=n) return -1;
return (((c-a)%n+n)%n+b)%n+1;
}
int main()
{
int t; scanf("%d",&t);
while (t--) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
a--;b--;c--;
int ans = solve(a,b,c);
printf("%d\n",ans);
}
return 0;
}
C - Infinity Table
有\(t(1\leq t \leq 100)\)组数据,每组数据给出一个\(k(1\leq k \leq 10^9)\),
求出值\(k\)在下列无限矩阵中的第几行第几列,输出行号和列号。
1 2 5 10 ... 4 3 6 11 ... 9 8 7 12 ... 16 15 14 13 ... ... ... ... ... ...
首先考虑第\(1\)列,发现是完全平方数,\((i,1)\)中存储的值是\(i^2\)且反\(L\)型的边长为\(i\)。
第\(i\)个反\(L\)型中包含的数字有\([(i-1)^2+1,i^2]\),因此\(k\)在第\(\lceil \sqrt{k}\rceil\)个反\(L\)型中。
接下来考虑\(k\)在反\(L\)型中是行号为\(\lceil \sqrt{k}\rceil\)还是列号为\(\lceil \sqrt{k}\rceil\)。
观察\((i,i)\)上的数,其值为\(i^2-i+1\),因此
-
若\(k \leq \lceil \sqrt{k}\rceil^2 -\lceil \sqrt{k}\rceil + 1\),则列号为\(\lceil \sqrt{k}\rceil\)可以求出行号\(k-(\lceil \sqrt{k}\rceil -1)^2\)
答案是\((k-(\lceil \sqrt{k}\rceil -1)^2,\lceil \sqrt{k}\rceil)\)
-
若\(k \geq \lceil \sqrt{k}\rceil^2 -\lceil \sqrt{k}\rceil + 1\),则行号为\(\lceil \sqrt{k}\rceil\)可以求出列号\(\lceil \sqrt{k}\rceil ^2 - k + 1\)
答案是\((\lceil \sqrt{k}\rceil,\lceil \sqrt{k}\rceil ^2 - k + 1)\)
# include <bits/stdc++.h>
using namespace std;
void solve() {
int k; scanf("%d",&k);
int s = ceil(sqrt(k));
if (k<=s*s-s+1) {
printf("%d %d\n",k-(s-1)*(s-1),s);
} else {
printf("%d %d\n",s,s*s-k+1);
}
}
signed main()
{
int t; scanf("%d",&t);
while (t--) {
solve();
}
return 0;
}
D - Make a Power of Two
有\(t(1\leq t \leq 10^4)\)组数据,每组数据给出一个数\(n(1\leq n\leq 10^9)\),
求最小的操作次数,让\(n‘ = 2^k (k \in N)\)
- 删除数\(n\)中的一个数位。
- 在数\(n\)尾部添加任意一个数字。
在数据范围内,有\(2^0,2^1,...,2^{62}\)这\(63\)个\(2\)的幂次,
对于\(n\),变成每一个\(2\)的幂次都计算一遍最下操作次数,再取最小值,就是最终答案了。
下面考虑\(n\)和\(s\),求最小操作次数,让\(n\)变成\(s\)
首先应该在\(n\)中删除一些数字,删除最少的情况就是保留最多的情况。
即\(n\)中的子序列,和\(s\)中从\(1\)开始的连续子串相等并且长度最大的情况,记此长度为\(r\)。
那么在\(n\)中应该删除\(|n| - r\)个元素形成\(n‘\),在\(n‘\)中添加\(|s|-r\)个元素形成\(s\)。
所以,答案就是\(|n|+|s| - 2r\)
\(r\)的计算用双指针可以轻松解决。
时间复杂度为\(O(63\times t\times (|n|+|s|))\)
# include<bits/stdc++.h>
# define int long long
# define pow hhh
# define inf (1e9)
using namespace std;
int pow[105],a[105],b[105],f[105][105];
int work(int x,int y) {
a[0]=b[0]=0;
while (x) { a[++a[0]]=x%10; x/=10;}
while (y) { b[++b[0]]=y%10; y/=10;}
reverse(a+1,a+1+a[0]); reverse(b+1,b+1+b[0]);
int i=1,j=1;
while (i<=a[0]&&j<=b[0]) {
if (a[i]==b[j]) i++,j++;
else i++;
}
int res=j-1;
return a[0]+b[0]-2*res;
}
signed main() {
int t; scanf("%lld",&t);
pow[0]=1;
for (int i=1;i<63;i++) pow[i]=pow[i-1]*2;
while (t--) {
int n; scanf("%lld",&n);
int ans = 1e9;
for (int i=0;i<63;i++) {
ans=min(ans,work(n,pow[i]));
}
printf("%lld\n",ans);
}
return 0;
}
E - Polycarp and String Transformation
有\(T\)组数据,\(1 \leq T \leq 10^4\)
初始有字符串\(s\),\(t\)为空。连续依次操作,\(t +=s\),\(s\)删除字符\(c\),前提是\(c \in s_i\)直到\(s = ""\)
给出若干次操作后的结果\(t\),求出原字符串\(s\),和依次删除字符的顺序\(c\)
保证所有输入字符的总数不会超过\(5\times 10^5\)
显然,\(t\)串倒序出现字母种类的顺序就是\(s\)串删除字母的顺序。
这是基于若当前删除一个字母,那么这个串后面的拼接到\(t\)的串上都不会出现这个字母了。
因此,很容易的就获得了,依次删除字符的顺序。
设\(t\)的长度为\(n\),有\(m\)种不同的字符,设\(cnt[c]\)表示字符\(c\)在\(t\)字符串中总共出现的次数,
那么初始的\(s\)经过且只经过\(m\)次操作,
基于每一次只删除一个字符的原理,我们可以计算出一个字符在原字符串\(s\)中出现的次数。
例如,最后一个被删除的字符\(c_m\),在原字符串\(s\)中出现的次数为\(\frac{cnt[c_m]}{m}\)
倒数第\(2\)个被删除的字符\(c_{m-1}\),在原字符串\(s\)中出现的次数为\(\frac{cnt[c_{m-1}]}{m-1}\)
因此\(c_i (1\leq i \leq w)\)在原字符串中出现的次数为\(\frac{cnt[c_i]}{i}\)次,那么原字符串长度为\(|s| = \sum_{i=1}^{m} \frac{cnt[c_i]}{i}\)
这样,从\(t[1]\)开始往后长度为\(|s|\) 的字符串,是唯一有可能满足情况的答案。
返过去模拟验证,可以排除\(-1\)的情况,剩下的就是正确的答案。
时间复杂度\(O(\sum |t|)\)
include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int cnt[26];
int used[26];
char ans[N];
int main() {
int t; scanf("%d",&t);
while (t--) {
string s; cin>>s;
int n=s.length(),tot=0;
for (int i=0;i<26;i++) {
cnt[i]=0; used[i]=0;
}
for (int i=n-1;i>=0;i--) {
if (!used[s[i]-‘a‘]) used[s[i]-‘a‘]=++tot;
cnt[s[i]-‘a‘]++;
}
for (int i=0;i<26;i++) if (used[i]) {
ans[used[i]]=i+‘a‘;
}
int res = 0;
for (int i=1;i<=tot;i++) res+=cnt[ans[i]-‘a‘]/(tot-i+1);
reverse(ans+1,ans+1+tot);
string s1 = "",s2 = "";
for (int i=0;i<res;i++) s1+=s[i];
int now=1;
while (s1!="") {
s2+=s1;
string w="";
for (int i=0;i<s1.length();i++) if (ans[now]!=s1[i]) {
w+=s1[i];
}
s1=w;
now++;
}
if (s2 == s) {
for (int i=0;i<res;i++) printf("%c",s[i]);
putchar(‘ ‘);
for (int i=1;i<=tot;i++) printf("%c",ans[i]);
puts("");
} else puts("-1");
}
return 0;
}
F - Nearest Beautiful Number
有\(t(1\leq t \leq 10^4)\)组数据,每组数据给出\(2\)个数\(n,k (1\leq n\leq 10^9,1 \leq k\leq 10)\),
求出最小的\(x\geq n\),满足\(x\)由最多\(k\)种数字构成。
方法一:动态规划。
\(f[i][mask][0/1]\)表示当前从最高位到最低位依次访问,当前到低\(i\)位,取数集合为\(mask\),\(0\)当前数前缀和目标前缀相等,\(1\)当前数前缀已经大于目标前缀了。
\(g[i][mask][0/1]\)表示当前从最高位到最低位依次访问,当前到低\(i\)位,取数集合为\(mask\),\(0\)当前数前缀和目标前缀相等,\(1\)当前数前缀已经大于目标前缀了。此时的最小前缀值。
初始值:\(f[0][0][0] = 1 ,g[0][0][0] = 0\) otherwise : \(f[i][mask][0/1]=0,g[i][mask][0/1]=inf\)
转移:考虑\(i\)向\(i+1\)转移。
- $f[i+1][mask|(1<<a[i+1])][0]=1; $ (条件:\(f[i][mask][0] =1\))同时更新\(g\)
- $f[i+1][mask|(1<<j)][1]=1; $ (条件:\(f[i][mask][0] =1\)并且\(a[i+1]+1\leq j\leq 9\))同时更新\(g\)
- \(f[i+1][mask|(1<<j)][1]=1;\) (条件:\(f[i][mask][1] =1 , 0 \leq j \leq 9\))同时更新\(g\)
答案为 \(\min \limits_{1\leq count(mask)\leq k }\ g[n][mask][0/1]\)
时间复杂度为\(O(t\times 2^k\times k\times |n|)\) 数量级为\(10^9\),会\(TLE\)
# include <bits/stdc++.h>
# define int long long
# define inf (1e18)
using namespace std;
bool f[10][1024][2];
int g[10][1024][2],a[15];
char s[15];
inline int read()
{
int x=0;char c=getchar();
while (c<‘0‘||c>‘9‘) c=getchar();
while (c>=‘0‘&&c<=‘9‘) x=x*10+c-‘0‘,c=getchar();
return x;
}
void write(int x) {
if (x>9) write(x/10);
putchar(x%10+‘0‘);
}
int count(int x) {
int res=0;
for (;x;x-=x&(-x)) res++;
return res;
}
signed main() {
int t=read();
while (t--) {
scanf("%s",s+1); int k=read(),n=strlen(s+1);
for (int i=1;i<=n;i++) a[i]=s[i]-‘0‘;
for (int i=1;i<=n;i++)
for (int mask=0;mask<(1<<10);mask++)
for (int j=0;j<=1;j++) {
f[i][mask][j]=0;
g[i][mask][j]=inf;
}
f[0][0][0]=1; g[0][0][0]=0;
for (int i=0;i<n;i++)
for (int mask=0;mask<(1<<10);mask++) {
if (count(mask)>k) continue;
if (f[i][mask][0]) {
f[i+1][mask|(1<<a[i+1])][0]=1;
g[i+1][mask|(1<<a[i+1])][0]=min(g[i+1][mask|(1<<a[i+1])][0],g[i][mask][0]*10+a[i+1]);
for (int j=a[i+1]+1;j<10;j++) {
f[i+1][mask|(1<<j)][1]=1;
g[i+1][mask|(1<<j)][1]=min(g[i+1][mask|(1<<j)][1],g[i][mask][0]*10+j);
}
}
if (f[i][mask][1]) {
for (int j=0;j<10;j++) {
f[i+1][mask|(1<<j)][1]=1;
g[i+1][mask|(1<<j)][1]=min(g[i+1][mask|(1<<j)][1],g[i][mask][1]*10+j);
}
}
}
int ans = inf;
for (int mask=0;mask<(1<<10);mask++) {
if (count(mask)>k) continue;
if (f[n][mask][0]) ans=min(ans,g[n][mask][0]);
if (f[n][mask][1]) ans=min(ans,g[n][mask][1]);
}
write(ans); putchar(‘\n‘);
}
return 0;
}
方法二:贪心。
考虑\(O(2^k)\)枚举,需要的元素总数,然后尝试用这些元素去填充大于等于\(n\)的\(x\),然后取最小的\(x\)。
若当前选择的数可以和\(n\)的前缀匹配,那么就按照\(n\)的前缀填写\(x\)的对应值,
否则,
- 若当前位存在至少一个比\(n\)的当前位要大的数字,那么选取这些数字里最小的那个填充,剩余的用需要选择的元素中最小值填充。
- 若当前位不存在至少一个比\(n\)的当前位要大的数字,那么选取最近的一位存在至少一个比\(n\)的对应位要大的数字,将该位改为这些数字里最小的那个填充,剩余的用需要选择的元素中最小值填充。
时间复杂度\(O(t\times 2^k\times |n|)\) 数量级为\(10^8\),不一定\(TLE\)
# include <bits/stdc++.h>
# define int long long
# define inf (1e18)
# define lowbit(x) (x&(-x))
using namespace std;
int a[10];
int count(int x) {
int res=0;
for (;x;x-=lowbit(x)) res++;
return res;
}
signed main()
{
int t; scanf("%lld",&t);
while (t--) {
int n,k; scanf("%lld%lld",&n,&k); a[0]=0;
while (n) { a[++a[0]]=n%10; n/=10;}
reverse(a+1,a+1+a[0]);
int ans = inf;
for (int mask=1;mask<(1<<10);mask++)
if (count(mask)<=k) {
int res=0;
for (int i=1;i<=a[0];i++) {
if (mask&(1<<a[i])) res=res*10+a[i];
else {
int pos=-1,mn,mx;
for (int j=a[i]+1;j<10;j++) if (mask&(1<<j)) {
pos=j; break;
}
for (int j=0;j<10;j++) if (mask&(1<<j)) {
mn=j; break;
}
for (int j=9;j>=0;j--) if (mask&(1<<j)) {
mx=j; break;
}
if (pos==-1) {
int w=-1;
for (int j=i;j>=1;j--) if (a[j]<mx) {
w=j; break;
}
if (w==-1) {
res=inf; break;
}
int nn;
for (int j=a[w]+1;j<10;j++) if (mask&(1<<j)) {
nn=j; break;
}
res=0;
for (int j=1;j<=w-1;j++) res=res*10+a[j];
res=res*10+nn;
for (int j=w+1;j<=a[0];j++) res=res*10+mn;
} else {
res=res*10+pos;
for (int j=i+1;j<=a[0];j++) res=res*10+mn;
}
break;
}
}
ans=min(ans,res);
}
printf("%lld\n",ans);
}
return 0;
}