Codeforces Round #739 (Div. 3)

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;
 } 
上一篇:错误


下一篇:iOS进阶笔记(一) 对象本质相关源码解读