前言:11点的时候某天下第一可爱的萌神问我怎么不打CF,跑去开题,11:30终于开了C和D(我家网速令人头秃),秒了一下,考后萌神差点阿克并告诉我E的tag于是我赛后补题。
A:n/x上取整是(n-1)/x+1,式子变形成x+1+(n-1)/(x+1)<=d。根据a+b>=2√ab随便化简一下。(20s秒了??)
1 #include<stdio.h> 2 #include<math.h> 3 using namespace std; 4 int T,n,d,x,y; 5 int main(){ 6 scanf("%d",&T); 7 while(T--) 8 scanf("%d%d",&d,&n),x=sqrt(n-1),y=x-1,((n<=d)||(x+1+(n-1)/(x+1)<=d)||(y+1+(n-1)/(y+1)<=d))?puts("YES"):puts("NO"); 9 return 0; 10 }
B:a*b+a+b=a*10b的位数+b。化简得知b+1=10b的位数。所以只有9,99,999……这样是可行的。那么统计B是几位数啥的随便算算再乘个A输出,还有要判相等。(1min不到秒了??)
1 #include<stdio.h> 2 const int x[]={0,0,9,99,999,9999,99999,999999,9999999,99999999,999999999,1000000001}; 3 const int y[]={0,0,1,2,3,4,5,6,7,8,9}; 4 int T,a,b,i; 5 int main(){ 6 scanf("%d",&T); 7 while(T--){ 8 scanf("%d%d",&a,&b); 9 for(i=1;i<=10&&x[i+1]<=b;++i); 10 printf("%lld ",1ll*a*y[i]); 11 } 12 return 0; 13 }
C:做过构造解的原题呢。会两种dp的写法。(我似乎看了10s就会了O(n2m)的做法,想了10min才想到O(nm)的。。还是太菜了)
由于A不降B不升,而且ai<=bi,所以b最小的比a最大的大,那我们可以把b翻转过来并起来看,于是这就是要我们构造一个长为2*m的单增序列。
一眼秒的O(n2m)做法:设f[i][j]=到i,选择的元素为j的方案。那么f[i][j]+=f[i-1][k](k<=j)。ans=Σf[2*m][i],1<=i<=n
再想想的O(nm)做法:f[i][j]+=f[i-1][j]+f[i][j-1]。就是把枚举k的那一维省去,直接把j的贡献用j-1的贡献计算,这里利用前缀和的思想,就相当于j-1已经存了之前的答案了,可以直接转移到j。 ans=f[2*m][n]。
upd:经评论区@碳的还原性大佬指正,我思考了一下确实和元素连续没关系,应该说是利用前缀和的思想统计这样更恰当一些。。感谢大佬!
upd:经评论区@落雨廾匸大佬指正,O(nm)的做法不需要累加,只需要输出最后答案就行了,因为这里利用了前缀和思想f[2*m][n]+=f[2*m][n-1]+f[2*m-1][n]这一步已经把之前的累加了。感谢大佬!
很抱歉O(nm)的做法我因为懒没写,所以有不少锅,感谢各位大佬的指正!
放个n2m的代码:
1 #include<stdio.h> 2 #define it register int 3 #define il inline 4 const int P=1e9+7; 5 il void mo(int &p,const int &q){p+=q,p=(p>=P?p-P:p);} 6 int f[22][1005],m,n,ans; 7 int main(){ 8 scanf("%d%d",&n,&m),m<<=1; 9 for(it i=1;i<=n;++i) f[1][i]=1; 10 for(it i=2,j,k;i<=m;++i) 11 for(j=1;j<=n;++j) 12 for(k=1;k<=j;++k) 13 mo(f[i][j],f[i-1][k]); 14 for(it i=1;i<=n;++i) mo(ans,f[m][i]); 15 printf("%d",ans); 16 return 0; 17 }
D:开始就想了个二分+枚举子集。萌神说只要枚举子集看是A还是B的贡献就行。所以为什么我要多个log?人间迷惑.jpg
不过,利用状压的思想带个log似乎也可以过?而且跑的不是很慢??(据说萌神的二分T飞了??)
做法是二分现在这个最大的最小B,然后每次把满足条件即a[i][j]>=mid的状态压缩起来,接着枚举所有压缩的状态,只要存在i|j==(1<<m)-1即全部都能选到(可以抽象理解为抽出两列至少有一列是有解的)说明可行,调整二分的上下界。思路很简单,代码很短。
1 #include<stdio.h> 2 #define it register int 3 #define il inline 4 const int M=300005; 5 const int N=12; 6 int p[N],id[M],a[M][N],n,m,o1,o2,lim; 7 bool ck(const int&x){ 8 it i,j,sta; 9 for(i=0;i<=lim;++i) id[i]=0;//清空所有状态 10 for(i=1;i<=n;++i){ 11 sta=0; 12 for(j=1;j<=m;++j) 13 if(a[i][j]>=x) sta|=p[j];//满足条件的状态压缩起来 14 id[sta]=i;//记录这个状态是抽出第i列得到的 15 } 16 for(i=0;i<=lim;++i)//枚举i,j两个状态进行检查,是否可以抽出id[i]和id[j]以得到答案 17 for(j=i;j<=lim;++j)//j从i枚举,以免重复计算 18 if((i|j)==lim&&id[i]&&id[j]) 19 return o1=id[i],o2=id[j],1; 20 return 0; 21 } 22 il void ms(){//MidSearch 即二分现在最大最小的b 23 int l=0,r=1e9,mid; 24 while(l<=r) 25 mid=l+r>>1,ck(mid)?l=mid+1:r=mid-1;//满足条件,说明还可能存在较大解 26 } 27 int main(){ 28 scanf("%d%d",&n,&m); 29 for(it i=1,j;i<=n;++i) 30 for(j=1;j<=m;++j) scanf("%d",&a[i][j]); 31 lim=(1<<m)-1; 32 for(it i=1;i<=m;++i) p[i]=1<<i-1;//常数优化 33 ms(),printf("%d %d ",o1,o2);//输出任意一组满足条件的解即可 34 return 0; 35 }
这怕不是码量最小的一次div2。。
E:萌神说是莫队,我:??我打不开E。
upd:早上去机房好不容易开了EF发现做过E的类似题??所以昨晚只有F有质量是吗……
zjf神仙说E是树状数组,于是我想了5min后删了我写了一半的Splay……
树状数组的做法是:由于最多只有m次操作,所以每次先把每个数的位置放在m+1+i处,就相当于向后推移m+1。每次把一个数提到前面就是消除他这个位置对后面的影响,并且在前面这个位置加上影响,然后更新这个数的位置。就相当于一个盒子,每次把后面的抽到前面去,统计有多少个在他前面的数。最后不要忘记统计一遍所有数的位置算出其最右位置。
1 #include<stdio.h> 2 #define it register int 3 #define ct const int 4 #define il inline 5 const int N=1000005; 6 int p[N],t[N],n,m,mn[N],mx[N]; 7 il void ckMax(int &p,ct q){p=(p>q?p:q);}//常数优化 8 il void add(it x,ct num){while(x<N) t[x]+=num,x+=(x&-x);} 9 il void cal(it x,int &now){now=0;while(x) now+=t[x],x-=(x&-x);}//树状数组 10 int main(){ 11 scanf("%d%d",&n,&m); 12 it i,pos=m+1,now; 13 for(i=1;i<=n;++i) 14 mn[i]=mx[i]=i,p[i]=pos+i,add(p[i],1);//先把位置往后推 15 while(m--) 16 scanf("%d",&i),mn[i]=1,cal(p[i],now),ckMax(mx[i],now),add(p[i],-1),p[i]=pos--,add(p[i],1);//消除原来位置的影响,把这个数放到新位置上 17 for(i=1;i<=n;++i) cal(p[i],now),ckMax(mx[i],now); 18 for(i=1;i<=n;++i) printf("%d %d ",mn[i],mx[i]); 19 return 0; 20 }
upd:意外地发现这场的ABCDE代码加起来还没Div1的E题三分之一多吧。。
F:??打不开告辞,明早去机房补。
upd:最近事有点多,F先咕着,寒假来补吧。