[考试反思]0323省选模拟53:常数

服气了。$T1$被卡常了。

出题人你卡常就卡常吧,但是能不能不要绑着子任务卡啊。

你绑子任务就绑子任务但是你好歹给被卡常的留一档分别直接卡成和$n^2$一样啊。

你卡就卡了能不能直接把最后一个子任务设成$63pts$啊。

这题被你卡常一下正常考试一半多的分数就没了。

算了出题人向来不考虑答题的感受,自己没被卡于是就卡别人呗。

本来大概是$120pts$大概也能混个前三,真有意思。

然后$T3std$还是巨型毒瘤$whzzt$写的,大力卡常,考后浪费了不少时间。。。

毒瘤,真毒瘤

 

T1:数(number)

大意:求$n$位的可以有前导$0$的回文的奇数位上数字和等于偶数位上数字和的数的个数。$T \le 10,n \le 10^6$

我还寻思这这玩意多测是要闹哪样,原来是卡常啊。。。

$dy$原题。思路不难。

按照$n \ mod \ 4$分类讨论,偶数直接乱填一半剩一半对称。

余$1$那么就枚举中间这一位是几然后两边的差确定,奇数位加偶数位减,和为$0$

把偶数位减$x$改为偶数位$+9-x$。就可以插板容斥组合数了。

余$3$大概也一样,枚举中间两位,但是需要记忆化,否则常数肯定升天。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 1000000007
 4 #define S 2750001
 5 map<int,int>M[S];
 6 int fac[S],inv[S],t,n;
 7 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 8 int C(int b,int t){return t<0||t>b?0:1ll*fac[b]*inv[t]%mod*inv[b-t]%mod;}
 9 int cal(int n,int v){
10     v=abs(v);
11     if(!n)return v?0:1;
12     if(M[n][v])return M[n][v];int rn=n,rv=v;
13     v+=n*9;int ans=0;n<<=1;
14     for(int i=0;v>=0;++i,v-=10)ans=(ans+(i&1?-1ll:1ll)*C(n,i)*C(v+n-1,n-1))%mod;
15     return M[rn][rv]=(ans+mod)%mod;
16 }
17 int main(){
18     for(int i=fac[0]=1;i<S;++i)fac[i]=1ll*fac[i-1]*i%mod;
19     inv[S-1]=qp(fac[S-1],mod-2);
20     for(int i=S-2;~i;--i)inv[i]=inv[i+1]*(i+1ll)%mod;
21     cin>>t;while(t--){cin>>n;
22         if(n%2==0)cout<<qp(10,n/2)<<endl;
23         else if(n%4==1){
24             int ans=0;
25             for(int i=0;i<5;++i)ans=(ans+cal(n/4,i))%mod;
26             cout<<ans<<endl;
27         }else{
28             int ans=0;
29             for(int i=0;i<5;++i)for(int j=0;j<=9;++j)ans=(ans+cal(n/4,i-j))%mod;
30             cout<<ans<<endl;
31         }
32     }
33 }
View Code

 

T2:序列

大意:给定序列,要求重新排列后奇偶相间,一种方案的代价为所有数字交换前后坐标差绝对值,最小化代价的基础上最小化新序列字典序。$n \le 10^5,a_i \le 10^9$

大概是个贪心。发现最小代价一定是所有奇数元素内部顺序不变,偶数同理。

奇数偶数可以分开讨论了。最后一起合并就行。

除了内部顺序不变,我们可以发现如果元素$a,b$对应的新位置是$p_a,p_b$,有$a<b\le p_a < p_b$那么其实$a$可以到$p_b$上,$b$可以到$p_a$上

同理,大小于号反过来也成立。

这样,更广泛的结论是:对于一个极长的变化方向相同的段,以$a<p_a$为例,那么$a$其实可以填在任意$p_i>a$

