前言: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\n",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\n",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\n",mn[i],mx[i]); 19 return 0; 20 }
upd:意外地發現這場的ABCDE代碼加起來還沒Div1的E題三分之一多吧。。
F:??打不開告辭,明早去機房補。
upd:最近事有點多,F先咕着,寒假來補吧。