ARC172 - sol
A - Chocolate
Ms. AtCoder has decided to distribute chocolates to \(N\) friends on Valentine's Day. For the \(i\)-th friend \((1 \leq i \leq N)\), she wants to give a square chocolate bar of size \(2^{A_i} \times 2^{A_i}\).
She has procured a rectangular chocolate bar of size \(H \times W\). It is partitioned by lines into a grid of \(H\) rows and \(W\) columns, each cell being a \(1 \times 1\) square.
Determine whether it is possible to divide the chocolate bar along the lines into several pieces to obtain all the chocolate bars for her friends. It is fine to have leftover pieces.
\(1 \le H,W \le 10^9,1 \le N \le 1000,1 \le A_i \le 25\)。
赛时愣了半天不敢写,感觉非常抽象,小细节错了还交了一发罚时。
感觉首先需要考虑这里的 \(2^{A_i}\) 的用意,不然为什么不是任意一个整数呢?
容易发现,对于一个边长为 \(2\) 的幂次的正方形,你用这样的方块去填,样都填的满。
具体来说,对于每一个这样的正方形,只要你填进去的面积 小于等于 正方形的面积,你就不需要关心怎么填的问题了,
因为一定有一种方案是可以放下的,这运用到了 \(2\) 的幂的性质,可以手玩一下。
于是对于这个 \(H \times W\) 的长方形,我们尝试把它分成这样的边长 \(2^x\) 的正方形。
显然正方形越大越好,(因为可以放下更大的方块)。
于是我们以一边为基准,以它的二进制为例不断往下分就可以了,容易发现这样一定更优。
这样的时间复杂度是 \(\mathcal O(\log H\log W \times 25)\) 的。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const int N=1e6+5;
int n,H,W,c[30];
ll S=0;
void calc(int x,ll Sum){
if(!n) return ;
for(int i=x;i>=0;i--){
ll nw=(1ll<<i)*(1ll<<i);
ll tmp=min(1ll*c[i],Sum/nw);
Sum-=1ll*nw*tmp,c[i]-=tmp,n-=tmp;
if(!Sum) break;
}
}
void sol(){
cin>>H>>W>>n;
for(int i=1,x;i<=n;i++) cin>>x,c[x]++,S+=1ll*(1ll<<x)*(1ll<<x);
if(H>W) swap(H,W);
for(int i=29;i>=0;i--){
int len=(1ll<<i),w=W;
if(len<=H){
calc(i,1ll*len*len*(w/len));
w-=1ll*(w/len)*len;
for(int j=i;j>=0;j--)
if(w>=(1ll<<j)) calc(j,1ll*len*(1ll<<j)),w-=(1ll<<j);
H-=len;
}
}
if(!n) cout<<"Yes\n";
else cout<<"No\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
sol();
return 0;
}
B - AtCoder Language
The AtCoder language has \(L\) different characters. How many \(N\)-character strings \(s\) consisting of characters in the AtCoder language satisfy the following condition? Find the count modulo \(998244353\).
- All \(K\)-character subsequences of the string \(s\) are different. More precisely, there are \(_N\mathrm{C}_K\) ways to obtain a \(K\)-character string by extracting \(K\) characters from the string \(s\) and concatenating them without changing the order, and all of these ways must generate different strings.
What is \(_N\mathrm{C}_K\)?\(_N\mathrm{C}_K\) refers to the total number of ways to choose \(K\) from \(N\) items. More precisely, \(_N\mathrm{C}_K\) is the value of \(N!\) divided by \(K! \times (N-K)!\).
\(1 \le K \lt N \le 500000,1 \le L \le 10^9\)。
场上小丑一样把自己绕到死胡同里面去了。
首先容易发现对于同一个字母,两次相邻的位置中间至少有 \(n-k\) 个字符。于是我们做完了。
现在有两种思路:对于每一个位置的字符,考虑它下一个的位置/考虑转移到它有多少种方案。
如果你选择第二种,你就赢了。
容易发现,对于第一个字符,它有 \(L\) 种选法;
而对于第二个,它有 \(L-1\) 种选法(因为不能选第一个);
$\dots $
发现对于第 \(i\) 个字符,它不能选的只是在它前面 \(n-k\) 个中出现的,而这显然是不重的,
所以第 \(i\) 个字符的选法是 \(L-min(i-1,n-k)\)。
于是就做完了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const int N=1e6+5,mod=998244353;
int n,k,l,ans=1;
void sol(){
cin>>n>>k>>l;
if(l<n-k) return cout<<"0",void();
for(int i=1;i<=n;i++){
if(i<=n-k) ans=1ll*ans*(l-i+1)%mod;
else ans=1ll*ans*(l-n+k)%mod;
}
cout<<ans;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
sol();
return 0;
}
C - Election
This year's AtCoder mayoral election has two candidates, A and B, and \(N\) voters have cast their votes. The voters are assigned numbers from \(1\) to \(N\), and voter \(i\) \((1 \leq i \leq N)\) voted for candidate \(c_i\).
Now, the votes will be counted. As each vote is counted, the provisional result will be announced as one of the following three outcomes:
- Result A: Currently, candidate A has more votes.
- Result B: Currently, candidate B has more votes.
- Result C: Currently, candidates A and B have the same number of votes.
There is a rule regarding the order of vote counting: votes from all voters except voter \(1\) must be counted in ascending order of their voter numbers. (The vote from voter \(1\) may be counted at any time.)
How many different sequences of provisional results could be announced?
What is a sequence of provisional results?Let \(s_i\) be the result (
A
,B
, orC
) reported when the \(i\)-th vote \((1 \leq i \leq N)\) is counted. The sequence of provisional results refers to the string \(s_1 s_2 \dots s_N\).\(1 \le N \le 1000000\)。
个人认为难度低于 B,你只需要简单分讨即可。
首先,容易发现只有 \(n\) 种可能的情况,那么我们不是只需要去讨论那 \(n\) 种情况就可以了?
那么现在我们这样来思考,当 \(1\) 从 \(x\) 的左边移动到右边在怎么情况下可以对答案有贡献。
(容易发现,相同的这种情况只会出现在相邻的序列中,指 \(1\) 从一个数的左边移动到右边)
画一下图分讨一下就可以知道了,只要我们记录一个前缀和,(AB 个数之差),
它的绝对值 \(\le 1\),就有贡献,于是就做完了,线性。
#include <bits/stdc++.h>
using namespace std;
int n,sum=0,ans=1;
char c[1000005];
int main(){
scanf("%d%s",&n,c+1);
for(int i=2;i<=n;i++){
if(c[i]!=c[1]&&abs(sum)<=1) ++ans;
sum+=(c[i]=='A'?1:-1);
}
cout<<ans;
return 0;
}
D - Distance Ranking
Place \(N\) points \(p_1, p_2, \dots, p_N\) in an \(N\)-dimensional space to satisfy the following conditions:
Condition 1 The coordinates of the points consist of integers between \(0\) and \(10^8\), inclusive.
Condition 2 For \((A_1, B_1), (A_2, B_2), \dots, (A_{N(N-1)/2}, B_{N(N-1)/2})\) specified as input, \(d(p_{A_1}, p_{B_1}) \lt d(p_{A_2}, p_{B_2}) \lt \dots \lt d(p_{A_{N(N-1)/2}}, p_{B_{N(N-1)/2}})\) must hold. Here, \(d(x, y)\) denotes the Euclidean distance between points \(x\) and \(y\).
It can be proved that a solution exists under the constraints of the problem. If multiple solutions exist, just print one of them.
What is Euclidean distance? The Euclidean distance between points \(x\) and \(y\) in an \(n\)-dimensional space, with coordinates \((x_1, x_2, \dots, x_n)\) for \(x\) and \((y_1, y_2, \dots, y_n)\) for \(y\), is calculated as \(\sqrt{(x_1-y_1)^2 + (x_2-y_2)^2 + \dots + (x_n-y_n)^2}\).
\(1 \le n \le 20\)。
好题!
首先,\(\lt\) 是不好计算的,所以我们先把它转化成 \(=\)。
容易发现有一种很明显的构造方式:
于是我们尝试把它转化成 \(\lt\) 的连接,这个其实只需要在原基础上面加上很小很小的数 \(\epsilon\) 就可以实现,
(这里我们忽略整数这个条件,因为最后乘上 \(10^8\) 就可以了)。
而由于这个 \(\epsilon\) 非常小,所以之后的运算中,它的平方项都可以被忽略不计。(类似于求极限)
现在,我们得到的点是:
那么我们再对 \(d(x,y)\) 表示,直接化简并去掉平方项就可以得到:
于是我们发现影响这个的只是后面的 \(\epsilon_{x,y}\),
那么一种构造方式就非常明显了,我们直接让一个为 \(0\),另一个按照越在后面输入的越小即可,
具体来说,对于第 \(i\) 组输入的 \(x,y\),我们让 \(\epsilon_{x,y}=n-i+1\) 即可(这里乘上了一个 \(10^8\))。
#include <bits/stdc++.h>
using namespace std;
int n,a[21][21],m;
int main(){
scanf("%d",&n);m=n*(n-1)/2;
for(int i=1;i<=n;i++) a[i][i]=1e8;
for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),a[x][y]=m-i+1;
for(int i=1;i<=n;i++,puts("")) for(int j=1;j<=n;j++) printf("%d ",a[i][j]);
return 0;
}
E - Last 9 Digits
Determine whether there is a positive integer \(n\) such that the remainder of \(n^n\) divided by \(10^9\) is \(X\). If it exists, find the smallest such \(n\).
You will be given \(Q\) test cases to solve.
\(1 \leq Q \leq 10000,1 \leq X \leq 10^9 - 1\),\(X\) is neither a multiple of \(2\) nor a multiple of \(5\).
告诉我们要勇于猜结论(在你不会证明的时候)。
首先还是手玩,猜结论:对于每一个 \(X\),有且仅有一个数满足条件(在 \(10^9\) 以内)。
而我们容易发现模数是较大的,所以我们考虑减小模数,从小的模数推过来,同时完成证明。
以下的规律从 \(10^2\) 才开始有用,后面会进行阐释。
首先对于 \(mod=10^2\) 的情况,我们发现确实对于每一个满足条件的 \(X\) 都有且仅有一个答案,
假设现在的答案是 \(n\),满足 \(n^n \equiv X \pmod {10^2}\),
我们猜测(打表之类的发现)当模数变成 \(10^3\) 时,满足条件的是 \(n+100t\) 中的一个,其中 \(t \in \{0,1,2,3,4,5,6,7,8,9 \}\)。
于是就进行一些推式子:(省略 \(\pmod {1000}\))
根据二项式定理。
由于后面的每一项都有 \((100t)^2\),也就是 \(10000\) 可以被整除掉。
发现 \(n^{100} \equiv 1 \pmod {1000}\),这是由于欧拉定理:
\(n^{\phi(8)} \equiv n^4 \equiv 1 \pmod 8\),那么 \(n^{100} \equiv 1 \pmod 8\);(由于 \(\phi(2)=1\) 所以对于 \(mod=10\) 是不成立的)
\(n^{\phi(125)} \equiv n^{100} \equiv 1 \pmod{125}\)。
而综合这两个条件,一定满足 \(n^n \equiv 1 \pmod {1000}\)。
而容易发现这个式子 \(n^n + n^n \times 100t \pmod {1000}\) 的最后两位一定和 \(n^n \pmod {100}\) 的最后两位是相同的,
所以这里的 \(X\) 只会多出前面的一位,是满足条件的。
于是乎,从 \(100 \to 1000\),这里满足条件的 \(n\) 只有可能是 \(n+100t\),这样转移就可以了。
而这样推出来之后,每一个数有且仅对应一个也就很容易证明了。
那么代码就是直接模拟即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n,ans=0;
int qpow(int a,int H){
int res=1,b=a;a=a%H;
while(b){if(b&1) res=1ll*res*a%H;a=1ll*a*a%H;b>>=1;}
return res;
}
void sol(){
scanf("%d",&n);
for(int i=1;i<100;i++) if(qpow(i,100)==n%100){ans=i;break;}
for(int i=2,t=100,P=1000;i<9;i++,t*=10,P*=10)
for(int j=0;j<10;j++) if(qpow(ans+j*t,P)==n%P){ans+=j*t;break;}
printf("%d\n",ans);
}
int main(){
int _;scanf("%d",&_);
while(_--) sol();
return 0;
}
F - Walking
The Kingdom of AtCoder has \(2N\) intersections labeled with numbers from \(1\) to \(2N\). Additionally, there are three types of one-way roads in the kingdom:
- Type A: For each \(2 \leq i \leq N\), there is a road from intersection \(2i-3\) to intersection \(2i-1\).
- Type B: For each \(2 \leq i \leq N\), there is a road from intersection \(2i-2\) to intersection \(2i\).
- Type C: For each \(1 \leq i \leq N\), there is a road connecting intersection \(2i-1\) and intersection \(2i\) with direction \(s_i\).
Here, \(s_i\) is
X
orY
, where directionX
means the road goes from intersection \(2i-1\) to intersection \(2i\), and directionY
means it goes from intersection \(2i\) to intersection \(2i-1\).Takahashi wants to walk several times so that for each \(1 \leq i \leq N\), the direction of Type-C road connecting intersections \(2i-1\) and \(2i\) will be \(t_i\).
What is a walk?
Start at any intersection and repeat the following action zero or more times:
- If it is possible to proceed to a Type-C road from the current intersection, walk along the Type-C road to the next intersection.
- Otherwise, if it is possible to proceed to a Type-A or B road from the current intersection, walk along that road to the next intersection.
Then, reverse the directions of all Type-C roads that were passed.
Calculate the minimum number of walks needed to achieve the goal and provide one method to achieve the goal in the minimum number of walks. Under the constraints of this problem, it can be proved that the goal can be achieved in a finite number of walks.
\(1 \le N \le 4000\)。
跟着提示做到只差一步,那一步还是因为没有退一步,把自己绕进去了。
首先是大量的实验,也就是手玩样例。
我们把计数和偶数分成两排,然后去模拟走的过程就会发现:
对于每一段连续的 \(X\) 或 \(Y\) 我们都会把第一个取反。
略加思考我们发现其实输出答案的其实位置和实际上区间的起始位置是有一定关系的,
所以可以忽略 Walk 这种抽象东西,将每次操作转化到一个序列的区间操作。
和上面说的一样,每次对 \([L,R]\) 的区间删除最后一个,并在 \(L\) 前面加入一个数。
那么这个就类似于将两个字符串进行匹配,要么加入一个 \(T\) 中的元素,要么删除一个 \(S\) 中的元素。
分析一下可以知道,记加入为 \(1\),删除为 \(-1\),那么前缀和一定 \(\ge 0\)。
这样就变成了一个字符串匹配问题,我们可以直接 dp 解决,设 \(f_{i,j}\) 表示 \(S\) 中匹配了 \(i\),\(T\) 中匹配了 \(j\) 的序列长度(就是图中所用的列数),这里为满足条件,只用考虑 \(i \le j\) 的。
那么转移是非常简单的:
当然会需要满足一些条件,这是简单的。
于是 dp 完之后我们就可以得到答案了,即 \(f_{n,n}-n\)(这是简单的)。
实现时再往前面找一次就可以得到那些地方插入,那些地方删除了,
于是直接输出就好了。
注意输出的时候,还会有一些细节,我们从小到大遍历数组,
第 \(i\) 个加入对于第 \(i\) 个删除,这里可能还需要仔细思考一下,
因为当输出第 \(i\) 组时,前 \(i-1\) 都已经操作完了,所以开始的坐标是要看 \(T\) 中的,反之结束要看 \(S\) 中的下标。
这样就做完了,时间复杂度 \(\mathcal O(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=4005;
int n,f[N][N],a[N],b[N],ca=0,cb=0;
string S,T;
int main(){
cin>>n>>S>>T;
memset(f,0x3f,sizeof(f)),f[0][0]=0;
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++){
if(i>0) f[i][j]=min(f[i][j],f[i-1][j]+1);
if(j>0) f[i][j]=min(f[i][j],f[i][j-1]+1);
if(i>0&&j>0&&S[i-1]==T[j-1]) f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
printf("%d\n",f[n][n]-n);
int x=n,y=n;
while(x||y){
if(x>0&&f[x][y]==f[x-1][y]+1) --x,a[++ca]=x;
else if(y>0&&f[x][y]==f[x][y-1]+1) --y,b[++cb]=y;
else --x,--y;
}
for(int i=ca;i>0;i--) printf("%d %d\n",2*b[i]+(T[b[i]]=='X'?2:1),2*a[i]+(S[a[i]]=='X'?2:1));
return 0;
}
Conclusion
第一次打 ARC,场上的发挥还是非常烂,我猜结论的水平还有待提高啊。
老是把自己搞到思维的误区里面就出不来了,这一点需要改进,有些时候退一步答案就出来了。
手玩是非常重要的!!!
- 思考时一定要注意有多少种不同的思路,很多时候 计数问题 也有填表法和刷表法两种思路,想不通就退出来换个方向!(B)
- 对于 \(\lt\) 连接的构造题,我们可以尝试先在 \(=\) 上找到合适的解,再进行微调。(D)
- 抽象问题,尝试从手玩中找到规律或者方法。(F)
- 对于模数较大的问题我们可以尝试从小推到大,用类似于归纳法的方法证明结论。(E)