AtCoder Regular Contest 127
A - Leading 1s
签到,常用的计数方法,枚举至少有几个前导1,符合条件的个数算1的贡献即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=18;
ll n,p0[N],p1[N];
int main() {
p0[0]=1;
for(int i=1;i<N;i++) p0[i]=p0[i-1]*10;
p1[1]=1;
for(int i=2;i<N;i++) p1[i]=p1[i-1]*10+1;
cin>>n;
ll ans=0;
for(int x=1;x<N && p1[x]<=n;x++){
for(int y=0;y<N && p1[x]*p0[y]<=n;y++){
ans+=min(p0[y],n-(p1[x]*p0[y])+1);
}
}
cout<<ans<<endl;
return 0;
}
B - Ternary Strings
因为有字典序的要求,故把每个串当成一个三进制数考虑,那么就是要求一个最大值最小的合法构造。而这个最大值肯定在开头为2的那一组,并且至少是\(2\times 3^{L-1}+N-1\),即直接把开头为2的N个数的后面填上\(0,...,N-1\)。然后发现这样是可以补成合法的,即对填的每个位置的数,在对应的开头为0/1的位置进行一轮置换。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,L;
char ans[N][20];
int main() {
cin>>n>>L;
int m=n*3;
for(int i=0;i<m;i++) ans[i][0]='0'+i/n;
for(int i=0;i<n;i++){
int x=i;
for(int j=L-1;j;j--){
int t=x%3;
ans[i+n+n][j]='0'+t;
ans[i+n][j]='0'+(t+1)%3;
ans[i][j]='0'+(t+2)%3;
x/=3;
}
}
for(int i=0;i<m;i++) printf("%s\n",ans[i]);
return 0;
}
C - Binary Strings
考虑按照字典序从小到大枚举,发现是一个二叉树的结构;所以沿着二叉树走,每次判断停下或者往左/右子树走,并维护是当前子树内的第几个,往左-1,往右-当前位,效率经过分析应该是线性的。
然后原本的代码里用了n次strlen(a),原本听说不要用还以为只是常数大,没想到是\(O(n)\)的。没发现的时候怀疑人生了半天,以为效率分析错了,但改成线段树还是TLE,自闭了很久。。。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
void print(int* a,int m){
for(int i=0;i<m;i++) putchar('0'+a[i]);
puts("");
}
int n,a[N],ans[N],cnt;
void sub(){
int q=0;
while(!a[q] && q<n) q++;
for(int i=0;i<q;i++) a[i]=1,cnt++;
a[q]=0,cnt--;
}
bool chk(){
for(int i=0;i<n;i++) if(a[i]) return 0;
return 1;
}
char b[N];
int main() {
//freopen("1.in","r",stdin);
// puts("!");
cin>>n;
scanf("%s",b);
int m=strlen(b);
reverse(b,b+m);
// puts("?");
for(int i=0;i<n;i++){
//cout<<i<<endl;
if(i<m) a[i]=b[i]-'0';
else a[i]=0;
if(a[i]) cnt++;
}
// puts("!!");
ans[0]=1;
sub();
// puts("??");
for(int i=1;i<=n;i++){
// cout<<"i="<<i<<endl;
//print(ans,i);
//print(a,n);
if(chk()){
print(ans,i);
return 0;
}
int len=n-i;
if(!a[len]){
ans[i]=0;
sub();
continue;
}
a[len]=0; cnt--;
ans[i]=1;
}
return 0;
}
D - LIS 2
难搞的地方在于取min,考虑比较\((a_i \oplus a_j,b_i \oplus b_j)\)两者的过程:是在它们第一位不一样的地方比较,取该位为0的那个。
而判断两个数在某位是否相等,可以想到异或操作,然后把这两者异或起来后,由异或运算的交换律可得等价于\((a_i \oplus b_i) \oplus (a_j \oplus b_j)\),这样就转成两个位置独立的式子的异或值,然后枚举这个第一个为1的位置,在trie树上记一些东西,再类似地查一下就行。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5,M=18;
int n,a[N],b[N],ch[N][2],cnt=1;
ll tot[N][2],suma[N][2][M],sumb[N][2][M];
int main() {
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++){
int z=(a[i]^b[i]);
int u=1;
for(int p=M-1;p>=0;p--){
int q=(z&(1<<p))>>p,v=(a[i]&(1<<p))>>p;
if(!ch[u][q]) ch[u][q]=++cnt;
u=ch[u][q];
for(int k=0;k<M;k++){
if(a[i]&(1<<k)) suma[u][v][k]++;
if(b[i]&(1<<k)) sumb[u][v][k]++;
}
tot[u][v]++;
}
}
ll ans=0;
for(int i=1;i<=n;i++){
//cout<<"i="<<i<<endl;
int z=(a[i]^b[i]);
int u=1;
for(int p=M-1;p>=0;p--){
int q=(z&(1<<p))>>p;
int cur=ch[u][!q];
int v=(a[i]&(1<<p))>>p;
//v=(!v);
for(int k=0;k<M;k++){
if(a[i]&(1<<k)) ans+=(tot[cur][v]-suma[cur][v][k])*(1<<k);
else ans+=suma[cur][v][k]*(1<<k);
}
//cout<<ans<<endl;
v=(!v);
for(int k=0;k<M;k++){
if(b[i]&(1<<k)) ans+=(tot[cur][v]-sumb[cur][v][k])*(1<<k);
else ans+=sumb[cur][v][k]*(1<<k);
}
u=ch[u][q];
//cout<<p<<" "<<ans<<endl;
}
int cur=u;
for(int v=0;v<2;v++)
for(int k=0;k<M;k++){
if(a[i]&(1<<k)) ans+=(tot[cur][v]-suma[cur][v][k])*(1<<k);
else ans+=suma[cur][v][k]*(1<<k);
}
//cout<<"ans="<<ans<<endl;
}
cout<<ans/2<<endl;
return 0;
}
E - Priority Queue
一般的思路是,考虑如何判断某个最终集合是否合法,但这样会得到非常复杂的转移,不是很可做。
从边界的角度,考虑令留下的集合最小和最大的情况(非严格定义,感性理解):最小显然可以做到删掉最大的那些元素;最大就是从小到大加入元素。然后发现这样做之后,对一个数\(v\),最小的做法使得小于它的元素最多;而最大的做法使得大于它的元素最多。
然后考虑把最终留下的元素升序排序,则对于某个合法的答案,每个元素必须要在,上面得出的对应位置的值作为上下界的区间内。(由上面的结论可以反证出来)
然后可以证明,这个条件是充分的。因为对于留下的最大的集合(删去的最小),一定可以通过修改,将本来要删掉的集合换成更大的(直接一一对应过去即可)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=10005,P=998244353;
void inc(int& x,int y){
x+=y;
if(x>=P) x-=P;
if(x<0) x+=P;
}
int sum(int x,int y){
x+=y;
if(x>=P) x-=P;
if(x<0) x+=P;
return x;
}
void mul(int& x,int y){
x=1ll*x*y%P;
}
int prd(int x,int y){
return 1ll*x*y%P;
}
int n,m,a[N],mx[N];
set<int>st;
int pos[N],f[N][N];
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++) mx[i]=1;
int tot=0;
for(int i=1;i<=n+m;i++){
int x;
scanf("%d",&x);
if(x==1){
st.insert(++tot);
}
else{
set<int>:: iterator it=st.end();
it--;
mx[*it]=0;
st.erase(it);
}
}
for(int i=1,j=0;i<=n;i++) if(mx[i]) pos[++j]=i;//cout<<pos[j]<<" "; puts("");
f[0][0]=1;
for(int j=1;j<=n;j++) inc(f[0][j],f[0][j-1]);
for(int i=1;i<=n-m;i++){
for(int j=1;j<=pos[i];j++) f[i][j]=f[i-1][j-1];
for(int j=1;j<=n;j++) inc(f[i][j],f[i][j-1]);
}
cout<<f[n-m][n]<<endl;
return 0;
}