那么就可以草率的贪心,对于每个极长同向段,扫描线扫一下,遇到$p_a$就加入堆,遇到$a$就取最值使字典序尽量小。复杂度$O(nlogn)$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 111111
 4 int a[S],n,t,p[S],ans1[S],ans2[S],c,C,c1,c2;
 5 void pt(int*a){for(int i=1;i<=n;++i)printf("%d ",a[i]);}
 6 priority_queue<int>q; map<int,int>M;
 7 void solve(int*A,int s,int t,int o){
 8     if(o){
 9         int p1=s,p2=s;
10         while(p1<=t||p2<=t){
11             while(p[p1]<=p2&&p1<=t)q.push(-a[p[p1]]),p1+=2;
12             while((p1>t||p2<p[p1])&&p2<=t)A[p2]=-q.top(),q.pop(),p2+=2;
13         }
14     }else{
15         int p1=t,p2=t;
16         while(p1>=s||p2>=s){
17             while(p[p1]>=p2&&p1>=s)q.push(a[p[p1]]),p1-=2;
18             while((p1<s||p2>p[p1])&&p2>=s)A[p2]=q.top(),q.pop(),p2-=2;
19         }
20     }
21 }
22 void work(int t,int*A){
23     c=-1;C=0;for(int i=1;i<=n;++i)if(a[i]&1^t)p[c+=2]=i;else p[C+=2]=i;
24     for(int i=1,d;d=i>=p[i],i<=n;)for(int j=i;;j+=2)if(j>=p[j]^d||j>n){solve(A,i,j-2,d);i=j;break;}
25     for(int i=2,d;d=i>=p[i],i<=n;)for(int j=i;;j+=2)if(j>=p[j]^d||j>n){solve(A,i,j-2,d);i=j;break;}
26 }
27 int main(){
28     cin>>n;
29     for(int i=1;i<=n;++i)scanf("%d",a+i),t+=a[i]&1,M[a[i]]=i;
30     if(n&1)work(t<n-t,ans1),pt(ans1);
31     else{
32         work(0,ans1);work(1,ans2);
33         for(int i=1;i<=n;++i)c1+=abs(i-M[ans1[i]]),c2+=abs(i-M[ans2[i]]);
34         if(c1!=c2)return pt(c1<c2?ans1:ans2),0;
35         for(int i=1;i<=n;++i)if(ans1[i]!=ans2[i]){pt(ans1[i]<ans2[i]?ans1:ans2);break;}
36     }
37 }
View Code

 

T3:烤仓鼠

大意:$n$组人分$m$个东西,不能劈开,每组有$a_i$人。最小化$d$,满足任意两人分到的数目差$\le d$,任意两组分到的总数差$\le d$。

要求构造方案。(输出每组的数量)。$n \le 10^6,\sum a_i \le 10^9,m\le 10^{18}$

先不说构造方案,先求$d$。这个东西当然有单调性,所以可以采取二分答案。问题在于检查是否合法。

首先,明显的是,如果把所有组按照人数排序,那么一定存在一组最优解满足前面的组分到的数量小于等于后面的组。

我们设每个人至少分到$x$个,那么第一组的最大数量应该就是$a[1] \times (x+d)$。其余组虽然人数比他们多,但总数最多只能比它们多$d$

还要注意,对于所有人数和第一组相同的组它们的上届也和第一组一样。

组之间相互独立没有影响。那么我们能得到,所有组的上届之和,也就是我们最多可以发放多少物品:

