\(BunnyOJ\)的测试赛。由\(@{T}\color{red}{sukimaru}\)和\(@{l}\color{red}{igen}\)举办。
\(A\):根号 \(by\) \(ligen\)
给你一个数\(n\),请将\(\sqrt{n}\)化为最简形式,即找到一个最小的\(b\)满足\(a\sqrt{b}=\sqrt{n}\),即\(a^2\cdot b=n\)。本题有多组询问。
数据范围:\(1 \leq T \leq 10^3\) , \(1 \leq n \leq 10^{10}\)
枚举\(a\),再根据\(a\)算出\(b= \dfrac{n}{a*a}\)(向下取整),然后判断是否满足\(a^2\cdot b=n\)即可。
由\(a^2\cdot b=n\)可知\(a\)只需枚举到\(\sqrt n\)。
时间复杂度:\(O(T \sqrt n)\)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
long long n;
long long len;
int main()
{
int t;
scanf("%d",&t);
long long b;
while (t--)
{
scanf("%lld",&n);
len=(long long)sqrt(n);
for (long long i=len;i>=1;i--)
{
b=(long long)(n/(i*i));
if (i*i*b==n)
{
printf("%lld %lld\n",i,b);
break;
}
}
}
return 0;
}
\(\color{#FAFAFA}{ligin毒瘤 卡你horse的int呢}\)
\(B\):\(sum\) \(from\) \(LibreOJ\)
给你\(n\)个数,请你在其中找出一些数,使得它们的和能被\(n\)整除。输出任意一组解(下标&值)即可。
数据范围:\(1 \leq n \leq 1000010\),\(0 \leq a_i \leq 10^{10}\)
将序列前缀和一下。将前缀和后的\(c[]\)数组\(mod\) \(n\),此时如果有\(c_i = 0\),那么\(a_1...a_i\)就是一个\(ans\)。
如果不存在\(c_i = 0\),由于\(k mod n\)只有\(0...n-1\)共\(n\)种取值,那么\(c[]\)中\(n\)个数,必定存在一组\(c[i] = c[j]\)且\(i \leq j\)。此时\(a_{i+1}...aj\)就是一个\(ans\).
算法复杂度:\(O(n)\)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
long long c[1000233],a[1000233];
int book[1000233]={};
int main() {
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) {
scanf("%lld",&c[i]);
a[i]=c[i];
c[i]%=n;
c[i]+=c[i-1];
// printf("%lld ",c[i]);
}
// printf("\n");
for (int i=1;i<=n;i++) {
if (!(c[i]%n)) {
for (int j=1;j<=i;j++) {
printf("%d %lld\n",j,a[j]);
}
return 0;
}
}
for (int i=1;i<=n;i++) {
if (!book[c[i]%n]) {
book[c[i]%n]=i;
continue;
}
for (int j=book[c[i]%n]+1;j<=i;j++) {
printf("%d %lld\n",j,a[j]);
}
return 0;
}
}
\(C\):最小字典序 \(from\) \(51nod\)
给出一个字符串S,你需要从S中挑选一对字符进行一次交换(不可以不交换),并让得到的新字符串字典序最小。输出这个字典序最小的字符串。
数据范围:\(2 \leq |S| \leq 500000\),\(S\)只包含\(a...z\)的小写字母。
考场上yy了一个假的单调栈做法,\(Bunnycxk\)一看觉得不对劲这sb做法怎么\(AC\)了,然后当场改数据把我卡到20。
其实正解很sb。
维护每个字母在原串中出现的次数以及在原串中最后一次出现的位置。
如果原串是不递减的,那么如果原串有两个相同字符,交换这两个字符。否则将最后两个字符交换。
否则,枚举原串的每一位\(s_i\),如果存在尽量小的小于\(s_i\)的字符\(s_j\)且\(i \leq j\),交换\(s_i\),\(s_j\)。
算法复杂度:\(O(n)\)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN 500233
using namespace std;
string s;
int tag[MAXN],sum[MAXN]={};
int main() {
cin>>s;
int len=s.size();
bool flag=0;
for (int i=0;i<len;i++) {
tag[s[i]-'a']=i;
sum[s[i]-'a']++;
if (s[i]<s[i-1]) flag=1;
}
if (!flag) {
for (int i=0;i<len;i++) {
if (sum[s[i]-'a']>1) {
cout<<s;
return 0;
}
}
swap(s[len-1],s[len-2]);
cout<<s;
return 0;
}
for (int i=0;i<len;i++) {
for (int j=0;j<s[i]-'a';j++) {
if (tag[j]>i) {
// printf(":::%d %d\n",s[i],j);
swap(s[i],s[tag[j]]);
// printf("qwq");
cout<<s;
return 0;
}
}
}
}
\(D\):\(262144\) \(from\) \(USACO\)
给你一个长度为\(n\)的正整数数列,每个数在 \(1\)到\(40\)之间。
你可以对序列进行操作,每次操作,你可以把两个相邻且相同的数替换成比他们值大\(1\)的数,例如两个相邻的\(7\)替换成\(8\)。当无法再合并时,游戏结束。游戏目标是使游戏结束时数列中的最大值尽可能大。输出这个最大值。
数据范围:\(n \leq 262144\)
区间\(DP\)..应该叫这个名字。
用\(f[i][j]\)表示以\(j\)为左界,合并后值为\(i\)的右界。
那么我们求\(f[i][j]\),需要两个合并后值为\(i-1\)的相邻的区间。那左边那个区间的左界就是\(j\)。
很容易得到左边那个区间是\(j...f[i-1][j]\),右边区间的左界就是\(f[i-1][j]+1\)(左边区间的右界+1)。
由此可得
\[f[i][j]=f[i-1][f[i-1][j]+1]\]
同时要考虑边界问题以及存在性问题。如果本身没有合法的\(f[i-1][j]\),那么就不能更新\(f[i][j]\)。如果\(f[i-1][j]+1>n\),也无法合并区间。
对于\(f[i][j]!=0\)的\(max{i}\)即为\(ans\)。
算法复杂度:\(O(n)\)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN 262233
using namespace std;
int n;
int f[80][MAXN],a[MAXN];
int main()
{
scanf("%d",&n);
int maxn=-1;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[a[i]][i]=i;
maxn=max(maxn,a[i]);
}
maxn+=37;
int ans=-1;
for (int i=2;i<=maxn;i++)
{
for (int j=1;j<=n;j++)
{
if (f[i-1][j]<n&&f[i-1][j]>0)
{
f[i][j]=f[i-1][f[i-1][j]+1];
}
if (f[i][j]!=0) ans=max(ans,i);
}
}
printf("%d",ans);
return 0;
}
\(E\):\(Bunny\)的计算器 \(by\) \(Tsukimaru\)
定义\(f(x)\)为将\(x\)重复写\(k\)次得到的整数的值。例如\(k=3\),\(f(12)=121212\),\(f(255)=255255255\)。
现在给定\(n\),\(k\),求 \(f(1)+f(2)+...+f(n)\) \(mod\) \(998244353\) 的值。
数据范围:\(n,k \leq 10^9\)
数学题。
考虑\(k=3\),\(f(255)=255255255\)。此时,\(f(255)\)可以写成\(100100100*255\)。
考虑\(100100100\),可以写成\(100+100000+100000000\)。其实这是一个公比为\(1000\),首项为\(100\)的等比数列的和。在\(k\)很大的情况下可以用快速幂套公式处理等比数列求和。公式为baidu.com
同样是\(k=3\)的情况下,对于不同的\(n\),等比数列的首项和公差都会不同。
由于我们要求的是\(f(1)\)至\(f(n)\)的和,那么我们可以分多段处理。我们知道,对于位数相同的一些\(n\),上面谈到的对于其的等比数列是相同的。比如\(n=111\),我们可以处理\(f(1)+...+f(9)\),\(f(10)...f(99)\),\(f(100)...f(111)\)。
例如\(k=3\),求\(f(10)...f(99)\),即求\(101010*(10+11+...+98+99)\)。求\((10+...+99)\)可以使用等差数列求和。
解法:等差数列求和+等比数列求和最后套个快速幂。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
int n,k;
#define mod 998244353
long long power(long long a,long long b)
{
long long ans=1,base=a;
while (b>0)
{
if (b&1)
{
ans*=base;
ans%=mod;
}
base*=base;
base%=mod;
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&k);
int len=0,tag=n;
while (tag>=10)
{
tag/=10;
len++;
}
len++;
long long sk,num;
long long ans=0;
for (int i=1;i<len;i++)
{
sk=(1-power(power(10,i),k))%mod*power(1-power(10,i),mod-2)%mod;
num=(power(10,i-1)%mod+power(10,i)%mod-1)*(power(10,i)%mod-power(10,i-1)%mod)%mod*499122177%mod;
ans=(ans+(sk*num)%mod)%mod;
}
long long l=power(10,len-1),r=n;
sk=(1-power(power(10,len),k))%mod*power(1-power(10,len),mod-2)%mod;
num=(l+r)%mod*(r-l+1)%mod*499122177%mod;
ans=(ans+(sk*num)%mod)%mod;
printf("%lld",ans);
}
\(F\):\(colorful\) \(tree\) \(by\) \(ATcoder\) \(Beginner\) \(Contest\)
有一棵\(n\)个节点的树,每个节点编号为\(1\)到\(n\) 。树上的第\(i\)条边连接点\(a_i\)和\(b_i\)颜色为\(c_i\),长度为\(d_i\) 。在这里, \(c_i\)是\(1\)到\(n-1\)之间的整数。
给出\(Q\)个询问,第\(j\)个询问给出 ,表示询问:如果将颜色为\(x_j\)的边长度全部改为\(y_j\) ,请你计算点\(u_j\)到点\(v_j\)的路径长度。
所有询问互不影响。
数据范围:\(2 \leq n,q \leq 10^5\),$$