题解 Educational Codeforces Round 80 [Rated for Div. 2](CF1288)

前言: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 }
View Code

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\n",1ll*a*y[i]);
11     }
12     return 0;
13 }
View Code

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 }
View Code

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\n",o1,o2);//输出任意一组满足条件的解即可 
34     return 0;
35 }
View Code

这怕不是码量最小的一次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\n",mn[i],mx[i]); 
19     return 0;
20 }
View Code

upd:意外地发现这场的ABCDE代码加起来还没Div1的E题三分之一多吧。。

F:??打不开告辞,明早去机房补。

upd:最近事有点多,F先咕着,寒假来补吧。

posted @ 2020-01-15 01:53  kylin_xy  阅读(634)  评论(14编辑  收藏  举报