$maxsum=(a[1] \times same1) \times (x+d) + ( (a[1] \times (n-same1 ) \times (x+d) + (n-same1 ) \times d ) $

而我们又知道如果$maxsum<m$那么一定无解。

于是我们移项让$x$做主元,就能得到$x$的上届:$x \le \lceil \frac{ m-(a[1] \times n + (n-same1) \times d) }{a[1] \times n} \rceil$

当然如果给每个人都分配$x$就直接超过了$m$那么直接非法。当然$x$也要对$0$取$max$

我们现在知道物品一定不会出现分不完的情况(因为$x$已经保证总数小于上届了)。问题就是:够不够分,以及每组是否能合法。

按照上面排序的说法,第$n$组分到的一定是最多的,其它组都不会超过它。

而最后一组的下界是$a[n]\times x$故其它各组的下界之一就是$\ge a[n] \times x -d$

当然对于第$i$组而言还有一个下界是$\ge a[i] \times x$。两个下届取$max$就是当前组真正的下界。

然后当前组的上界也很简单:如果它的人数和第一组一样多,那么上届就是$a[1] \times (x+d)$否则就是$a[1] \times (x+d) + d$

(其实应该和$a[i] \times (x+d)$取$min$但是既然$a[i]-a[1] \ge 1$那么明显后面这个界就被前面包含了。

所以对于每个组,如果下界$>$上界那么自然无解。

同时,如果下界之和大于$m$了那么就不够分了,也是无解。

在满足上述所有要求的情况下,每组都有一个合法区间,且总和区间也包含$m$。

那么就一定存在一种方案来给每个组分物件。组内分物件那就更简单了。

(不必再考虑人与人之间的限制,因为我们在限制组的时候,人的上下界也已经用$[x,x+d]$卡住了,所以组合法人也一定合法)

所以满足上述条件就一定存在合法方案。就这样就可以二分了,时间复杂度$O(nlogm)$。

 

然后是构造方案。这个比较神奇。

首先如果最后$d=0$那么方案就可以直接输出了。

这条卡常理论上当然可以不加,但是无良出题人在$std$里加了,然后就拿来卡你常数了,所以你也必须加。

首先我们对于一个确定的$d$我们依旧可以解出一个$x$。

然后我们还是可以按照上面的方式得到每个组的上下界。

我们首先给每个组都分若干东西直至每个组都达到上届,这时候很可能不够分,于是我们先设为上界,然后再往回要。

首先我们当然可以把某些突出的组压下来:再不低于这个组的下届的情况下,每个组的数量都下压至$a[i] \times (n+d)$。

(也相当于在尽量缩小组间极差使之合法,现在组差一定$\le d$因为最小的也是$a[i] \times (n+d)$。如果有的组超过这个值$+d$那么一定非法)

既然组之间已经合法了,那么接下来我们就可以把所有组同时下调相同数目,一定依旧合法。

但是下降数目不能超过剩余总下降量,也不能使某个组下降到下届以下,同时下降量还应该是$d$的整数倍,表示分的最多的人变成最少的,方便处理。

(这里为了节约常数,不突破下届,可以直接对$mx[n]-mn[n]$取$mn$。因为$n$的下界一定是最大的,而上界又被压平了,所以一定是限制的最紧的)

再接下来,如果下降的还不够,我们考虑继续下调。这时候,我们的下调幅度不能超过$d$,这样依旧能保证下降之后仍然合法。

(其实也一定不会超过$d$,因为上面已经保证了所有组同时下降,就说明,要么是不够让你下降那么多,要么是触及了某些组的下界)

当然下降幅度依旧不能突破每组的下届。当然这里应该倒着扫。不需要继续下降就跳出。才可以保证合法。

这样一轮下降之后,如果依旧还是需要下降,我们就能确定:一定是某些组触及了下界。(应该是后面的组,下界较高)

而我们又知道,我们的这个$d$保证有解,所以我们只要倒着再扫一遍,把遇到的组都拍到下界就行了。

按照道理来讲这个构造只是线性的扫了若干遍,理论上讲是$O(n)$的。

但是其实这里的常数,肉眼可见的大:乘除膜以及好多好多$min,max$。本来$n=10^6$就不小了。

然后出题人还有意卡你,让你直接飞升到$12s$。所以$d=0$的那条特判是有必要加的。。。

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 1111111
 4 long long m,n,a[S],sum[S],same1,o[S],R[S],mn[S],mx[S];
 5 bool chk(long long d){
 6     // MAX_SUM = (a[1]*same1)*(x+d) + [ (a[1]*(n-same1))*(x+d) + (n-same1) * d ] 
 7     // = a[1]*n*(x+d) + d * (n-same1)
 8     // = a[1]*n*x + ( a[1]*n + ( n-same1 ) )*d
 9     // MAX >= m
10     // x<= [ m - ( a[1]*n +(n-same1) )*d ]  /  (a[1]*n) {ceil}
11     long long x=max(0.,ceil((m-(1.*a[1]*n+(n-same1))*d)/a[1]/n)),MIN_SUM=sum[n]*x;
12     if(x>m/sum[n])return 0;
13     for(int i=1;i<=n&&MIN_SUM<=m;++i)
14         if( max(a[i]*x,a[n]*x-d) > 1.*a[1]*(x+d)+(a[i]==a[1]?0:d)) return 0;//MIN_GROUP > MAX_GROUP
15         else MIN_SUM+=max((a[n]-a[i])*x-d,0ll);//Can not be less than a[n]*x-d,so add (a[n]*x-d) - (a[i]*x)
16     return MIN_SUM<=m;
17 }
18 void solve(long long d){
19     if(!d){for(int i=1;i<=n;++i)printf("%lld ",m/n);return;}
20     long long x=max(0.,ceil((m-(1.*a[1]*n+(n-same1))*d)/a[1]/n)),tot=0,w,MN=a[1]*(d+x);
21     for(int i=1;i<=n;++i)mn[i]=max(a[i]*x,a[n]*x-d),mx[i]=a[1]*(x+d)+(a[i]!=a[1]?d:0),tot+=mx[i];//init to max
22     for(int i=n;i&&tot!=m;--i)w=min(mx[i]-max(mn[i],MN),tot-m),mx[i]-=w,tot-=w;//make it legel when a[1] is at its max
23     if(d){long long T=min((tot-m)/n,mx[n]-mn[n])/d*d;if(T)for(int i=n;i;--i)mx[i]-=T,tot-=T;}//decrease averangely,still legel
24     MN=min(mx[n]-mn[n],d);//cannot decrease more than d.
25     for(int i=n;i&&tot!=m;--i)w=min(min(mx[i]-mn[i],MN),tot-m),tot-=w,mx[i]-=w;
26     for(int i=n;i&&tot!=m;--i)w=min(mx[i]-mn[i],tot-m),tot-=w,mx[i]-=w;
27     for(int i=1;i<=n;++i)printf("%lld ",mx[R[i]]);
28 }
29 char buffer[S],*s,*t;
30 #define getchar() ((s==t&&(t=(s=buffer)+fread(buffer,1,S,stdin),s==t))?EOF:*s++)
31 int read(){
32     register int p=0;register char ch=getchar();
33     while(ch<'0'||ch>'9')ch=getchar();
34     while(ch>='0'&&ch<='9')p=(p<<3)+(p<<1)+ch-'0',ch=getchar();
35     return p;
36 }
37 int main(){//freopen("hamster6-1.in","r",stdin);freopen("0.out","w",stdout);
38     cin>>n>>m;
39     for(int i=1;i<=n;++i)a[i]=read(),o[i]=i;
40     sort(o+1,o+1+n,[](int x,int y){return a[x]<a[y];}); sort(a+1,a+1+n);
41     for(int i=1;i<=n;++i)R[o[i]]=i;
42     for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i],same1+=a[i]==a[1];
43     long long l=0,r=m,ans;
44     while(l<=r)if(chk(l+r>>1))r=ans=l+r>>1,r--;else l=(l+r>>1)+1;
45     cout<<ans<<endl;solve(ans);
46 }
也可以参照一下代码及注释

 

posted @ 2020-03-25 22:43  DeepinC  阅读(258)  评论(0编辑  收藏  举报